From ba94119d028208728637fc6d6ae64a8cd56d4f5b Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Fri, 4 Sep 2020 22:00:59 +0100 Subject: [PATCH] Initial commit --- .gitignore | 14 + Data/Scripts.rxdata | Bin 0 -> 359 bytes Data/Scripts/001_Settings.rb | 353 + .../001_Technical/001_Ruby Utilities.rb | 211 + .../001_Technical/002_RGSS2Compatibility.rb | 608 + Data/Scripts/001_Technical/003_RPG__Sprite.rb | 534 + Data/Scripts/001_Technical/004_Win32API.rb | 104 + Data/Scripts/001_Technical/005_Sockets.rb | 696 + .../Scripts/001_Technical/006_DebugConsole.rb | 158 + .../001_Technical/007_Sprite_Resizer.rb | 757 + .../001_Technical/008_Plugin_Manager.rb | 376 + .../001_Game_Temp.rb | 104 + .../002_Game_Switches.rb | 36 + .../003_Game_Variables.rb | 36 + .../004_Game_SelfSwitches.rb | 30 + .../003_Game classes/001_Game_Screen.rb | 156 + .../003_Game classes/002_Game_System.rb | 289 + .../003_Game classes/003_Game_Picture.rb | 156 + .../003_Game classes/004_Game_CommonEvent.rb | 81 + .../003_Game classes/005_Game_Character.rb | 869 + .../003_Game classes/006_Game_Event.rb | 247 + .../003_Game classes/007_Game_Player.rb | 505 + .../008_Game_Player_Visuals.rb | 103 + Data/Scripts/003_Game classes/009_Game_Map.rb | 438 + .../010_Game_Map_Autoscroll.rb | 194 + .../003_Game classes/011_MapFactory.rb | 488 + .../Scripts/004_Sprites/001_Sprite_Picture.rb | 58 + Data/Scripts/004_Sprites/002_Sprite_Timer.rb | 44 + .../004_Sprites/003_Sprite_Character.rb | 174 + .../004_Sprites/004_Sprite_WaterReflection.rb | 90 + .../004_Sprites/005_Sprite_SurfBase.rb | 77 + Data/Scripts/004_Sprites/006_Spriteset_Map.rb | 167 + .../004_Sprites/007_Spriteset_Global.rb | 34 + .../004_Sprites/008_Sprite_AnimationSprite.rb | 94 + .../004_Sprites/009_Sprite_DynamicShadows.rb | 255 + .../Scripts/004_Sprites/010_ParticleEngine.rb | 586 + .../005_Map renderer/001_Tilemap_XP.rb | 947 + .../002_Tilemap_Perspective.rb | 447 + .../005_Map renderer/003_Tilemap_Original.rb | 118 + .../005_Map renderer/004_TilemapLoader.rb | 70 + .../005_Map renderer/005_TileDrawingHelper.rb | 136 + .../006_Events and files/001_Interpreter.rb | 1469 + .../006_Events and files/002_EventHandlers.rb | 172 + .../006_Events and files/003_File_Mixins.rb | 163 + .../006_Events and files/004_Intl_Messages.rb | 776 + .../006_Events and files/005_PBDebug.rb | 40 + Data/Scripts/007_Audio/001_Audio.rb | 376 + Data/Scripts/007_Audio/002_AudioPlay.rb | 292 + Data/Scripts/007_Audio/003_AudioUtilities.rb | 1350 + .../008_Objects and windows/001_FileTests.rb | 657 + .../002_BitmapCache.rb | 497 + .../008_Objects and windows/003_Window.rb | 608 + .../004_SpriteWindow.rb | 781 + .../005_SpriteWindow_text.rb | 2369 ++ .../006_SpriteWindow_sprites.rb | 1085 + .../008_Objects and windows/007_DrawText.rb | 1212 + .../008_Objects and windows/008_Messages.rb | 1431 + .../008_Objects and windows/009_TextEntry.rb | 1676 + .../008_Objects and windows/010_EventScene.rb | 709 + .../011_Interpolators.rb | 172 + Data/Scripts/009_Scenes/001_Scene_Map.rb | 239 + Data/Scripts/009_Scenes/002_Scene_Intro.rb | 134 + Data/Scripts/009_Scenes/003_Scene_Controls.rb | 46 + Data/Scripts/009_Scenes/004_Scene_Movie.rb | 52 + Data/Scripts/009_Scenes/005_Scene_Credits.rb | 231 + Data/Scripts/009_Scenes/006_Transitions.rb | 1617 + Data/Scripts/010_Data/001_MiscData.rb | 472 + Data/Scripts/010_Data/002_PBMove.rb | 104 + Data/Scripts/010_Data/003_PBStatuses.rb | 28 + Data/Scripts/010_Data/004_PBTypes_Extra.rb | 90 + Data/Scripts/010_Data/005_PBNatures.rb | 87 + Data/Scripts/010_Data/006_PBGenderRates.rb | 24 + Data/Scripts/010_Data/007_PBExperience.rb | 197 + Data/Scripts/010_Data/008_PBStats.rb | 66 + Data/Scripts/010_Data/009_PBRibbons.rb | 259 + Data/Scripts/010_Data/010_PBEggGroups.rb | 42 + Data/Scripts/010_Data/011_PBColors.rb | 33 + Data/Scripts/010_Data/012_PBHabitats.rb | 32 + .../001_Battler/001_PokeBattle_Battler.rb | 665 + .../001_Battler/002_Battler_Initialize.rb | 326 + .../001_Battler/003_Battler_ChangeSelf.rb | 301 + .../001_Battler/004_Battler_Statuses.rb | 573 + .../001_Battler/005_Battler_StatStages.rb | 310 + .../001_Battler/006_Battler_AbilityAndItem.rb | 322 + .../001_Battler/007_Battler_UseMove.rb | 729 + .../008_Battler_UseMove_Targeting.rb | 193 + .../009_Battler_UseMove_SuccessChecks.rb | 538 + .../010_Battler_UseMove_TriggerEffects.rb | 188 + Data/Scripts/011_Battle/001_PBEnvironment.rb | 30 + .../002_Move/001_PokeBattle_Move.rb | 140 + .../011_Battle/002_Move/002_Move_Usage.rb | 350 + .../002_Move/003_Move_Usage_Calculations.rb | 491 + .../002_Move/004_Move_Effects_Generic.rb | 714 + .../002_Move/005_Move_Effects_000-07F.rb | 2924 ++ .../002_Move/006_Move_Effects_080-0FF.rb | 3746 +++ .../002_Move/007_Move_Effects_100-17F.rb | 2626 ++ Data/Scripts/011_Battle/002_PBWeather.rb | 32 + .../003_Battle/001_PokeBattle_BattleCommon.rb | 230 + .../003_Battle/002_PokeBattle_Battle.rb | 781 + .../003_Battle/003_Battle_StartAndEnd.rb | 537 + .../004_Battle_ExpAndMoveLearning.rb | 263 + .../005_Battle_Action_AttacksPriority.rb | 254 + .../003_Battle/006_Battle_Action_Switching.rb | 411 + .../003_Battle/007_Battle_Action_UseItem.rb | 144 + .../003_Battle/008_Battle_Action_Running.rb | 145 + .../003_Battle/009_Battle_Action_Other.rb | 192 + .../003_Battle/010_Battle_Phase_Command.rb | 250 + .../003_Battle/011_Battle_Phase_Attack.rb | 190 + .../003_Battle/012_Battle_Phase_EndOfRound.rb | 667 + .../011_Battle/003_PBBattleTerrains.rb | 25 + .../011_Battle/004_AI/001_PokeBattle_AI.rb | 69 + Data/Scripts/011_Battle/004_AI/002_AI_Item.rb | 182 + .../011_Battle/004_AI/003_AI_Switch.rb | 182 + Data/Scripts/011_Battle/004_AI/004_AI_Move.rb | 287 + .../004_AI/005_AI_Move_EffectScores.rb | 3075 ++ .../004_AI/006_AI_Move_Utilities.rb | 675 + Data/Scripts/011_Battle/004_PBTargets.rb | 70 + .../001_PokeBattle_Animation.rb | 264 + .../002_PokeBattle_SceneAnimations.rb | 882 + .../003_PokeBattle_SceneConstants.rb | 63 + .../004_PokeBattle_SceneElements.rb | 657 + .../005_PokeBattle_SceneMenus.rb | 548 + .../005_Battle scene/006_PokeBattle_Scene.rb | 351 + .../005_Battle scene/007_Scene_Initialize.rb | 191 + .../005_Battle scene/008_Scene_Commands.rb | 467 + .../005_Battle scene/009_Scene_Animations.rb | 542 + Data/Scripts/011_Battle/005_PBEffects.rb | 182 + Data/Scripts/011_Battle/006_BattleHandlers.rb | 611 + .../001_PokeBattle_AnimationPlayer.rb | 879 + .../002_PokeBattle_SafariZone.rb | 494 + .../003_PokeBattle_BugContest.rb | 87 + .../004_PokeBattle_BattlePalace.rb | 275 + .../005_PokeBattle_BattleArena.rb | 313 + .../006_PokeBattle_BattleRecord.rb | 297 + .../007_PokeBattle_DebugScene.rb | 85 + .../008_PokeBattle_BattlePeer.rb | 65 + .../009_PokeBattle_Clauses.rb | 244 + .../007_BattleHandlers_Abilities.rb | 2507 ++ .../011_Battle/008_BattleHandlers_Items.rb | 1587 + .../011_Battle/009_PokeBall_CatchEffects.rb | 245 + .../011_Battle/010_PokeBattle_ActiveField.rb | 90 + .../011_Battle/011_PokeBattle_DamageState.rb | 84 + Data/Scripts/012_Overworld/001_PBTerrain.rb | 93 + .../Scripts/012_Overworld/002_PField_Field.rb | 1387 + .../012_Overworld/003_PField_Visuals.rb | 729 + .../012_Overworld/004_PField_Weather.rb | 274 + .../012_Overworld/005_PField_Metadata.rb | 225 + .../012_Overworld/006_PField_Battles.rb | 681 + .../012_Overworld/007_PField_Encounters.rb | 468 + .../008_PField_EncounterModifiers.rb | 45 + .../009_PField_RoamingPokemon.rb | 247 + .../010_PField_RandomDungeons.rb | 584 + .../012_Overworld/011_PField_FieldMoves.rb | 996 + .../012_Overworld/012_PField_BerryPlants.rb | 584 + .../012_Overworld/013_PField_DayCare.rb | 443 + .../014_PField_DependentEvents.rb | 568 + Data/Scripts/012_Overworld/015_PField_Time.rb | 313 + .../013_Trainers/001_PokeBattle_Trainer.rb | 271 + .../013_Trainers/002_PTrainer_NPCTrainers.rb | 300 + Data/Scripts/014_Items/001_PItem_Items.rb | 966 + .../014_Items/002_PItem_ItemEffects.rb | 1110 + .../014_Items/003_PItem_BattleItemEffects.rb | 677 + Data/Scripts/014_Items/004_PItem_Phone.rb | 302 + Data/Scripts/014_Items/005_PItem_PokeRadar.rb | 252 + Data/Scripts/014_Items/006_PItem_Mail.rb | 117 + Data/Scripts/014_Items/007_PItem_Sprites.rb | 158 + Data/Scripts/014_Items/008_PItem_Bag.rb | 401 + .../015_Pokemon/001_PokeBattle_Pokemon.rb | 937 + Data/Scripts/015_Pokemon/002_Pokemon_Forms.rb | 690 + .../015_Pokemon/003_Pokemon_MegaEvolution.rb | 110 + .../015_Pokemon/004_Pokemon_ShadowPokemon.rb | 708 + .../015_Pokemon/005_Pokemon_Evolution.rb | 334 + .../015_Pokemon/006_Pokemon_Chatter.rb | 49 + .../015_Pokemon/007_Pokemon_Sprites.rb | 364 + .../015_Pokemon/008_Pokemon_Storage.rb | 390 + Data/Scripts/016_UI/001_PScreen_PauseMenu.rb | 277 + .../Scripts/016_UI/002_PScreen_PokedexMenu.rb | 126 + .../Scripts/016_UI/003_PScreen_PokedexMain.rb | 1191 + .../016_UI/004_PScreen_PokedexEntry.rb | 586 + Data/Scripts/016_UI/005_PScreen_Party.rb | 1337 + Data/Scripts/016_UI/006_PScreen_Summary.rb | 1355 + Data/Scripts/016_UI/007_PScreen_Bag.rb | 716 + Data/Scripts/016_UI/008_PScreen_Pokegear.rb | 152 + Data/Scripts/016_UI/009_PScreen_RegionMap.rb | 365 + Data/Scripts/016_UI/010_PScreen_Phone.rb | 148 + Data/Scripts/016_UI/011_PScreen_Jukebox.rb | 132 + .../Scripts/016_UI/012_PScreen_TrainerCard.rb | 110 + Data/Scripts/016_UI/013_PScreen_Load.rb | 631 + Data/Scripts/016_UI/014_PScreen_Save.rb | 156 + Data/Scripts/016_UI/015_PScreen_Options.rb | 624 + Data/Scripts/016_UI/016_PScreen_ReadyMenu.rb | 327 + .../016_UI/017_PScreen_PokemonStorage.rb | 1977 ++ .../Scripts/016_UI/018_PScreen_ItemStorage.rb | 365 + Data/Scripts/016_UI/019_PScreen_PC.rb | 259 + .../Scripts/016_UI/020_PScreen_EggHatching.rb | 234 + Data/Scripts/016_UI/021_PScreen_Evolution.rb | 645 + Data/Scripts/016_UI/022_PScreen_Trading.rb | 248 + .../016_UI/023_PScreen_MoveRelearner.rb | 225 + .../016_UI/024_PScreen_PurifyChamber.rb | 1315 + Data/Scripts/016_UI/025_PScreen_Mart.rb | 932 + .../Scripts/016_UI/026_PScreen_MysteryGift.rb | 427 + Data/Scripts/016_UI/027_PScreen_HallOfFame.rb | 511 + .../017_Other battles/001_PBattle_Safari.rb | 138 + .../002_PBattle_BugContest.rb | 405 + .../003_PBattle_OrgBattle.rb | 985 + .../004_PBattle_OrgBattleRules.rb | 1561 + .../005_PBattle_OrgBattleGenerator.rb | 1396 + .../006_PBattle_BattleSwap.rb | 244 + .../018_Minigames/001_PMinigame_Duel.rb | 407 + .../002_PMinigame_TripleTriad.rb | 1321 + .../003_PMinigame_SlotMachine.rb | 404 + .../004_PMinigame_VoltorbFlip.rb | 626 + .../018_Minigames/005_PMinigame_Lottery.rb | 54 + .../018_Minigames/006_PMinigame_Mining.rb | 626 + .../007_PMinigame_TilePuzzles.rb | 582 + .../001_PSystem_Controls.rb | 292 + .../002_PSystem_System.rb | 137 + .../003_PSystem_FileUtilities.rb | 681 + .../004_PSystem_PokemonUtilities.rb | 505 + .../005_PSystem_Utilities.rb | 1202 + Data/Scripts/020_Debug/001_Debug_Menu.rb | 866 + Data/Scripts/020_Debug/002_Debug_Actions.rb | 925 + Data/Scripts/020_Debug/003_Debug_Pokemon.rb | 866 + Data/Scripts/020_Debug/004_Editor_Screens.rb | 1457 + Data/Scripts/020_Debug/005_Editor_SaveData.rb | 1496 + .../Scripts/020_Debug/006_Editor_DataTypes.rb | 1519 + Data/Scripts/020_Debug/007_Editor_Listers.rb | 618 + .../Scripts/020_Debug/008_Editor_Utilities.rb | 549 + .../020_Debug/009_Editor_TilesetEditor.rb | 209 + .../010_Editor_MapConnectionEditor.rb | 813 + .../020_Debug/011_Editor_SpritePosEditor.rb | 425 + .../012_Editor_BattleAnimationEditor.rb | 3809 +++ Data/Scripts/021_Compiler/001_Compiler.rb | 1289 + Data/Scripts/021_Compiler/002_Compiler_PBS.rb | 1727 ++ .../003_Compiler_MapsAndEvents.rb | 1465 + Data/Scripts/999_Main/999_Main.rb | 69 + Essentials Docs Wiki.URL | 9 + Fonts/pkmndp.ttf | Bin 0 -> 55900 bytes Fonts/pkmndpb.ttf | Bin 0 -> 49496 bytes Fonts/pkmnem.ttf | Bin 0 -> 20008 bytes Fonts/pkmnemn.ttf | Bin 0 -> 19008 bytes Fonts/pkmnems.ttf | Bin 0 -> 18164 bytes Fonts/pkmnfl.ttf | Bin 0 -> 19804 bytes Fonts/pkmnrs.ttf | Bin 0 -> 20116 bytes Fonts/pkmnrsi.ttf | Bin 0 -> 20172 bytes PBS/Gen 5/abilities.txt | 166 + PBS/Gen 5/items.txt | 534 + PBS/Gen 5/moves.txt | 577 + PBS/Gen 5/pokemon.txt | 20972 +++++++++++++ PBS/Gen 5/pokemonforms.txt | 451 + PBS/Gen 5/tm.txt | 547 + PBS/Gen 5/types.txt | 121 + PBS/Gen 7/abilities.txt | 235 + PBS/Gen 7/items.txt | 648 + PBS/Gen 7/moves.txt | 695 + PBS/Gen 7/pokemon.txt | 25894 ++++++++++++++++ PBS/Gen 7/pokemonforms.txt | 1646 + PBS/Gen 7/tm.txt | 553 + PBS/Gen 7/types.txt | 129 + PBS/abilities.txt | 235 + PBS/berryplants.txt | 66 + PBS/btpokemon.txt | 884 + PBS/bttrainers.txt | 2401 ++ PBS/connections.txt | 40 + PBS/encounters.txt | 471 + PBS/fancycuppm.txt | 620 + PBS/fancycupsinglepm.txt | 256 + PBS/fancycupsingletr.txt | 2401 ++ PBS/fancycuptr.txt | 2401 ++ PBS/items.txt | 648 + PBS/littlecuppm.txt | 371 + PBS/littlecuptr.txt | 1601 + PBS/metadata.txt | 392 + PBS/moves.txt | 695 + PBS/phone.txt | 34 + PBS/pikacuppm.txt | 286 + PBS/pikacuptr.txt | 2401 ++ PBS/pokecuppm.txt | 365 + PBS/pokecuptr.txt | 1601 + PBS/pokemon.txt | 25894 ++++++++++++++++ PBS/pokemonforms.txt | 1646 + PBS/shadowmoves.txt | 85 + PBS/tm.txt | 553 + PBS/townmap.txt | 36 + PBS/trainerlists.txt | 25 + PBS/trainers.txt | 117 + PBS/trainertypes.txt | 74 + PBS/types.txt | 129 + PBS/~$okemon.txt | Bin 0 -> 162 bytes README.md | 1 + animmaker.exe | Bin 0 -> 10240 bytes animmaker.txt | 26 + extendtext.exe | Bin 0 -> 4640 bytes extendtext.txt | 4 + gif.dll | Bin 0 -> 32768 bytes knownpoint.bmp | Bin 0 -> 246 bytes rubyscreen.dll | Bin 0 -> 28160 bytes script_dumper.rb | 172 + selpoint.bmp | Bin 0 -> 246 bytes townmapgen.html | 373 + 300 files changed, 227558 insertions(+) create mode 100644 .gitignore create mode 100644 Data/Scripts.rxdata create mode 100644 Data/Scripts/001_Settings.rb create mode 100644 Data/Scripts/001_Technical/001_Ruby Utilities.rb create mode 100644 Data/Scripts/001_Technical/002_RGSS2Compatibility.rb create mode 100644 Data/Scripts/001_Technical/003_RPG__Sprite.rb create mode 100644 Data/Scripts/001_Technical/004_Win32API.rb create mode 100644 Data/Scripts/001_Technical/005_Sockets.rb create mode 100644 Data/Scripts/001_Technical/006_DebugConsole.rb create mode 100644 Data/Scripts/001_Technical/007_Sprite_Resizer.rb create mode 100644 Data/Scripts/001_Technical/008_Plugin_Manager.rb create mode 100644 Data/Scripts/002_Switches and Variables/001_Game_Temp.rb create mode 100644 Data/Scripts/002_Switches and Variables/002_Game_Switches.rb create mode 100644 Data/Scripts/002_Switches and Variables/003_Game_Variables.rb create mode 100644 Data/Scripts/002_Switches and Variables/004_Game_SelfSwitches.rb create mode 100644 Data/Scripts/003_Game classes/001_Game_Screen.rb create mode 100644 Data/Scripts/003_Game classes/002_Game_System.rb create mode 100644 Data/Scripts/003_Game classes/003_Game_Picture.rb create mode 100644 Data/Scripts/003_Game classes/004_Game_CommonEvent.rb create mode 100644 Data/Scripts/003_Game classes/005_Game_Character.rb create mode 100644 Data/Scripts/003_Game classes/006_Game_Event.rb create mode 100644 Data/Scripts/003_Game classes/007_Game_Player.rb create mode 100644 Data/Scripts/003_Game classes/008_Game_Player_Visuals.rb create mode 100644 Data/Scripts/003_Game classes/009_Game_Map.rb create mode 100644 Data/Scripts/003_Game classes/010_Game_Map_Autoscroll.rb create mode 100644 Data/Scripts/003_Game classes/011_MapFactory.rb create mode 100644 Data/Scripts/004_Sprites/001_Sprite_Picture.rb create mode 100644 Data/Scripts/004_Sprites/002_Sprite_Timer.rb create mode 100644 Data/Scripts/004_Sprites/003_Sprite_Character.rb create mode 100644 Data/Scripts/004_Sprites/004_Sprite_WaterReflection.rb create mode 100644 Data/Scripts/004_Sprites/005_Sprite_SurfBase.rb create mode 100644 Data/Scripts/004_Sprites/006_Spriteset_Map.rb create mode 100644 Data/Scripts/004_Sprites/007_Spriteset_Global.rb create mode 100644 Data/Scripts/004_Sprites/008_Sprite_AnimationSprite.rb create mode 100644 Data/Scripts/004_Sprites/009_Sprite_DynamicShadows.rb create mode 100644 Data/Scripts/004_Sprites/010_ParticleEngine.rb create mode 100644 Data/Scripts/005_Map renderer/001_Tilemap_XP.rb create mode 100644 Data/Scripts/005_Map renderer/002_Tilemap_Perspective.rb create mode 100644 Data/Scripts/005_Map renderer/003_Tilemap_Original.rb create mode 100644 Data/Scripts/005_Map renderer/004_TilemapLoader.rb create mode 100644 Data/Scripts/005_Map renderer/005_TileDrawingHelper.rb create mode 100644 Data/Scripts/006_Events and files/001_Interpreter.rb create mode 100644 Data/Scripts/006_Events and files/002_EventHandlers.rb create mode 100644 Data/Scripts/006_Events and files/003_File_Mixins.rb create mode 100644 Data/Scripts/006_Events and files/004_Intl_Messages.rb create mode 100644 Data/Scripts/006_Events and files/005_PBDebug.rb create mode 100644 Data/Scripts/007_Audio/001_Audio.rb create mode 100644 Data/Scripts/007_Audio/002_AudioPlay.rb create mode 100644 Data/Scripts/007_Audio/003_AudioUtilities.rb create mode 100644 Data/Scripts/008_Objects and windows/001_FileTests.rb create mode 100644 Data/Scripts/008_Objects and windows/002_BitmapCache.rb create mode 100644 Data/Scripts/008_Objects and windows/003_Window.rb create mode 100644 Data/Scripts/008_Objects and windows/004_SpriteWindow.rb create mode 100644 Data/Scripts/008_Objects and windows/005_SpriteWindow_text.rb create mode 100644 Data/Scripts/008_Objects and windows/006_SpriteWindow_sprites.rb create mode 100644 Data/Scripts/008_Objects and windows/007_DrawText.rb create mode 100644 Data/Scripts/008_Objects and windows/008_Messages.rb create mode 100644 Data/Scripts/008_Objects and windows/009_TextEntry.rb create mode 100644 Data/Scripts/008_Objects and windows/010_EventScene.rb create mode 100644 Data/Scripts/008_Objects and windows/011_Interpolators.rb create mode 100644 Data/Scripts/009_Scenes/001_Scene_Map.rb create mode 100644 Data/Scripts/009_Scenes/002_Scene_Intro.rb create mode 100644 Data/Scripts/009_Scenes/003_Scene_Controls.rb create mode 100644 Data/Scripts/009_Scenes/004_Scene_Movie.rb create mode 100644 Data/Scripts/009_Scenes/005_Scene_Credits.rb create mode 100644 Data/Scripts/009_Scenes/006_Transitions.rb create mode 100644 Data/Scripts/010_Data/001_MiscData.rb create mode 100644 Data/Scripts/010_Data/002_PBMove.rb create mode 100644 Data/Scripts/010_Data/003_PBStatuses.rb create mode 100644 Data/Scripts/010_Data/004_PBTypes_Extra.rb create mode 100644 Data/Scripts/010_Data/005_PBNatures.rb create mode 100644 Data/Scripts/010_Data/006_PBGenderRates.rb create mode 100644 Data/Scripts/010_Data/007_PBExperience.rb create mode 100644 Data/Scripts/010_Data/008_PBStats.rb create mode 100644 Data/Scripts/010_Data/009_PBRibbons.rb create mode 100644 Data/Scripts/010_Data/010_PBEggGroups.rb create mode 100644 Data/Scripts/010_Data/011_PBColors.rb create mode 100644 Data/Scripts/010_Data/012_PBHabitats.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/001_PokeBattle_Battler.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/002_Battler_Initialize.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/003_Battler_ChangeSelf.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/004_Battler_Statuses.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/005_Battler_StatStages.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/006_Battler_AbilityAndItem.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/007_Battler_UseMove.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/008_Battler_UseMove_Targeting.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/009_Battler_UseMove_SuccessChecks.rb create mode 100644 Data/Scripts/011_Battle/001_Battler/010_Battler_UseMove_TriggerEffects.rb create mode 100644 Data/Scripts/011_Battle/001_PBEnvironment.rb create mode 100644 Data/Scripts/011_Battle/002_Move/001_PokeBattle_Move.rb create mode 100644 Data/Scripts/011_Battle/002_Move/002_Move_Usage.rb create mode 100644 Data/Scripts/011_Battle/002_Move/003_Move_Usage_Calculations.rb create mode 100644 Data/Scripts/011_Battle/002_Move/004_Move_Effects_Generic.rb create mode 100644 Data/Scripts/011_Battle/002_Move/005_Move_Effects_000-07F.rb create mode 100644 Data/Scripts/011_Battle/002_Move/006_Move_Effects_080-0FF.rb create mode 100644 Data/Scripts/011_Battle/002_Move/007_Move_Effects_100-17F.rb create mode 100644 Data/Scripts/011_Battle/002_PBWeather.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/001_PokeBattle_BattleCommon.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/002_PokeBattle_Battle.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/003_Battle_StartAndEnd.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/004_Battle_ExpAndMoveLearning.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/005_Battle_Action_AttacksPriority.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/006_Battle_Action_Switching.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/007_Battle_Action_UseItem.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/008_Battle_Action_Running.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/009_Battle_Action_Other.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/010_Battle_Phase_Command.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/011_Battle_Phase_Attack.rb create mode 100644 Data/Scripts/011_Battle/003_Battle/012_Battle_Phase_EndOfRound.rb create mode 100644 Data/Scripts/011_Battle/003_PBBattleTerrains.rb create mode 100644 Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb create mode 100644 Data/Scripts/011_Battle/004_AI/002_AI_Item.rb create mode 100644 Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb create mode 100644 Data/Scripts/011_Battle/004_AI/004_AI_Move.rb create mode 100644 Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb create mode 100644 Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb create mode 100644 Data/Scripts/011_Battle/004_PBTargets.rb create mode 100644 Data/Scripts/011_Battle/005_Battle scene/001_PokeBattle_Animation.rb create mode 100644 Data/Scripts/011_Battle/005_Battle scene/002_PokeBattle_SceneAnimations.rb create mode 100644 Data/Scripts/011_Battle/005_Battle scene/003_PokeBattle_SceneConstants.rb create mode 100644 Data/Scripts/011_Battle/005_Battle scene/004_PokeBattle_SceneElements.rb create mode 100644 Data/Scripts/011_Battle/005_Battle scene/005_PokeBattle_SceneMenus.rb create mode 100644 Data/Scripts/011_Battle/005_Battle scene/006_PokeBattle_Scene.rb create mode 100644 Data/Scripts/011_Battle/005_Battle scene/007_Scene_Initialize.rb create mode 100644 Data/Scripts/011_Battle/005_Battle scene/008_Scene_Commands.rb create mode 100644 Data/Scripts/011_Battle/005_Battle scene/009_Scene_Animations.rb create mode 100644 Data/Scripts/011_Battle/005_PBEffects.rb create mode 100644 Data/Scripts/011_Battle/006_BattleHandlers.rb create mode 100644 Data/Scripts/011_Battle/006_Other battle types/001_PokeBattle_AnimationPlayer.rb create mode 100644 Data/Scripts/011_Battle/006_Other battle types/002_PokeBattle_SafariZone.rb create mode 100644 Data/Scripts/011_Battle/006_Other battle types/003_PokeBattle_BugContest.rb create mode 100644 Data/Scripts/011_Battle/006_Other battle types/004_PokeBattle_BattlePalace.rb create mode 100644 Data/Scripts/011_Battle/006_Other battle types/005_PokeBattle_BattleArena.rb create mode 100644 Data/Scripts/011_Battle/006_Other battle types/006_PokeBattle_BattleRecord.rb create mode 100644 Data/Scripts/011_Battle/006_Other battle types/007_PokeBattle_DebugScene.rb create mode 100644 Data/Scripts/011_Battle/006_Other battle types/008_PokeBattle_BattlePeer.rb create mode 100644 Data/Scripts/011_Battle/006_Other battle types/009_PokeBattle_Clauses.rb create mode 100644 Data/Scripts/011_Battle/007_BattleHandlers_Abilities.rb create mode 100644 Data/Scripts/011_Battle/008_BattleHandlers_Items.rb create mode 100644 Data/Scripts/011_Battle/009_PokeBall_CatchEffects.rb create mode 100644 Data/Scripts/011_Battle/010_PokeBattle_ActiveField.rb create mode 100644 Data/Scripts/011_Battle/011_PokeBattle_DamageState.rb create mode 100644 Data/Scripts/012_Overworld/001_PBTerrain.rb create mode 100644 Data/Scripts/012_Overworld/002_PField_Field.rb create mode 100644 Data/Scripts/012_Overworld/003_PField_Visuals.rb create mode 100644 Data/Scripts/012_Overworld/004_PField_Weather.rb create mode 100644 Data/Scripts/012_Overworld/005_PField_Metadata.rb create mode 100644 Data/Scripts/012_Overworld/006_PField_Battles.rb create mode 100644 Data/Scripts/012_Overworld/007_PField_Encounters.rb create mode 100644 Data/Scripts/012_Overworld/008_PField_EncounterModifiers.rb create mode 100644 Data/Scripts/012_Overworld/009_PField_RoamingPokemon.rb create mode 100644 Data/Scripts/012_Overworld/010_PField_RandomDungeons.rb create mode 100644 Data/Scripts/012_Overworld/011_PField_FieldMoves.rb create mode 100644 Data/Scripts/012_Overworld/012_PField_BerryPlants.rb create mode 100644 Data/Scripts/012_Overworld/013_PField_DayCare.rb create mode 100644 Data/Scripts/012_Overworld/014_PField_DependentEvents.rb create mode 100644 Data/Scripts/012_Overworld/015_PField_Time.rb create mode 100644 Data/Scripts/013_Trainers/001_PokeBattle_Trainer.rb create mode 100644 Data/Scripts/013_Trainers/002_PTrainer_NPCTrainers.rb create mode 100644 Data/Scripts/014_Items/001_PItem_Items.rb create mode 100644 Data/Scripts/014_Items/002_PItem_ItemEffects.rb create mode 100644 Data/Scripts/014_Items/003_PItem_BattleItemEffects.rb create mode 100644 Data/Scripts/014_Items/004_PItem_Phone.rb create mode 100644 Data/Scripts/014_Items/005_PItem_PokeRadar.rb create mode 100644 Data/Scripts/014_Items/006_PItem_Mail.rb create mode 100644 Data/Scripts/014_Items/007_PItem_Sprites.rb create mode 100644 Data/Scripts/014_Items/008_PItem_Bag.rb create mode 100644 Data/Scripts/015_Pokemon/001_PokeBattle_Pokemon.rb create mode 100644 Data/Scripts/015_Pokemon/002_Pokemon_Forms.rb create mode 100644 Data/Scripts/015_Pokemon/003_Pokemon_MegaEvolution.rb create mode 100644 Data/Scripts/015_Pokemon/004_Pokemon_ShadowPokemon.rb create mode 100644 Data/Scripts/015_Pokemon/005_Pokemon_Evolution.rb create mode 100644 Data/Scripts/015_Pokemon/006_Pokemon_Chatter.rb create mode 100644 Data/Scripts/015_Pokemon/007_Pokemon_Sprites.rb create mode 100644 Data/Scripts/015_Pokemon/008_Pokemon_Storage.rb create mode 100644 Data/Scripts/016_UI/001_PScreen_PauseMenu.rb create mode 100644 Data/Scripts/016_UI/002_PScreen_PokedexMenu.rb create mode 100644 Data/Scripts/016_UI/003_PScreen_PokedexMain.rb create mode 100644 Data/Scripts/016_UI/004_PScreen_PokedexEntry.rb create mode 100644 Data/Scripts/016_UI/005_PScreen_Party.rb create mode 100644 Data/Scripts/016_UI/006_PScreen_Summary.rb create mode 100644 Data/Scripts/016_UI/007_PScreen_Bag.rb create mode 100644 Data/Scripts/016_UI/008_PScreen_Pokegear.rb create mode 100644 Data/Scripts/016_UI/009_PScreen_RegionMap.rb create mode 100644 Data/Scripts/016_UI/010_PScreen_Phone.rb create mode 100644 Data/Scripts/016_UI/011_PScreen_Jukebox.rb create mode 100644 Data/Scripts/016_UI/012_PScreen_TrainerCard.rb create mode 100644 Data/Scripts/016_UI/013_PScreen_Load.rb create mode 100644 Data/Scripts/016_UI/014_PScreen_Save.rb create mode 100644 Data/Scripts/016_UI/015_PScreen_Options.rb create mode 100644 Data/Scripts/016_UI/016_PScreen_ReadyMenu.rb create mode 100644 Data/Scripts/016_UI/017_PScreen_PokemonStorage.rb create mode 100644 Data/Scripts/016_UI/018_PScreen_ItemStorage.rb create mode 100644 Data/Scripts/016_UI/019_PScreen_PC.rb create mode 100644 Data/Scripts/016_UI/020_PScreen_EggHatching.rb create mode 100644 Data/Scripts/016_UI/021_PScreen_Evolution.rb create mode 100644 Data/Scripts/016_UI/022_PScreen_Trading.rb create mode 100644 Data/Scripts/016_UI/023_PScreen_MoveRelearner.rb create mode 100644 Data/Scripts/016_UI/024_PScreen_PurifyChamber.rb create mode 100644 Data/Scripts/016_UI/025_PScreen_Mart.rb create mode 100644 Data/Scripts/016_UI/026_PScreen_MysteryGift.rb create mode 100644 Data/Scripts/016_UI/027_PScreen_HallOfFame.rb create mode 100644 Data/Scripts/017_Other battles/001_PBattle_Safari.rb create mode 100644 Data/Scripts/017_Other battles/002_PBattle_BugContest.rb create mode 100644 Data/Scripts/017_Other battles/003_PBattle_OrgBattle.rb create mode 100644 Data/Scripts/017_Other battles/004_PBattle_OrgBattleRules.rb create mode 100644 Data/Scripts/017_Other battles/005_PBattle_OrgBattleGenerator.rb create mode 100644 Data/Scripts/017_Other battles/006_PBattle_BattleSwap.rb create mode 100644 Data/Scripts/018_Minigames/001_PMinigame_Duel.rb create mode 100644 Data/Scripts/018_Minigames/002_PMinigame_TripleTriad.rb create mode 100644 Data/Scripts/018_Minigames/003_PMinigame_SlotMachine.rb create mode 100644 Data/Scripts/018_Minigames/004_PMinigame_VoltorbFlip.rb create mode 100644 Data/Scripts/018_Minigames/005_PMinigame_Lottery.rb create mode 100644 Data/Scripts/018_Minigames/006_PMinigame_Mining.rb create mode 100644 Data/Scripts/018_Minigames/007_PMinigame_TilePuzzles.rb create mode 100644 Data/Scripts/019_System and utilities/001_PSystem_Controls.rb create mode 100644 Data/Scripts/019_System and utilities/002_PSystem_System.rb create mode 100644 Data/Scripts/019_System and utilities/003_PSystem_FileUtilities.rb create mode 100644 Data/Scripts/019_System and utilities/004_PSystem_PokemonUtilities.rb create mode 100644 Data/Scripts/019_System and utilities/005_PSystem_Utilities.rb create mode 100644 Data/Scripts/020_Debug/001_Debug_Menu.rb create mode 100644 Data/Scripts/020_Debug/002_Debug_Actions.rb create mode 100644 Data/Scripts/020_Debug/003_Debug_Pokemon.rb create mode 100644 Data/Scripts/020_Debug/004_Editor_Screens.rb create mode 100644 Data/Scripts/020_Debug/005_Editor_SaveData.rb create mode 100644 Data/Scripts/020_Debug/006_Editor_DataTypes.rb create mode 100644 Data/Scripts/020_Debug/007_Editor_Listers.rb create mode 100644 Data/Scripts/020_Debug/008_Editor_Utilities.rb create mode 100644 Data/Scripts/020_Debug/009_Editor_TilesetEditor.rb create mode 100644 Data/Scripts/020_Debug/010_Editor_MapConnectionEditor.rb create mode 100644 Data/Scripts/020_Debug/011_Editor_SpritePosEditor.rb create mode 100644 Data/Scripts/020_Debug/012_Editor_BattleAnimationEditor.rb create mode 100644 Data/Scripts/021_Compiler/001_Compiler.rb create mode 100644 Data/Scripts/021_Compiler/002_Compiler_PBS.rb create mode 100644 Data/Scripts/021_Compiler/003_Compiler_MapsAndEvents.rb create mode 100644 Data/Scripts/999_Main/999_Main.rb create mode 100644 Essentials Docs Wiki.URL create mode 100644 Fonts/pkmndp.ttf create mode 100644 Fonts/pkmndpb.ttf create mode 100644 Fonts/pkmnem.ttf create mode 100644 Fonts/pkmnemn.ttf create mode 100644 Fonts/pkmnems.ttf create mode 100644 Fonts/pkmnfl.ttf create mode 100644 Fonts/pkmnrs.ttf create mode 100644 Fonts/pkmnrsi.ttf create mode 100644 PBS/Gen 5/abilities.txt create mode 100644 PBS/Gen 5/items.txt create mode 100644 PBS/Gen 5/moves.txt create mode 100644 PBS/Gen 5/pokemon.txt create mode 100644 PBS/Gen 5/pokemonforms.txt create mode 100644 PBS/Gen 5/tm.txt create mode 100644 PBS/Gen 5/types.txt create mode 100644 PBS/Gen 7/abilities.txt create mode 100644 PBS/Gen 7/items.txt create mode 100644 PBS/Gen 7/moves.txt create mode 100644 PBS/Gen 7/pokemon.txt create mode 100644 PBS/Gen 7/pokemonforms.txt create mode 100644 PBS/Gen 7/tm.txt create mode 100644 PBS/Gen 7/types.txt create mode 100644 PBS/abilities.txt create mode 100644 PBS/berryplants.txt create mode 100644 PBS/btpokemon.txt create mode 100644 PBS/bttrainers.txt create mode 100644 PBS/connections.txt create mode 100644 PBS/encounters.txt create mode 100644 PBS/fancycuppm.txt create mode 100644 PBS/fancycupsinglepm.txt create mode 100644 PBS/fancycupsingletr.txt create mode 100644 PBS/fancycuptr.txt create mode 100644 PBS/items.txt create mode 100644 PBS/littlecuppm.txt create mode 100644 PBS/littlecuptr.txt create mode 100644 PBS/metadata.txt create mode 100644 PBS/moves.txt create mode 100644 PBS/phone.txt create mode 100644 PBS/pikacuppm.txt create mode 100644 PBS/pikacuptr.txt create mode 100644 PBS/pokecuppm.txt create mode 100644 PBS/pokecuptr.txt create mode 100644 PBS/pokemon.txt create mode 100644 PBS/pokemonforms.txt create mode 100644 PBS/shadowmoves.txt create mode 100644 PBS/tm.txt create mode 100644 PBS/townmap.txt create mode 100644 PBS/trainerlists.txt create mode 100644 PBS/trainers.txt create mode 100644 PBS/trainertypes.txt create mode 100644 PBS/types.txt create mode 100644 PBS/~$okemon.txt create mode 100644 README.md create mode 100644 animmaker.exe create mode 100644 animmaker.txt create mode 100644 extendtext.exe create mode 100644 extendtext.txt create mode 100644 gif.dll create mode 100644 knownpoint.bmp create mode 100644 rubyscreen.dll create mode 100644 script_dumper.rb create mode 100644 selpoint.bmp create mode 100644 townmapgen.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..5bc2d02ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Audio and Graphics folders +Audio/ +Graphics/ + +# Data folder, but not Data/Scripts folder +Data/* +!Data/Scripts.rxdata +!Data/Scripts/ + +# Files in the main project folder +Game.exe +Game.ini +Game.rxproj +RGSS*.dll diff --git a/Data/Scripts.rxdata b/Data/Scripts.rxdata new file mode 100644 index 0000000000000000000000000000000000000000..b6ed00a3ff9302c6c1652b05b14ee990af0dbf5d GIT binary patch literal 359 zcmV-t0hs;-2wMhQ2x$a3-?9TD2~A;XZXyCv0eGB!Qp--mFc7>~>OX7(Qe#oMcciM9 z!Vka=p~$t@jn&kS)-I(Y{5#I8At1O|_Rj9i>?AkvTo@0^p$F>%oE;s&Xb1(uUK|l3 zh*r|23aFC#OreI-36*oqL+0_u9W^F`N^Jw^qt4518;E}NDG?jT*I@zjjLW)S7o z$rqVsaNqkH3Jnulb@j`=qmYcx#@)o1bPD4Nt+cDq{t#V4N-2YnW&kvg+bQ_$CU>|u zw!22nH&JE{7wam7UY-!1+CU$O91Wc$jGqA9;yJrb_z}Gq`zgUNG{W4UcFDK)B?J-@ zcS0U=!YXddCQA7T<7lIgXh^CLb#Dx)njJx-@*sz5qj8 F(Z9!Ts%!uN literal 0 HcmV?d00001 diff --git a/Data/Scripts/001_Settings.rb b/Data/Scripts/001_Settings.rb new file mode 100644 index 000000000..60634a782 --- /dev/null +++ b/Data/Scripts/001_Settings.rb @@ -0,0 +1,353 @@ +#==============================================================================# +# Pokémon Essentials # +# Version 18 # +#==============================================================================# + +#=============================================================================== +# * The default screen width (at a zoom of 1.0; size is half this at zoom 0.5). +# * The default screen height (at a zoom of 1.0). +# * The default screen zoom. (1.0 means each tile is 32x32 pixels, 0.5 means +# each tile is 16x16 pixels, 2.0 means each tile is 64x64 pixels.) +# * Whether full-screen display lets the border graphic go outside the edges of +# the screen (true), or forces the border graphic to always be fully shown +# (false). +# * The width of each of the left and right sides of the screen border. This is +# added on to the screen width above, only if the border is turned on. +# * The height of each of the top and bottom sides of the screen border. This is +# added on to the screen height above, only if the border is turned on. +# * Map view mode (0=original, 1=custom, 2=perspective). +#=============================================================================== +SCREEN_WIDTH = 512 +SCREEN_HEIGHT = 384 +SCREEN_ZOOM = 1.0 +BORDER_FULLY_SHOWS = false +BORDER_WIDTH = 78 +BORDER_HEIGHT = 78 +MAP_VIEW_MODE = 1 +# To forbid the player from changing the screen size themselves, quote out or +# delete the relevant bit of code in the PScreen_Options script section. + +#=============================================================================== +# * The maximum level Pokémon can reach. +# * The level of newly hatched Pokémon. +# * The odds of a newly generated Pokémon being shiny (out of 65536). +# * The odds of a wild Pokémon/bred egg having Pokérus (out of 65536). +#=============================================================================== +MAXIMUM_LEVEL = 100 +EGG_LEVEL = 1 +SHINY_POKEMON_CHANCE = 8 +POKERUS_CHANCE = 3 + +#=============================================================================== +# * Whether outdoor maps should be shaded according to the time of day. +#=============================================================================== +TIME_SHADING = true + +#=============================================================================== +# * Whether poisoned Pokémon will lose HP while walking around in the field. +# * Whether poisoned Pokémon will faint while walking around in the field +# (true), or survive the poisoning with 1HP (false). +# * Whether fishing automatically hooks the Pokémon (if false, there is a +# reaction test first). +# * Whether the player can surface from anywhere while diving (true), or only in +# spots where they could dive down from above (false). +# * Whether planted berries grow according to Gen 4 mechanics (true) or Gen 3 +# mechanics (false). +# * Whether TMs can be used infinitely as in Gen 5 (true), or are one-use-only +# as in older Gens (false). +#=============================================================================== +POISON_IN_FIELD = true +POISON_FAINT_IN_FIELD = false +FISHING_AUTO_HOOK = false +DIVING_SURFACE_ANYWHERE = false +NEW_BERRY_PLANTS = true +INFINITE_TMS = true + +#=============================================================================== +# * The number of steps allowed before a Safari Zone game is over (0=infinite). +# * The number of seconds a Bug Catching Contest lasts for (0=infinite). +#=============================================================================== +SAFARI_STEPS = 600 +BUG_CONTEST_TIME = 1200 + +#=============================================================================== +# * Pairs of map IDs, where the location signpost isn't shown when moving from +# one of the maps in a pair to the other (and vice versa). Useful for +# single long routes/towns that are spread over multiple maps. +# e.g. [4,5,16,17,42,43] will be map pairs 4,5 and 16,17 and 42,43. +# Moving between two maps that have the exact same name won't show the +# location signpost anyway, so you don't need to list those maps here. +#=============================================================================== +NO_SIGNPOSTS = [] + +#=============================================================================== +# * The amount of money the player starts the game with. +# * The maximum amount of money the player can have. +# * The maximum number of Game Corner coins the player can have. +# * The maximum length, in characters, that the player's name can be. +#=============================================================================== +INITIAL_MONEY = 3000 +MAX_MONEY = 999999 +MAX_COINS = 99999 +MAX_PLAYER_NAME_SIZE = 10 + +#=============================================================================== +# * A set of arrays each containing a trainer type followed by a Global Variable +# number. If the variable isn't set to 0, then all trainers with the +# associated trainer type will be named as whatever is in that variable. +#=============================================================================== +RIVAL_NAMES = [ + [:RIVAL1,12], + [:RIVAL2,12], + [:CHAMPION,12] +] + +#=============================================================================== +# * The minimum number of badges required to boost each stat of a player's +# Pokémon by 1.1x, while using moves in battle only. +# * Whether the badge restriction on using certain hidden moves is either owning +# at least a certain number of badges (true), or owning a particular badge +# (false). +# * Depending on FIELD_MOVES_COUNT_BADGES, either the number of badges required +# to use each hidden move, or the specific badge number required to use +# each move. Remember that badge 0 is the first badge, badge 1 is the +# second badge, etc. +# e.g. To require the second badge, put false and 1. +# To require at least 2 badges, put true and 2. +#=============================================================================== +NUM_BADGES_BOOST_ATTACK = 1 +NUM_BADGES_BOOST_DEFENSE = 5 +NUM_BADGES_BOOST_SPATK = 7 +NUM_BADGES_BOOST_SPDEF = 7 +NUM_BADGES_BOOST_SPEED = 3 +FIELD_MOVES_COUNT_BADGES = true +BADGE_FOR_CUT = 1 +BADGE_FOR_FLASH = 2 +BADGE_FOR_ROCKSMASH = 3 +BADGE_FOR_SURF = 4 +BADGE_FOR_FLY = 5 +BADGE_FOR_STRENGTH = 6 +BADGE_FOR_DIVE = 7 +BADGE_FOR_WATERFALL = 8 + +#=============================================================================== +# * Whether a move's physical/special category depends on the move itself as in +# newer Gens (true), or on its type as in older Gens (false). +# * Whether the battle mechanics mimic Gen 5 (false) or Gen 7 (true). +# * Whether the Exp gained from beating a Pokémon should be scaled depending on +# the gainer's level as in Gens 5/7 (true) or not as in other Gens (false). +# * Whether the Exp gained from beating a Pokémon should be divided equally +# between each participant (true), or whether each participant should gain +# that much Exp (false). This also applies to Exp gained via the Exp Share +# (held item version) being distributed to all Exp Share holders. This is +# true in Gen 6 and false otherwise. +# * Whether the critical capture mechanic applies (true) or not (false). Note +# that it is based on a total of 600+ species (i.e. that many species need +# to be caught to provide the greatest critical capture chance of 2.5x), +# and there may be fewer species in your game. +# * Whether Pokémon gain Exp for capturing a Pokémon (true) or not (false). +# * An array of items which act as Mega Rings for the player (NPCs don't need a +# Mega Ring item, just a Mega Stone held by their Pokémon). +#=============================================================================== +MOVE_CATEGORY_PER_MOVE = true +NEWEST_BATTLE_MECHANICS = true +SCALED_EXP_FORMULA = true +SPLIT_EXP_BETWEEN_GAINERS = false +ENABLE_CRITICAL_CAPTURES = false +GAIN_EXP_FOR_CAPTURE = true +MEGA_RINGS = [:MEGARING,:MEGABRACELET,:MEGACUFF,:MEGACHARM] + +#=============================================================================== +# * The names of each pocket of the Bag. Leave the first entry blank. +# * The maximum number of slots per pocket (-1 means infinite number). Ignore +# the first number (0). +# * The maximum number of items each slot in the Bag can hold. +# * Whether each pocket in turn auto-sorts itself by item ID number. Ignore the +# first entry (the 0). +#=============================================================================== +def pbPocketNames; return ["", + _INTL("Items"), + _INTL("Medicine"), + _INTL("Poké Balls"), + _INTL("TMs & HMs"), + _INTL("Berries"), + _INTL("Mail"), + _INTL("Battle Items"), + _INTL("Key Items") +]; end +BAG_MAX_POCKET_SIZE = [0,-1,-1,-1,-1,-1,-1,-1,-1] +BAG_MAX_PER_SLOT = 999 +BAG_POCKET_AUTO_SORT = [0,false,false,false,true,true,false,false,false] + +#=============================================================================== +# * A set of arrays each containing details of a graphic to be shown on the +# region map if appropriate. The values for each array are as follows: +# - Region number. +# - Global Switch; the graphic is shown if this is ON (non-wall maps only). +# - X coordinate of the graphic on the map, in squares. +# - Y coordinate of the graphic on the map, in squares. +# - Name of the graphic, found in the Graphics/Pictures folder. +# - The graphic will always (true) or never (false) be shown on a wall map. +#=============================================================================== +REGION_MAP_EXTRAS = [ + [0,51,16,15,"mapHiddenBerth",false], + [0,52,20,14,"mapHiddenFaraday",false] +] + +#=============================================================================== +# * The name of the person who created the Pokémon storage system. +# * The number of boxes in Pokémon storage. +#=============================================================================== +def pbStorageCreator + return _INTL("Bill") +end +NUM_STORAGE_BOXES = 30 + +#=============================================================================== +# * Whether the Pokédex list shown is the one for the player's current region +# (true), or whether a menu pops up for the player to manually choose which +# Dex list to view if more than one is available (false). +# * The names of each Dex list in the game, in order and with National Dex at +# the end. This is also the order that $PokemonGlobal.pokedexUnlocked is +# in, which records which Dexes have been unlocked (first is unlocked by +# default). +# You can define which region a particular Dex list is linked to. This +# means the area map shown while viewing that Dex list will ALWAYS be that +# of the defined region, rather than whichever region the player is +# currently in. To define this, put the Dex name and the region number in +# an array, like the Kanto and Johto Dexes are. The National Dex isn't in +# an array with a region number, therefore its area map is whichever region +# the player is currently in. +# * Whether all forms of a given species will be immediately available to view +# in the Pokédex so long as that species has been seen at all (true), or +# whether each form needs to be seen specifically before that form appears +# in the Pokédex (false). +# * An array of numbers, where each number is that of a Dex list (National Dex +# is -1). All Dex lists included here have the species numbers in them +# reduced by 1, thus making the first listed species have a species number +# of 0 (e.g. Victini in Unova's Dex). +#=============================================================================== +USE_CURRENT_REGION_DEX = false +def pbDexNames; return [ + [_INTL("Kanto Pokédex"),0], + [_INTL("Johto Pokédex"),1], + _INTL("National Pokédex") +]; end +DEX_SHOWS_ALL_FORMS = false +DEXES_WITH_OFFSETS = [] + +#=============================================================================== +# * A list of maps used by roaming Pokémon. Each map has an array of other maps +# it can lead to. +# * A set of arrays each containing the details of a roaming Pokémon. The +# information within is as follows: +# - Species. +# - Level. +# - Global Switch; the Pokémon roams while this is ON. +# - Encounter type (0=any, 1=grass/walking in cave, 2=surfing, 3=fishing, +# 4=surfing/fishing). See bottom of PField_RoamingPokemon for lists. +# - Name of BGM to play for that encounter (optional). +# - Roaming areas specifically for this Pokémon (optional). +#=============================================================================== +RoamingAreas = { + 5 => [21,28,31,39,41,44,47,66,69], + 21 => [5,28,31,39,41,44,47,66,69], + 28 => [5,21,31,39,41,44,47,66,69], + 31 => [5,21,28,39,41,44,47,66,69], + 39 => [5,21,28,31,41,44,47,66,69], + 41 => [5,21,28,31,39,44,47,66,69], + 44 => [5,21,28,31,39,41,47,66,69], + 47 => [5,21,28,31,39,41,44,66,69], + 66 => [5,21,28,31,39,41,44,47,69], + 69 => [5,21,28,31,39,41,44,47,66] +} +RoamingSpecies = [ + [:LATIAS, 30, 53, 0, "Battle roaming"], + [:LATIOS, 30, 53, 0, "Battle roaming"], + [:KYOGRE, 40, 54, 2, nil, { + 2 => [21,31], + 21 => [2,31,69], + 31 => [2,21,69], + 69 => [21,31] + }], + [:ENTEI, 40, 55, 1, nil] +] + +#=============================================================================== +# * A set of arrays each containing details of a wild encounter that can only +# occur via using the Poké Radar. The information within is as follows: +# - Map ID on which this encounter can occur. +# - Probability that this encounter will occur (as a percentage). +# - Species. +# - Minimum possible level. +# - Maximum possible level (optional). +#=============================================================================== +POKE_RADAR_ENCOUNTERS = [ + [5, 20, :STARLY, 12, 15], + [21, 10, :STANTLER, 14], + [28, 20, :BUTTERFREE, 15, 18], + [28, 20, :BEEDRILL, 15, 18] +] + +#=============================================================================== +# * The Global Switch that is set to ON when the player whites out. +# * The Global Switch that is set to ON when the player has seen Pokérus in the +# Poké Center, and doesn't need to be told about it again. +# * The Global Switch which, while ON, makes all wild Pokémon created be +# shiny. +# * The Global Switch which, while ON, makes all Pokémon created considered to +# be met via a fateful encounter. +# * The Global Switch which determines whether the player will lose money if +# they lose a battle (they can still gain money from trainers for winning). +# * The Global Switch which, while ON, prevents all Pokémon in battle from Mega +# Evolving even if they otherwise could. +#=============================================================================== +STARTING_OVER_SWITCH = 1 +SEEN_POKERUS_SWITCH = 2 +SHINY_WILD_POKEMON_SWITCH = 31 +FATEFUL_ENCOUNTER_SWITCH = 32 +NO_MONEY_LOSS = 33 +NO_MEGA_EVOLUTION = 34 + +#=============================================================================== +# * The ID of the common event that runs when the player starts fishing (runs +# instead of showing the casting animation). +# * The ID of the common event that runs when the player stops fishing (runs +# instead of showing the reeling in animation). +#=============================================================================== +FISHING_BEGIN_COMMON_EVENT = -1 +FISHING_END_COMMON_EVENT = -1 + +#=============================================================================== +# * The ID of the animation played when the player steps on grass (shows grass +# rustling). +# * The ID of the animation played when the player lands on the ground after +# hopping over a ledge (shows a dust impact). +# * The ID of the animation played when a trainer notices the player (an +# exclamation bubble). +# * The ID of the animation played when a patch of grass rustles due to using +# the Poké Radar. +# * The ID of the animation played when a patch of grass rustles vigorously due +# to using the Poké Radar. (Rarer species) +# * The ID of the animation played when a patch of grass rustles and shines due +# to using the Poké Radar. (Shiny encounter) +# * The ID of the animation played when a berry tree grows a stage while the +# player is on the map (for new plant growth mechanics only). +#=============================================================================== +GRASS_ANIMATION_ID = 1 +DUST_ANIMATION_ID = 2 +EXCLAMATION_ANIMATION_ID = 3 +RUSTLE_NORMAL_ANIMATION_ID = 1 +RUSTLE_VIGOROUS_ANIMATION_ID = 5 +RUSTLE_SHINY_ANIMATION_ID = 6 +PLANT_SPARKLE_ANIMATION_ID = 7 + +#=============================================================================== +# * An array of available languages in the game, and their corresponding +# message file in the Data folder. Edit only if you have 2 or more +# languages to choose from. +#=============================================================================== +LANGUAGES = [ +# ["English","english.dat"], +# ["Deutsch","deutsch.dat"] +] \ No newline at end of file diff --git a/Data/Scripts/001_Technical/001_Ruby Utilities.rb b/Data/Scripts/001_Technical/001_Ruby Utilities.rb new file mode 100644 index 000000000..64142d1fc --- /dev/null +++ b/Data/Scripts/001_Technical/001_Ruby Utilities.rb @@ -0,0 +1,211 @@ +#=============================================================================== +# class Class +#=============================================================================== +class Class + def to_sym + return self.to_s.to_sym + end +end + + + +#=============================================================================== +# module Comparable +#=============================================================================== +unless Comparable.method_defined? :clamp + module Comparable + def clamp(min, max) + if max-min<0 + raise ArgumentError("min argument must be smaller than max argument") + end + return (self>max) ? max : (self= str.length + return proc ? proc : false + end + + def ends_with?(str) + e = self.length - 1 + proc = (self[(e-str.length)...e] == str) if self.length >= str.length + return proc ? proc : false + end + + def starts_with_vowel? + return ['a','e','i','o','u'].include?(self[0,1].downcase) + end + + def first(n=1) + return self[0...n] + end + + def last(n=1) + return self[-n..-1] || self + end + + def bytesize + return self.size + end + + def capitalize + proc = self.scan(/./) + proc[0] = proc[0].upcase + string = "" + for letter in proc + string += letter + end + return string + end + + def capitalize! + self.replace(self.capitalize) + end + + def blank? + blank = true + s = self.scan(/./) + for l in s + blank = false if l != "" + end + return blank + end + + def cut(bitmap,width) + string = self + width -= bitmap.text_size("...").width + string_width = 0 + text = [] + for char in string.scan(/./) + wdh = bitmap.text_size(char).width + next if (wdh+string_width) > width + string_width += wdh + text.push(char) + end + text.push("...") if text.length < string.length + new_string = "" + for char in text + new_string += char + end + return new_string + end +end + + + +#=============================================================================== +# class Numeric +#=============================================================================== +class Numeric + # Turns a number into a string formatted like 12,345,678. + def to_s_formatted + return self.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1,').reverse + end +end + + + +#=============================================================================== +# class Integer +#=============================================================================== +class Integer + # Returns an array containing each digit of the number in turn. + def digits(base=10) + quotient, remainder = divmod(base) + (quotient==0) ? [remainder] : quotient.digits(base).push(remainder) + end +end + + + +#=============================================================================== +# class Array +#=============================================================================== +class Array + def first + return self[0] + end + + def last + return self[self.length-1] + end + + def ^(other) # xor of two arrays + return (self|other)-(self&other) + end + + def shuffle + dup.shuffle! + end unless method_defined? :shuffle + + def shuffle! + (size-1).times do |i| + r = i+rand(size-i) + self[i], self[r] = self[r], self[i] + end + self + end unless method_defined? :shuffle! +end + + +#=============================================================================== +# module Enumerable +#=============================================================================== +module Enumerable + def transform + ret = [] + self.each { |item| ret.push(yield(item)) } + return ret + end +end + + + +#=============================================================================== +# Kernel methods +#=============================================================================== +def rand(*args) + Kernel.rand(*args) +end + +class << Kernel + alias oldRand rand unless method_defined?(:oldRand) + def rand(a = nil, b = nil) + if a.is_a?(Range) + lo = a.min + hi = a.max + return lo + oldRand(hi - lo + 1) + elsif a.is_a?(Numeric) + if b.is_a?(Numeric) + return a + oldRand(b - a + 1) + else + return oldRand(a) + end + elsif a.nil? + return (b) ? oldRand(b) : oldRand(2) + end + end +end + +def nil_or_empty?(string) + return string.nil? || !string.is_a?(String) || string.size == 0 +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/002_RGSS2Compatibility.rb b/Data/Scripts/001_Technical/002_RGSS2Compatibility.rb new file mode 100644 index 000000000..e1fdcc4c2 --- /dev/null +++ b/Data/Scripts/001_Technical/002_RGSS2Compatibility.rb @@ -0,0 +1,608 @@ +$TEST = true if $DEBUG +$DEBUG = true if $TEST +$scene = nil +Font.default_shadow = false if Font.respond_to?(:default_shadow) +Graphics.frame_rate = 40 + + + +=begin +class Win32API + class << self + unless defined?(debug_new) + alias debug_new new + end + + def new(*args) + File.open("winapi.txt","ab") { |f| f.write("new(#{args[0]},#{args[1]})\r\n") } + b=debug_new(*args) + b.setDllName(args[0],args[1]) + return b + end + end + + unless defined?(debug_call) + alias debug_call call + end + + def setDllName(a,b) + @w32dll=a + @w32name=b + end + + def call(*args) + if @w32name!="GetAsyncKeyState" + File.open("winapi.txt","ab") { |f| + f.write("call(#{@w32dll},#{@w32name},#{args.inspect})\r\n") + } + end + debug_call(*args) + end +end + +class Bitmap + class << self + unless defined?(debug_new) + alias debug_new new + end + + def new(*args) + if args.length==1 + File.open("winapib.txt","ab") { |f| f.write("new(#{args[0]})\r\n") } + end + debug_new(*args) + end + end +end + +alias debug_load_data load_data + +def load_data(*args) + File.open("winapif.txt","ab") { |f| f.write("load(#{args[0]})\r\n") } + debug_load_data(*args) +end +=end + + + +class Hangup < Exception; end + + + +if false + p (Tilemap.instance_methods-Kernel.instance_methods-Object.instance_methods).sort +# no changes + p (Plane.instance_methods-Kernel.instance_methods-Object.instance_methods).sort +# no changes + p (Viewport.instance_methods-Kernel.instance_methods-Object.instance_methods).sort + p (Bitmap.instance_methods-Kernel.instance_methods-Object.instance_methods).sort +# openness(=) + p (Window.instance_methods-Kernel.instance_methods-Object.instance_methods).sort + p (Sprite.instance_methods-Kernel.instance_methods-Object.instance_methods).sort +end + + + +module RPG + class Animation + attr_accessor :id + attr_accessor :name + attr_accessor :animation_name + attr_accessor :animation_hue + attr_accessor :position + attr_accessor :frame_max + attr_accessor :frames + attr_accessor :timings + + def initialize + @id = 0 + @name = "" + @animation_name = "" + @animation_hue = 0 + @position = 1 + @frame_max = 1 + @frames = [RPG::Animation::Frame.new] + @timings = [] + end + end +end + + + +module RPG + class Animation + class Frame + attr_accessor :cell_max + attr_accessor :cell_data + + def initialize + @cell_max = 0 + @cell_data = Table.new(0, 0) + end + end + end +end + + + +module RPG + class Animation + class Timing + attr_accessor :frame + attr_accessor :se + attr_accessor :flash_scope + attr_accessor :flash_color + attr_accessor :flash_duration + attr_accessor :condition + + def initialize + @frame = 0 + @se = RPG::AudioFile.new("", 80) + @flash_scope = 0 + @flash_color = Color.new(255,255,255,255) + @flash_duration = 5 + @condition = 0 + end + end + end +end + + + +module RPG + class System + attr_accessor :magic_number + attr_accessor :party_members + attr_accessor :elements + attr_accessor :switches + attr_accessor :variables + attr_accessor :windowskin_name + attr_accessor :title_name + attr_accessor :gameover_name + attr_accessor :battle_transition + attr_accessor :title_bgm + attr_accessor :battle_bgm + attr_accessor :battle_end_me + attr_accessor :gameover_me + attr_accessor :cursor_se + attr_accessor :decision_se + attr_accessor :cancel_se + attr_accessor :buzzer_se + attr_accessor :equip_se + attr_accessor :shop_se + attr_accessor :save_se + attr_accessor :load_se + attr_accessor :battle_start_se + attr_accessor :escape_se + attr_accessor :actor_collapse_se + attr_accessor :enemy_collapse_se + attr_accessor :words + attr_accessor :test_battlers + attr_accessor :test_troop_id + attr_accessor :start_map_id + attr_accessor :start_x + attr_accessor :start_y + attr_accessor :battleback_name + attr_accessor :battler_name + attr_accessor :battler_hue + attr_accessor :edit_map_id + + def initialize + @magic_number = 0 + @party_members = [1] + @elements = [nil, ""] + @switches = [nil, ""] + @variables = [nil, ""] + @windowskin_name = "" + @title_name = "" + @gameover_name = "" + @battle_transition = "" + @title_bgm = RPG::AudioFile.new + @battle_bgm = RPG::AudioFile.new + @battle_end_me = RPG::AudioFile.new + @gameover_me = RPG::AudioFile.new + @cursor_se = RPG::AudioFile.new("", 80) + @decision_se = RPG::AudioFile.new("", 80) + @cancel_se = RPG::AudioFile.new("", 80) + @buzzer_se = RPG::AudioFile.new("", 80) + @equip_se = RPG::AudioFile.new("", 80) + @shop_se = RPG::AudioFile.new("", 80) + @save_se = RPG::AudioFile.new("", 80) + @load_se = RPG::AudioFile.new("", 80) + @battle_start_se = RPG::AudioFile.new("", 80) + @escape_se = RPG::AudioFile.new("", 80) + @actor_collapse_se = RPG::AudioFile.new("", 80) + @enemy_collapse_se = RPG::AudioFile.new("", 80) + @words = RPG::System::Words.new + @test_battlers = [] + @test_troop_id = 1 + @start_map_id = 1 + @start_x = 0 + @start_y = 0 + @battleback_name = "" + @battler_name = "" + @battler_hue = 0 + @edit_map_id = 1 + end + end +end + + + +module RPG + class Tileset + attr_accessor :id + attr_accessor :name + attr_accessor :tileset_name + attr_accessor :autotile_names + attr_accessor :panorama_name + attr_accessor :panorama_hue + attr_accessor :fog_name + attr_accessor :fog_hue + attr_accessor :fog_opacity + attr_accessor :fog_blend_type + attr_accessor :fog_zoom + attr_accessor :fog_sx + attr_accessor :fog_sy + attr_accessor :battleback_name + attr_accessor :passages + attr_accessor :priorities + attr_accessor :terrain_tags + + def initialize + @id = 0 + @name = "" + @tileset_name = "" + @autotile_names = [""]*7 + @panorama_name = "" + @panorama_hue = 0 + @fog_name = "" + @fog_hue = 0 + @fog_opacity = 64 + @fog_blend_type = 0 + @fog_zoom = 200 + @fog_sx = 0 + @fog_sy = 0 + @battleback_name = "" + @passages = Table.new(384) + @priorities = Table.new(384) + @priorities[0] = 5 + @terrain_tags = Table.new(384) + end + end +end + + + +module RPG + class CommonEvent + attr_accessor :id + attr_accessor :name + attr_accessor :trigger + attr_accessor :switch_id + attr_accessor :list + + def initialize + @id = 0 + @name = "" + @trigger = 0 + @switch_id = 1 + @list = [RPG::EventCommand.new] + end + end +end + + + +module RPG + class Map + attr_accessor :tileset_id + attr_accessor :width + attr_accessor :height + attr_accessor :autoplay_bgm + attr_accessor :bgm + attr_accessor :autoplay_bgs + attr_accessor :bgs + attr_accessor :encounter_list + attr_accessor :encounter_step + attr_accessor :data + attr_accessor :events + + def initialize(width, height) + @tileset_id = 1 + @width = width + @height = height + @autoplay_bgm = false + @bgm = RPG::AudioFile.new + @autoplay_bgs = false + @bgs = RPG::AudioFile.new("", 80) + @encounter_list = [] + @encounter_step = 30 + @data = Table.new(width, height, 3) + @events = {} + end + end +end + + + +module RPG + class MapInfo + attr_accessor :name + attr_accessor :parent_id + attr_accessor :order + attr_accessor :expanded + attr_accessor :scroll_x + attr_accessor :scroll_y + + def initialize + @name = "" + @parent_id = 0 + @order = 0 + @expanded = false + @scroll_x = 0 + @scroll_y = 0 + end + end +end + + + +module RPG + class Event + attr_accessor :id + attr_accessor :name + attr_accessor :x + attr_accessor :y + attr_accessor :pages + + def initialize(x, y) + @id = 0 + @name = "" + @x = x + @y = y + @pages = [RPG::Event::Page.new] + end + end +end + + + +module RPG + class Event + class Page + attr_accessor :condition + attr_accessor :graphic + attr_accessor :move_type + attr_accessor :move_speed + attr_accessor :move_frequency + attr_accessor :move_route + attr_accessor :walk_anime + attr_accessor :step_anime + attr_accessor :direction_fix + attr_accessor :through + attr_accessor :always_on_top + attr_accessor :trigger + attr_accessor :list + + def initialize + @condition = RPG::Event::Page::Condition.new + @graphic = RPG::Event::Page::Graphic.new + @move_type = 0 + @move_speed = 3 + @move_frequency = 3 + @move_route = RPG::MoveRoute.new + @walk_anime = true + @step_anime = false + @direction_fix = false + @through = false + @always_on_top = false + @trigger = 0 + @list = [RPG::EventCommand.new] + end + end + end +end + + + +module RPG + class Event + class Page + class Condition + attr_accessor :switch1_valid + attr_accessor :switch2_valid + attr_accessor :variable_valid + attr_accessor :self_switch_valid + attr_accessor :switch1_id + attr_accessor :switch2_id + attr_accessor :variable_id + attr_accessor :variable_value + attr_accessor :self_switch_ch + + def initialize + @switch1_valid = false + @switch2_valid = false + @variable_valid = false + @self_switch_valid = false + @switch1_id = 1 + @switch2_id = 1 + @variable_id = 1 + @variable_value = 0 + @self_switch_ch = "A" + end + end + end + end +end + + + +module RPG + class Event + class Page + class Graphic + attr_accessor :tile_id + attr_accessor :character_name + attr_accessor :character_hue + attr_accessor :direction + attr_accessor :pattern + attr_accessor :opacity + attr_accessor :blend_type + + def initialize + @tile_id = 0 + @character_name = "" + @character_hue = 0 + @direction = 2 + @pattern = 0 + @opacity = 255 + @blend_type = 0 + end + end + end + end +end + + + +module RPG + class EventCommand + attr_accessor :code + attr_accessor :indent + attr_accessor :parameters + + def initialize(code = 0, indent = 0, parameters = []) + @code = code + @indent = indent + @parameters = parameters + end + end +end + + + +module RPG + class MoveRoute + attr_accessor :repeat + attr_accessor :skippable + attr_accessor :list + + def initialize + @repeat = true + @skippable = false + @list = [RPG::MoveCommand.new] + end + end +end + + + +module RPG + class MoveCommand + attr_accessor :code + attr_accessor :parameters + + def initialize(code = 0, parameters = []) + @code = code + @parameters = parameters + end + end +end + + + +module RPG + class System + class Words + attr_accessor :gold + attr_accessor :hp + attr_accessor :sp + attr_accessor :str + attr_accessor :dex + attr_accessor :agi + attr_accessor :int + attr_accessor :atk + attr_accessor :pdef + attr_accessor :mdef + attr_accessor :weapon + attr_accessor :armor1 + attr_accessor :armor2 + attr_accessor :armor3 + attr_accessor :armor4 + attr_accessor :attack + attr_accessor :skill + attr_accessor :guard + attr_accessor :item + attr_accessor :equip + + def initialize + @gold = "" + @hp = "" + @sp = "" + @str = "" + @dex = "" + @agi = "" + @int = "" + @atk = "" + @pdef = "" + @mdef = "" + @weapon = "" + @armor1 = "" + @armor2 = "" + @armor3 = "" + @armor4 = "" + @attack = "" + @skill = "" + @guard = "" + @item = "" + @equip = "" + end + end + end +end + + + +module RPG + class System + class TestBattler + attr_accessor :actor_id + attr_accessor :level + attr_accessor :weapon_id + attr_accessor :armor1_id + attr_accessor :armor2_id + attr_accessor :armor3_id + attr_accessor :armor4_id + + def initialize + @actor_id = 1 + @level = 1 + @weapon_id = 0 + @armor1_id = 0 + @armor2_id = 0 + @armor3_id = 0 + @armor4_id = 0 + end + end + end +end + + + +module RPG + class AudioFile + attr_accessor :name + attr_accessor :volume + attr_accessor :pitch + + def initialize(name = "", volume = 100, pitch = 100) + @name = name + @volume = volume + @pitch = pitch + end + +# def play +# end + end +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/003_RPG__Sprite.rb b/Data/Scripts/001_Technical/003_RPG__Sprite.rb new file mode 100644 index 000000000..f40b815dd --- /dev/null +++ b/Data/Scripts/001_Technical/003_RPG__Sprite.rb @@ -0,0 +1,534 @@ +class SpriteAnimation + @@_animations = [] + @@_reference_count = {} + + def initialize(sprite) + @sprite = sprite + end + + %w[ + x y ox oy viewport flash src_rect opacity tone + ].each_with_index do |s, i| + eval <<-__END__ + + def #{s}(*arg) + @sprite.#{s}(*arg) + end + + __END__ + end + + def self.clear + @@_animations.clear + end + + def dispose + dispose_animation + dispose_loop_animation + end + + def animation(animation, hit, height = 3) + dispose_animation + @_animation = animation + return if @_animation == nil + @_animation_hit = hit + @_animation_height = height + @_animation_duration = @_animation.frame_max + fr = 20 + if @_animation.name[/\[\s*(\d+?)\s*\]\s*$/] + fr = $~[1].to_i + end + @_animation_frame_skip = Graphics.frame_rate / fr + animation_name = @_animation.animation_name + animation_hue = @_animation.animation_hue + bitmap = pbGetAnimation(animation_name, animation_hue) + if @@_reference_count.include?(bitmap) + @@_reference_count[bitmap] += 1 + else + @@_reference_count[bitmap] = 1 + end + @_animation_sprites = [] + if @_animation.position != 3 or not @@_animations.include?(animation) + for i in 0..15 + sprite = ::Sprite.new(self.viewport) + sprite.bitmap = bitmap + sprite.visible = false + @_animation_sprites.push(sprite) + end + unless @@_animations.include?(animation) + @@_animations.push(animation) + end + end + update_animation + end + + def loop_animation(animation) + return if animation == @_loop_animation + dispose_loop_animation + @_loop_animation = animation + return if @_loop_animation == nil + @_loop_animation_index = 0 + fr = 20 + if @_animation.name[/\[\s*(\d+?)\s*\]\s*$/] + fr = $~[1].to_i + end + @_loop_animation_frame_skip = Graphics.frame_rate / fr + animation_name = @_loop_animation.animation_name + animation_hue = @_loop_animation.animation_hue + bitmap = pbGetAnimation(animation_name, animation_hue) + if @@_reference_count.include?(bitmap) + @@_reference_count[bitmap] += 1 + else + @@_reference_count[bitmap] = 1 + end + @_loop_animation_sprites = [] + for i in 0..15 + sprite = ::Sprite.new(self.viewport) + sprite.bitmap = bitmap + sprite.visible = false + @_loop_animation_sprites.push(sprite) + end + update_loop_animation + end + + def dispose_animation + return if @_animation_sprites == nil + sprite = @_animation_sprites[0] + if sprite != nil + @@_reference_count[sprite.bitmap] -= 1 + if @@_reference_count[sprite.bitmap] == 0 + sprite.bitmap.dispose + end + end + for sprite in @_animation_sprites + sprite.dispose + end + @_animation_sprites = nil + @_animation = nil + end + + def dispose_loop_animation + return if @_loop_animation_sprites == nil + sprite = @_loop_animation_sprites[0] + if sprite != nil + @@_reference_count[sprite.bitmap] -= 1 + if @@_reference_count[sprite.bitmap] == 0 + sprite.bitmap.dispose + end + end + for sprite in @_loop_animation_sprites + sprite.dispose + end + @_loop_animation_sprites = nil + @_loop_animation = nil + end + + def active? + return @_loop_animation_sprites != nil || @_animation_sprites != nil + end + + def effect? + return @_animation_duration > 0 + end + + def update + if @_animation != nil + quick_update = true + if Graphics.frame_count % @_animation_frame_skip == 0 + @_animation_duration -= 1 + quick_update = false + end + update_animation(quick_update) + end + if @_loop_animation != nil + quick_update = (Graphics.frame_count % @_loop_animation_frame_skip != 0) + update_loop_animation(quick_update) + if !quick_update + @_loop_animation_index += 1 + @_loop_animation_index %= @_loop_animation.frame_max + end + end + end + + def update_animation(quick_update = false) + if @_animation_duration <= 0 + dispose_animation + return + end + frame_index = @_animation.frame_max - @_animation_duration + cell_data = @_animation.frames[frame_index].cell_data + position = @_animation.position + animation_set_sprites(@_animation_sprites, cell_data, position, quick_update) + return if quick_update + for timing in @_animation.timings + next if timing.frame != frame_index + animation_process_timing(timing, @_animation_hit) + end + end + + def update_loop_animation(quick_update = false) + frame_index = @_loop_animation_index + cell_data = @_loop_animation.frames[frame_index].cell_data + position = @_loop_animation.position + animation_set_sprites(@_loop_animation_sprites, cell_data, position, quick_update) + return if quick_update + for timing in @_loop_animation.timings + next if timing.frame != frame_index + animation_process_timing(timing, true) + end + end + + def animation_set_sprites(sprites, cell_data, position, quick_update = false) + sprite_x = 320 + sprite_y = 240 + if position == 3 + if self.viewport != nil + sprite_x = self.viewport.rect.width / 2 + sprite_y = self.viewport.rect.height - 160 + end + else + sprite_x = self.x - self.ox + self.src_rect.width / 2 + sprite_y = self.y - self.oy + self.src_rect.height / 2 + sprite_y -= self.src_rect.height / 4 if position == 0 + sprite_y += self.src_rect.height / 4 if position == 2 + end + for i in 0..15 + sprite = sprites[i] + pattern = cell_data[i, 0] + if sprite == nil or pattern == nil or pattern == -1 + sprite.visible = false if sprite != nil + next + end + sprite.x = sprite_x + cell_data[i, 1] + sprite.y = sprite_y + cell_data[i, 2] + next if quick_update + sprite.visible = true + sprite.src_rect.set(pattern % 5 * 192, pattern / 5 * 192, 192, 192) + case @_animation_height + when 0; sprite.z = 1 + when 1; sprite.z = sprite.y+32+15 + when 2; sprite.z = sprite.y+32+32+17 + else; sprite.z = 2000 + end + sprite.ox = 96 + sprite.oy = 96 + sprite.zoom_x = cell_data[i, 3] / 100.0 + sprite.zoom_y = cell_data[i, 3] / 100.0 + sprite.angle = cell_data[i, 4] + sprite.mirror = (cell_data[i, 5] == 1) + sprite.tone = self.tone + sprite.opacity = cell_data[i, 6] * self.opacity / 255.0 + sprite.blend_type = cell_data[i, 7] + end + end + + def animation_process_timing(timing, hit) + if (timing.condition == 0) or + (timing.condition == 1 and hit == true) or + (timing.condition == 2 and hit == false) + if timing.se.name != "" + se = timing.se + pbSEPlay(se) + end + case timing.flash_scope + when 1 + self.flash(timing.flash_color, timing.flash_duration * 2) + when 2 + if self.viewport != nil + self.viewport.flash(timing.flash_color, timing.flash_duration * 2) + end + when 3 + self.flash(nil, timing.flash_duration * 2) + end + end + end + + def x=(x) + sx = x - self.x + return if sx == 0 + if @_animation_sprites != nil + for i in 0..15 + @_animation_sprites[i].x += sx + end + end + if @_loop_animation_sprites != nil + for i in 0..15 + @_loop_animation_sprites[i].x += sx + end + end + end + + def y=(y) + sy = y - self.y + return if sy == 0 + if @_animation_sprites != nil + for i in 0..15 + @_animation_sprites[i].y += sy + end + end + if @_loop_animation_sprites != nil + for i in 0..15 + @_loop_animation_sprites[i].y += sy + end + end + end +end + + + +module RPG + class Sprite < ::Sprite + def initialize(viewport = nil) + super(viewport) + @_whiten_duration = 0 + @_appear_duration = 0 + @_escape_duration = 0 + @_collapse_duration = 0 + @_damage_duration = 0 + @_animation_duration = 0 + @_blink = false + @animations = [] + @loopAnimations = [] + end + + def dispose + dispose_damage + dispose_animation + dispose_loop_animation + super + end + + def whiten + self.blend_type = 0 + self.color.set(255, 255, 255, 128) + self.opacity = 255 + @_whiten_duration = 16 + @_appear_duration = 0 + @_escape_duration = 0 + @_collapse_duration = 0 + end + + def appear + self.blend_type = 0 + self.color.set(0, 0, 0, 0) + self.opacity = 0 + @_appear_duration = 16 + @_whiten_duration = 0 + @_escape_duration = 0 + @_collapse_duration = 0 + end + + def escape + self.blend_type = 0 + self.color.set(0, 0, 0, 0) + self.opacity = 255 + @_escape_duration = 32 + @_whiten_duration = 0 + @_appear_duration = 0 + @_collapse_duration = 0 + end + + def collapse + self.blend_type = 1 + self.color.set(255, 64, 64, 255) + self.opacity = 255 + @_collapse_duration = 48 + @_whiten_duration = 0 + @_appear_duration = 0 + @_escape_duration = 0 + end + + def damage(value, critical) + dispose_damage + damage_string = (value.is_a?(Numeric)) ? value.abs.to_s : value.to_s + bitmap = Bitmap.new(160, 48) + bitmap.font.name = "Arial Black" + bitmap.font.size = 32 + bitmap.font.color.set(0, 0, 0) + bitmap.draw_text(-1, 12-1, 160, 36, damage_string, 1) + bitmap.draw_text(+1, 12-1, 160, 36, damage_string, 1) + bitmap.draw_text(-1, 12+1, 160, 36, damage_string, 1) + bitmap.draw_text(+1, 12+1, 160, 36, damage_string, 1) + if value.is_a?(Numeric) and value < 0 + bitmap.font.color.set(176, 255, 144) + else + bitmap.font.color.set(255, 255, 255) + end + bitmap.draw_text(0, 12, 160, 36, damage_string, 1) + if critical + bitmap.font.size = 20 + bitmap.font.color.set(0, 0, 0) + bitmap.draw_text(-1, -1, 160, 20, "CRITICAL", 1) + bitmap.draw_text(+1, -1, 160, 20, "CRITICAL", 1) + bitmap.draw_text(-1, +1, 160, 20, "CRITICAL", 1) + bitmap.draw_text(+1, +1, 160, 20, "CRITICAL", 1) + bitmap.font.color.set(255, 255, 255) + bitmap.draw_text(0, 0, 160, 20, "CRITICAL", 1) + end + @_damage_sprite = ::Sprite.new(self.viewport) + @_damage_sprite.bitmap = bitmap + @_damage_sprite.ox = 80 + @_damage_sprite.oy = 20 + @_damage_sprite.x = self.x + @_damage_sprite.y = self.y - self.oy / 2 + @_damage_sprite.z = 3000 + @_damage_duration = 40 + end + + def pushAnimation(array, anim) + for i in 0...array.length + next if array[i] && array[i].active? + array[i] = anim + return + end + array.push(anim) + end + + def animation(animation, hit, height = 3) + anim = SpriteAnimation.new(self) + anim.animation(animation,hit,height) + pushAnimation(@animations,anim) + end + + def loop_animation(animation) + anim = SpriteAnimation.new(self) + anim.loop_animation(animation) + pushAnimation(@loopAnimations,anim) + end + + def dispose_damage + return if @_damage_sprite == nil + @_damage_sprite.bitmap.dispose + @_damage_sprite.dispose + @_damage_sprite = nil + @_damage_duration = 0 + end + + def dispose_animation + for a in @animations + a.dispose_animation if a + end + @animations.clear + end + + def dispose_loop_animation + for a in @loopAnimations + a.dispose_loop_animation if a + end + @loopAnimations.clear + end + + def blink_on + return if @_blink + @_blink = true + @_blink_count = 0 + end + + def blink_off + return unless @_blink + @_blink = false + self.color.set(0, 0, 0, 0) + end + + def blink? + return @_blink + end + + def effect? + return true if @_whiten_duration > 0 + return true if @_appear_duration > 0 + return true if @_escape_duration > 0 + return true if @_collapse_duration > 0 + return true if @_damage_duration > 0 + for a in @animations + return true if a.effect? + end + return false + end + + def update + super + if @_whiten_duration > 0 + @_whiten_duration -= 1 + self.color.alpha = 128 - (16 - @_whiten_duration) * 10 + end + if @_appear_duration > 0 + @_appear_duration -= 1 + self.opacity = (16 - @_appear_duration) * 16 + end + if @_escape_duration > 0 + @_escape_duration -= 1 + self.opacity = 256 - (32 - @_escape_duration) * 10 + end + if @_collapse_duration > 0 + @_collapse_duration -= 1 + self.opacity = 256 - (48 - @_collapse_duration) * 6 + end + if @_damage_duration > 0 + @_damage_duration -= 1 + case @_damage_duration + when 38..39 + @_damage_sprite.y -= 4 + when 36..37 + @_damage_sprite.y -= 2 + when 34..35 + @_damage_sprite.y += 2 + when 28..33 + @_damage_sprite.y += 4 + end + @_damage_sprite.opacity = 256 - (12 - @_damage_duration) * 32 + if @_damage_duration == 0 + dispose_damage + end + end + for a in @animations + a.update + end + for a in @loopAnimations + a.update + end + if @_blink + @_blink_count = (@_blink_count + 1) % 32 + if @_blink_count < 16 + alpha = (16 - @_blink_count) * 6 + else + alpha = (@_blink_count - 16) * 6 + end + self.color.set(255, 255, 255, alpha) + end + SpriteAnimation.clear + end + + def update_animation + for a in @animations + a.update_animation if a && a.active? + end + end + + def update_loop_animation + for a in @loopAnimations + a.update_loop_animation if a && a.active? + end + end + + def x=(x) + for a in @animations + a.x = x if a + end + for a in @loopAnimations + a.x = x if a + end + super + end + + def y=(y) + for a in @animations + a.y = y if a + end + for a in @loopAnimations + a.y = y if a + end + super + end + end +end diff --git a/Data/Scripts/001_Technical/004_Win32API.rb b/Data/Scripts/001_Technical/004_Win32API.rb new file mode 100644 index 000000000..495aa517f --- /dev/null +++ b/Data/Scripts/001_Technical/004_Win32API.rb @@ -0,0 +1,104 @@ +class Win32API + @@RGSSWINDOW = nil + @@GetCurrentThreadId = Win32API.new('kernel32','GetCurrentThreadId', '%w()','l') + @@GetWindowThreadProcessId = Win32API.new('user32','GetWindowThreadProcessId', '%w(l p)','l') + @@FindWindowEx = Win32API.new('user32','FindWindowEx', '%w(l l p p)','l') + + def Win32API.SetWindowText(text) + hWnd = pbFindRgssWindow + swp = Win32API.new('user32','SetWindowTextA',%(l, p),'i') + swp.call(hWnd, text.to_s) + end + + # Added by Peter O. as a more reliable way to get the RGSS window + def Win32API.pbFindRgssWindow + return @@RGSSWINDOW if @@RGSSWINDOW + processid = [0].pack('l') + threadid = @@GetCurrentThreadId.call + nextwindow = 0 + begin + nextwindow = @@FindWindowEx.call(0,nextwindow,"RGSS Player",0) + if nextwindow!=0 + wndthreadid = @@GetWindowThreadProcessId.call(nextwindow,processid) + if wndthreadid==threadid + @@RGSSWINDOW = nextwindow + return @@RGSSWINDOW + end + end + end until nextwindow==0 + raise "Can't find RGSS player window" + return 0 + end + + def Win32API.SetWindowPos(w, h) + hWnd = pbFindRgssWindow + windowrect = Win32API.GetWindowRect + clientsize = Win32API.client_size + xExtra = windowrect.width-clientsize[0] + yExtra = windowrect.height-clientsize[1] + swp = Win32API.new('user32','SetWindowPos',%(l,l,i,i,i,i,i),'i') + win = swp.call(hWnd,0,windowrect.x,windowrect.y,w+xExtra,h+yExtra,0) + return win + end + + def Win32API.client_size + hWnd = pbFindRgssWindow + rect = [0,0,0,0].pack('l4') + Win32API.new('user32','GetClientRect',%w(l p),'i').call(hWnd,rect) + width,height = rect.unpack('l4')[2..3] + return width,height + end + + def Win32API.GetWindowRect + hWnd = pbFindRgssWindow + rect = [0,0,0,0].pack('l4') + Win32API.new('user32','GetWindowRect',%w(l p),'i').call(hWnd,rect) + x,y,width,height = rect.unpack('l4') + return Rect.new(x,y,width-x,height-y) + end + + def Win32API.focusWindow + window = Win32API.new('user32','ShowWindow','LL','L') + hWnd = pbFindRgssWindow + window.call(hWnd,9) + end + + def Win32API.fillScreen + setWindowLong = Win32API.new('user32','SetWindowLong','LLL','L') + setWindowPos = Win32API.new('user32','SetWindowPos','LLIIIII','I') + metrics = Win32API.new('user32', 'GetSystemMetrics', 'I', 'I') + hWnd = pbFindRgssWindow + width = metrics.call(0) + height = metrics.call(1) + setWindowLong.call(hWnd,-16,0x00000000) + setWindowPos.call(hWnd,0,0,0,width,height,0) + Win32API.focusWindow + return [width,height] + end + + def Win32API.restoreScreen + setWindowLong = Win32API.new('user32','SetWindowLong','LLL','L') + setWindowPos = Win32API.new('user32','SetWindowPos','LLIIIII','I') + metrics = Win32API.new('user32','GetSystemMetrics','I','I') + hWnd = pbFindRgssWindow + width = SCREEN_WIDTH*$ResizeFactor + height = SCREEN_HEIGHT*$ResizeFactor + if $PokemonSystem && $PokemonSystem.border==1 + width += BORDER_WIDTH*2*$ResizeFactor + height += BORDER_HEIGHT*2*$ResizeFactor + end + x = [(metrics.call(0)-width)/2,0].max + y = [(metrics.call(1)-height)/2,0].max + setWindowLong.call(hWnd,-16,0x14CA0000) + setWindowPos.call(hWnd,0,x,y,width+6,height+29,0) + Win32API.focusWindow + return [width,height] + end +end + + + +# Well done for finding this place. +# DO NOT EDIT THESE +ESSENTIALS_VERSION = "18" +ERROR_TEXT = "" \ No newline at end of file diff --git a/Data/Scripts/001_Technical/005_Sockets.rb b/Data/Scripts/001_Technical/005_Sockets.rb new file mode 100644 index 000000000..57ad18ce6 --- /dev/null +++ b/Data/Scripts/001_Technical/005_Sockets.rb @@ -0,0 +1,696 @@ +module Win32 + def copymem(len) + buf = "\0" * len + Win32API.new("kernel32", "RtlMoveMemory", "ppl", "").call(buf, self, len) + buf + end +end + + + +# Extends the numeric class. +class Numeric + include Win32 +end + + + +# Extends the string class. +class String + include Win32 +end + + + +module Winsock + DLL = "ws2_32" + #----------------------------------------------------------------------------- + # * Accept Connection + #----------------------------------------------------------------------------- + def self.accept(*args) + Win32API.new(DLL, "accept", "ppl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Bind + #----------------------------------------------------------------------------- + def self.bind(*args) + Win32API.new(DLL, "bind", "ppl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Close Socket + #----------------------------------------------------------------------------- + def self.closesocket(*args) + Win32API.new(DLL, "closesocket", "p", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Connect + #----------------------------------------------------------------------------- + def self.connect(*args) + Win32API.new(DLL, "connect", "ppl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Get host (Using Adress) + #----------------------------------------------------------------------------- + def self.gethostbyaddr(*args) + Win32API.new(DLL, "gethostbyaddr", "pll", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Get host (Using Name) + #----------------------------------------------------------------------------- + def self.gethostbyname(*args) + Win32API.new(DLL, "gethostbyname", "p", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Get host's Name + #----------------------------------------------------------------------------- + def self.gethostname(*args) + Win32API.new(DLL, "gethostname", "pl", "").call(*args) + end + #----------------------------------------------------------------------------- + # * Get Server (Using Name) + #----------------------------------------------------------------------------- + def self.getservbyname(*args) + Win32API.new(DLL, "getservbyname", "pp", "p").call(*args) + end + #----------------------------------------------------------------------------- + # * Convert Host Long To Network Long + #----------------------------------------------------------------------------- + def self.htonl(*args) + Win32API.new(DLL, "htonl", "l", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Convert Host Short To Network Short + #----------------------------------------------------------------------------- + def self.htons(*args) + Win32API.new(DLL, "htons", "l", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Inet Adress + #----------------------------------------------------------------------------- + def self.inet_addr(*args) + Win32API.new(DLL, "inet_addr", "p", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Inet N To A + #----------------------------------------------------------------------------- + def self.inet_ntoa(*args) + Win32API.new(DLL, "inet_ntoa", "l", "p").call(*args) + end + #----------------------------------------------------------------------------- + # * Listen + #----------------------------------------------------------------------------- + def self.listen(*args) + Win32API.new(DLL, "listen", "pl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Recieve + #----------------------------------------------------------------------------- + def self.recv(*args) + Win32API.new(DLL, "recv", "ppll", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Select + #----------------------------------------------------------------------------- + def self.select(*args) + Win32API.new(DLL, "select", "lpppp", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Send + #----------------------------------------------------------------------------- + def self.send(*args) + Win32API.new(DLL, "send", "ppll", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Set Socket Options + #----------------------------------------------------------------------------- + def self.setsockopt(*args) + Win32API.new(DLL, "setsockopt", "pllpl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Shutdown + #----------------------------------------------------------------------------- + def self.shutdown(*args) + Win32API.new(DLL, "shutdown", "pl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Socket + #----------------------------------------------------------------------------- + def self.socket(*args) + Win32API.new(DLL, "socket", "lll", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Get Last Error + #----------------------------------------------------------------------------- + def self.WSAGetLastError(*args) + Win32API.new(DLL, "WSAGetLastError", "", "l").call(*args) + end +end + + + +if !Object.const_defined?(:Socket) # for compatibility + + + +#=============================================================================== +# ** Socket - Creates and manages sockets. +#------------------------------------------------------------------------------- +# Author Ruby +# Version 1.8.1 +#=============================================================================== +class Socket + #----------------------------------------------------------------------------- + # * Constants + #----------------------------------------------------------------------------- + AF_UNSPEC = 0 + AF_UNIX = 1 + AF_INET = 2 + AF_IPX = 6 + AF_APPLETALK = 16 + PF_UNSPEC = 0 + PF_UNIX = 1 + PF_INET = 2 + PF_IPX = 6 + PF_APPLETALK = 16 + SOCK_STREAM = 1 + SOCK_DGRAM = 2 + SOCK_RAW = 3 + SOCK_RDM = 4 + SOCK_SEQPACKET = 5 + IPPROTO_IP = 0 + IPPROTO_ICMP = 1 + IPPROTO_IGMP = 2 + IPPROTO_GGP = 3 + IPPROTO_TCP = 6 + IPPROTO_PUP = 12 + IPPROTO_UDP = 17 + IPPROTO_IDP = 22 + IPPROTO_ND = 77 + IPPROTO_RAW = 255 + IPPROTO_MAX = 256 + SOL_SOCKET = 65535 + SO_DEBUG = 1 + SO_REUSEADDR = 4 + SO_KEEPALIVE = 8 + SO_DONTROUTE = 16 + SO_BROADCAST = 32 + SO_LINGER = 128 + SO_OOBINLINE = 256 + SO_RCVLOWAT = 4100 + SO_SNDTIMEO = 4101 + SO_RCVTIMEO = 4102 + SO_ERROR = 4103 + SO_TYPE = 4104 + SO_SNDBUF = 4097 + SO_RCVBUF = 4098 + SO_SNDLOWAT = 4099 + TCP_NODELAY = 1 + MSG_OOB = 1 + MSG_PEEK = 2 + MSG_DONTROUTE = 4 + IP_OPTIONS = 1 + IP_DEFAULT_MULTICAST_LOOP = 1 + IP_DEFAULT_MULTICAST_TTL = 1 + IP_MULTICAST_IF = 2 + IP_MULTICAST_TTL = 3 + IP_MULTICAST_LOOP = 4 + IP_ADD_MEMBERSHIP = 5 + IP_DROP_MEMBERSHIP = 6 + IP_TTL = 7 + IP_TOS = 8 + IP_MAX_MEMBERSHIPS = 20 + EAI_ADDRFAMILY = 1 + EAI_AGAIN = 2 + EAI_BADFLAGS = 3 + EAI_FAIL = 4 + EAI_FAMILY = 5 + EAI_MEMORY = 6 + EAI_NODATA = 7 + EAI_NONAME = 8 + EAI_SERVICE = 9 + EAI_SOCKTYPE = 10 + EAI_SYSTEM = 11 + EAI_BADHINTS = 12 + EAI_PROTOCOL = 13 + EAI_MAX = 14 + AI_PASSIVE = 1 + AI_CANONNAME = 2 + AI_NUMERICHOST = 4 + AI_MASK = 7 + AI_ALL = 256 + AI_V4MAPPED_CFG = 512 + AI_ADDRCONFIG = 1024 + AI_DEFAULT = 1536 + AI_V4MAPPED = 2048 + #-------------------------------------------------------------------------- + # * Returns the associated IP address for the given hostname. + #-------------------------------------------------------------------------- + def self.getaddress(host) + gethostbyname(host)[3].unpack("C4").join(".") + end + #-------------------------------------------------------------------------- + # * Returns the associated IP address for the given hostname. + #-------------------------------------------------------------------------- + def self.getservice(serv) + case serv + when Numeric + return serv + when String + return getservbyname(serv) + else + raise "Please use an integer or string for services." + end + end + #-------------------------------------------------------------------------- + # * Returns information about the given hostname. + #-------------------------------------------------------------------------- + def self.gethostbyname(name) + raise SocketError::ENOASSOCHOST if (ptr = Winsock.gethostbyname(name)) == 0 + host = ptr.copymem(16).unpack("iissi") + [host[0].copymem(64).split("\0")[0], [], host[2], host[4].copymem(4).unpack("l")[0].copymem(4)] + end + #-------------------------------------------------------------------------- + # * Returns the user's hostname. + #-------------------------------------------------------------------------- + def self.gethostname + buf = "\0" * 256 + Winsock.gethostname(buf, 256) + buf.strip + end + #-------------------------------------------------------------------------- + # * Returns information about the given service. + #-------------------------------------------------------------------------- + def self.getservbyname(name) + case name + when /echo/i + return 7 + when /daytime/i + return 13 + when /ftp/i + return 21 + when /telnet/i + return 23 + when /smtp/i + return 25 + when /time/i + return 37 + when /http/i + return 80 + when /pop/i + return 110 + else + #Network.testing? != 0 ? (Network.testresult(true)) : (raise "Service not recognized.") + #return if Network.testing? == 2 + end + end + #-------------------------------------------------------------------------- + # * Creates an INET-sockaddr struct. + #-------------------------------------------------------------------------- + def self.sockaddr_in(port, host) + begin + [AF_INET, getservice(port)].pack("sn") + gethostbyname(host)[3] + [].pack("x8") + rescue + #Network.testing? != 0 ? (Network.testresult(true)): (nil) + #return if Network.testing? == 2 + rescue Hangup + #Network.testing? != 0 ? (Network.testresult(true)): (nil) + #return if Network.testing? == 2 + end + end + #-------------------------------------------------------------------------- + # * Creates a new socket and connects it to the given host and port. + #-------------------------------------------------------------------------- + def self.open(*args) + socket = new(*args) + if block_given? + begin + yield socket + ensure + socket.close + end + end + nil + end + #-------------------------------------------------------------------------- + # * Creates a new socket. + #-------------------------------------------------------------------------- + def initialize(domain, type, protocol) + SocketError.check if (@fd = Winsock.socket(domain, type, protocol)) == -1 + @fd + end + #-------------------------------------------------------------------------- + # * Accepts incoming connections. + #-------------------------------------------------------------------------- + def accept(flags = 0) + buf = "\0" * 16 + SocketError.check if Winsock.accept(@fd, buf, flags) == -1 + buf + end + #-------------------------------------------------------------------------- + # * Binds a socket to the given sockaddr. + #-------------------------------------------------------------------------- + def bind(sockaddr) + SocketError.check if (ret = Winsock.bind(@fd, sockaddr, sockaddr.size)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Closes a socket. + #-------------------------------------------------------------------------- + def close + SocketError.check if (ret = Winsock.closesocket(@fd)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Connects a socket to the given sockaddr. + #-------------------------------------------------------------------------- + def connect(sockaddr) + #return if Network.testing? == 2 + SocketError.check if (ret = Winsock.connect(@fd, sockaddr, sockaddr.size)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Listens for incoming connections. + #-------------------------------------------------------------------------- + def listen(backlog) + SocketError.check if (ret = Winsock.listen(@fd, backlog)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Checks waiting data's status. + #-------------------------------------------------------------------------- + def select(timeout) # timeout in seconds + SocketError.check if (ret = Winsock.select(1, [1, @fd].pack("ll"), 0, 0, [timeout.to_i, + (timeout * 1000000).to_i].pack("ll"))) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Checks if data is waiting. + #-------------------------------------------------------------------------- + def ready? + not select(0) == 0 + end + #-------------------------------------------------------------------------- + # * Reads data from socket. + #-------------------------------------------------------------------------- + def read(len) + buf = "\0" * len + Win32API.new("msvcrt", "_read", "lpl", "l").call(@fd, buf, len) + buf + end + #-------------------------------------------------------------------------- + # * Returns received data. + #-------------------------------------------------------------------------- + def recv(len, flags = 0) + retString="" + remainLen=len + while remainLen > 0 + buf = "\0" * remainLen + retval=Winsock.recv(@fd, buf, buf.size, flags) + SocketError.check if retval == -1 + # Note: Return value may not equal requested length + remainLen-=retval + retString+=buf[0,retval] + end + return retString + end + #-------------------------------------------------------------------------- + # * Sends data to a host. + #-------------------------------------------------------------------------- + def send(data, flags = 0) + SocketError.check if (ret = Winsock.send(@fd, data, data.size, flags)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Recieves file from a socket + # size : file size + # scene : update scene boolean + #-------------------------------------------------------------------------- + def recv_file(size,scene=false,file="") + data = [] + size.times do |i| + if scene == true + $scene.recv_update(size,i,file) if i%((size/1000)+1)== 0 + else + Graphics.update if i%1024 == 0 + end + data << recv(1) + end + return data + end + + def recvTimeout + if select(10)==0 + raise Hangup.new("Timeout") + end + return recv(1) + end + #-------------------------------------------------------------------------- + # * Gets + #-------------------------------------------------------------------------- + def gets + # Create buffer + message = "" + # Loop Until "end of line" + count=0 + while true + x=select(0.05) + if x==0 + count+=1 + Graphics.update if count%10==0 + raise Errno::ETIMEOUT if count>200 + next + end + ch = recv(1) + break if ch == "\n" + message += ch + end + # Return recieved data + return message + end + #-------------------------------------------------------------------------- + # * Writes data to socket. + #-------------------------------------------------------------------------- + def write(data) + Win32API.new("msvcrt", "_write", "lpl", "l").call(@fd, data, 1) + end +end + + + +#=============================================================================== +# ** TCPSocket - Creates and manages TCP sockets. +#------------------------------------------------------------------------------- +# Author Ruby +# Version 1.8.1 +#=============================================================================== + +#------------------------------------------------------------------------------- +# Begin SDK Enabled Check +#------------------------------------------------------------------------------- +class TCPSocket < Socket + #-------------------------------------------------------------------------- + # * Creates a new socket and connects it to the given host and port. + #-------------------------------------------------------------------------- + def self.open(*args) + socket = new(*args) + if block_given? + begin + yield socket + ensure + socket.close + end + end + nil + end + #-------------------------------------------------------------------------- + # * Creates a new socket and connects it to the given host and port. + #-------------------------------------------------------------------------- + def initialize(host, port) + super(AF_INET, SOCK_STREAM, IPPROTO_TCP) + connect(Socket.sockaddr_in(port, host)) + end +end + + + +#============================================================================== +# ** SocketError +#------------------------------------------------------------------------------ +# Default exception class for sockets. +#============================================================================== +class SocketError < StandardError + ENOASSOCHOST = "getaddrinfo: no address associated with hostname." + + def self.check + errno = Winsock.WSAGetLastError + #if not Network.testing? == 1 + raise Errno.const_get(Errno.constants.detect { |c| Errno.const_get(c).new.errno == errno }) + #else + # errno != 0 ? (Network.testresult(true)) : (Network.testresult(false)) + #end + end +end + + + +end # !Object.const_defined?(:Socket) + + +############################# +# +# HTTP utility functions +# +############################# +def pbPostData(url, postdata, filename=nil, depth=0) + if url[/^http:\/\/([^\/]+)(.*)$/] + host = $1 + path = $2 + path = "/" if path.length==0 + userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.14) Gecko/2009082707 Firefox/3.0.14" + body = postdata.map { |key, value| + keyString = key.to_s + valueString = value.to_s + keyString.gsub!(/[^a-zA-Z0-9_\.\-]/n) { |s| sprintf('%%%02x', s[0]) } + valueString.gsub!(/[^a-zA-Z0-9_\.\-]/n) { |s| sprintf('%%%02x', s[0]) } + next "#{keyString}=#{valueString}" + }.join('&') + request = "POST #{path} HTTP/1.1\r\n" + request += "Host: #{host}\r\n" + request += "Proxy-Connection: Close\r\n" + request += "Content-Length: #{body.length}\r\n" + request += "Pragma: no-cache\r\n" + request += "User-Agent: #{userAgent}\r\n" + request += "Content-Type: application/x-www-form-urlencoded\r\n" + request += "\r\n" + request += body + return pbHttpRequest(host, request, filename, depth) + end + return "" +end + +def pbDownloadData(url, filename=nil, depth=0) + raise "Redirection level too deep" if depth>10 + if url[/^http:\/\/([^\/]+)(.*)$/] + host = $1 + path = $2 + path = "/" if path.length==0 + userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.14) Gecko/2009082707 Firefox/3.0.14" + request = "GET #{path} HTTP/1.1\r\n" + request += "User-Agent: #{userAgent}\r\n" + request += "Pragma: no-cache\r\n" + request += "Host: #{host}\r\n" + request += "Proxy-Connection: Close\r\n" + request += "\r\n" + return pbHttpRequest(host, request, filename, depth) + end + return "" +end + +def pbHttpRequest(host, request, filename=nil, depth=0) + raise "Redirection level too deep" if depth>10 + socket = ::TCPSocket.new(host, 80) + time = Time.now.to_i + begin + socket.send(request) + result = socket.gets + data = "" + # Get the HTTP result + if result[/^HTTP\/1\.[01] (\d+).*/] + errorcode = $1.to_i + raise "HTTP Error #{errorcode}" if errorcode>=400 && errorcode<500 + headers = {} + # Get the response headers + while true + result = socket.gets.sub(/\r$/,"") + break if result=="" + if result[/^([^:]+):\s*(.*)/] + headers[$1] = $2 + end + end + length = -1 + chunked = false + if headers["Content-Length"] + length = headers["Content-Length"].to_i + end + if headers["Transfer-Encoding"]=="chunked" + chunked = true + end + if headers["Location"] && errorcode>=300 && errorcode<400 + socket.close rescue socket = nil + return pbDownloadData(headers["Location"],filename,depth+1) + end + if chunked + # Chunked content + while true + lengthline = socket.gets.sub(/\r$/,"") + length = lengthline.to_i(16) + break if length==0 + while Time.now.to_i-time>=5 || socket.select(10)==0 + time = Time.now.to_i + Graphics.update + end + data += socket.recv(length) + socket.gets + end + elsif length==-1 + # No content length specified + while true + break if socket.select(500)==0 + while Time.now.to_i-time>=5 || socket.select(10)==0 + time = Time.now.to_i + Graphics.update + end + data += socket.recv(1) + end + else + # Content length specified + while length>0 + chunk = [length,4096].min + while Time.now.to_i-time>=5 || socket.select(10)==0 + time = Time.now.to_i + Graphics.update + end + data += socket.recv(chunk) + length -= chunk + end + end + end + return data if !filename + File.open(filename,"wb") { |f| f.write(data) } + ensure + socket.close rescue socket = nil + end + return "" +end + +def pbDownloadToString(url) + begin + data = pbDownloadData(url) + return data + rescue + return "" + end +end + +def pbDownloadToFile(url, file) + begin + pbDownloadData(url,file) + rescue + end +end + +def pbPostToString(url, postdata) + begin + data = pbPostData(url, postdata) + return data + rescue + return "" + end +end + +def pbPostToFile(url, postdata, file) + begin + pbPostData(url, postdata,file) + rescue + end +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/006_DebugConsole.rb b/Data/Scripts/001_Technical/006_DebugConsole.rb new file mode 100644 index 000000000..1672d7f25 --- /dev/null +++ b/Data/Scripts/001_Technical/006_DebugConsole.rb @@ -0,0 +1,158 @@ +module Console + attr_reader :bufferHandle + GENERIC_READ = 0x80000000 + GENERIC_WRITE = 0x40000000 + FILE_SHARE_READ = 0x00000001 + FILE_SHARE_WRITE = 0x00000002 + CONSOLE_TEXTMODE_BUFFER = 0x00000001 + + def Console::AllocConsole + return @apiAllocConsole.call + end + + def Console::CreateConsoleScreenBuffer(dwDesiredAccess,dwShareMode,dwFlags) + return @apiCreateConsoleScreenBuffer.call(dwDesiredAccess,dwShareMode,nil,dwFlags,nil) + end + + def Console::WriteConsole(lpBuffer) + hFile = @bufferHandle + return if !hFile + return @apiWriteConsole.call(hFile,lpBuffer,lpBuffer.size,0,0) + end + + def Console::ReadConsole(lpBuffer) + hFile = @bufferHandle + return @apiReadConsole.call(hFile,lpBuffer,lpBuffer.size,0,0) + end + + def Console::SetConsoleActiveScreenBuffer(hScreenBuffer) + return @apiSetConsoleActiveScreenBuffer.call(hScreenBuffer) + end + + def Console::SetConsoleScreenBufferSize(hScreenBuffer,x,y) + return @apiSetConsoleScreenBufferSize.call(hScreenBuffer,[x,y].pack("vv")) + end + + def Console::SetConsoleTitle(title) + return @apiSetConsoleTitle.call(title) + end + + def self.setup_console + return unless $DEBUG + @apiAllocConsole = Win32API.new("kernel32","AllocConsole","","l") + @apiCreateConsoleScreenBuffer = Win32API.new("kernel32","CreateConsoleScreenBuffer","nnpnp","l") + @apiSetConsoleActiveScreenBuffer = Win32API.new("kernel32","SetConsoleActiveScreenBuffer","l","s") + @apiWriteConsole = Win32API.new("kernel32","WriteConsole","lpnnn","S") + @apiReadConsole = Win32API.new("kernel32","ReadConsole","lpnnn","S") + @apiSetConsoleScreenBufferSize = Win32API.new("kernel32","SetConsoleScreenBufferSize","lp","S") + @apiSetConsoleTitle = Win32API.new("kernel32","SetConsoleTitle","p","s") + access = (GENERIC_READ | GENERIC_WRITE) + sharemode = (FILE_SHARE_READ | FILE_SHARE_WRITE) + returnCode = AllocConsole() + @bufferHandle = CreateConsoleScreenBuffer(access,sharemode,CONSOLE_TEXTMODE_BUFFER) + f = File.open("Game.ini") + lines = f.readlines() + s = lines[3] + len = s.size + title = (s[6,len - 7]) + SetConsoleScreenBufferSize(@bufferHandle,100,2000) + SetConsoleTitle("Debug Console -- #{title}") + echo "#{title} Output Window\n" + echo "-------------------------------\n" + echo "If you are seeing this window, you are running\n" + echo "#{title} in Debug Mode. This means\n" + echo "that you're either playing a Debug Version, or\n" + echo "you are playing from within RPG Maker XP.\n" + echo "\n" + echo "Closing this window will close the game. If \n" + echo "you want to get rid of this window, run the\n" + echo "program from the Shell, or download a Release\n" + echo "version.\n" + echo "\n" + echo "Gameplay will be paused while the console has\n" + echo "focus. To resume playing, switch to the Game\n" + echo "Window.\n" + echo "-------------------------------\n" + echo "Debug Output:\n" + echo "-------------------------------\n\n" + SetConsoleActiveScreenBuffer(@bufferHandle) + end + + def self.readInput + length=20 + buffer=0.chr*length + eventsread=0.chr*4 + done=false + input="" + while !done + echo("waiting for input") + begin + @apiReadConsole.call(@bufferHandle,buffer,1,eventsread) + rescue Hangup + return + end + offset=0 + events=eventsread.unpack("V") + echo("got input [eventsread #{events}") + for i in 0...events[0] + keyevent=buffer[offset,20] + keyevent=keyevent.unpack("vCvvvvV") + if keyevent[0]==1 && keyevent[1]>0 + input+=keyevent[4].chr + if keyevent[4].chr=="\n" + done=true + break + end + end + offset+=20 + end + end + return input + end + + def self.readInput2 + buffer=0.chr + done=false + input="" + eventsread=0.chr*4 + while !done + if ReadConsole(buffer)==0 + getlast = Win32API.new("kernel32","GetLastError","","n") + echo(sprintf("failed (%d)\r\n",getlast.call())) + break + end + offset=0 + events=eventsread.unpack("V") + if events[0]>0 + echo("got input [eventsread #{events}][buffer #{buffer}]\r\n") + key=buffer[0,events[0]] + input+=key + if key=="\n" + break + end + Graphics.update + end + end + return input + end + + def self.get_input + echo self.readInput2 + end +end + + + +module Kernel + def echo(string) + unless $DEBUG + return + end + Console::WriteConsole(string.is_a?(String) ? string : string.inspect) + end + + def echoln(string) + echo(string) + echo("\r\n") + end +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/007_Sprite_Resizer.rb b/Data/Scripts/001_Technical/007_Sprite_Resizer.rb new file mode 100644 index 000000000..da9e2a094 --- /dev/null +++ b/Data/Scripts/001_Technical/007_Sprite_Resizer.rb @@ -0,0 +1,757 @@ +#=============================================================================== +# Overriding Sprite, Viewport, and Plane to support resizing +# By Peter O. +# Modified by Luka S.J. and Maruno to support fullscreen and more sizes. +# -- This is a stand-alone RGSS script. -- +#=============================================================================== +$ResizeFactor = 1.0 +$ResizeFactorMul = 100 +$ResizeOffsetX = 0 +$ResizeOffsetY = 0 +$ResizeFactorSet = false +$HaveResizeBorder = false + +if true # Disables using Alt+Enter to go fullscreen + regHotKey = Win32API.new('user32', 'RegisterHotKey', 'LIII', 'I') + regHotKey.call(0, 1, 1, 0x0D) +end + +def pbSetResizeFactor(factor=1,norecalc=false) + factor = [0.5,1.0,2.0,-1][factor] if !norecalc + (factor<0) ? pbConfigureFullScreen : pbConfigureWindowedScreen(factor) +end + +def pbSetResizeFactor2(factor,force=false) + if $ResizeFactor!=factor || force + $ResizeFactor = factor + $ResizeFactorMul = (factor*100).to_i + pbRefreshResizeFactor if $ResizeFactorSet + end + $ResizeFactorSet = true + $ResizeBorder.refresh if $HaveResizeBorder + begin + if Graphics.haveresizescreen + Graphics.oldresizescreen( + (Graphics.width+$ResizeOffsetX*2)*factor, + (Graphics.height+$ResizeOffsetY*2)*factor + ) + end + Win32API.SetWindowPos( + (Graphics.width+$ResizeOffsetX*2)*factor, + (Graphics.height+$ResizeOffsetY*2)*factor + ) + rescue + end +end + +def pbRefreshResizeFactor + ObjectSpace.each_object(Sprite) { |o| + next if o.disposed? + o.x = o.x + o.y = o.y + o.ox = o.ox + o.oy = o.oy + o.zoom_x = o.zoom_x + o.zoom_y = o.zoom_y + } + ObjectSpace.each_object(Viewport) { |o| + begin + o.rect = o.rect + o.ox = o.ox + o.oy = o.oy + rescue RGSSError + end + } + ObjectSpace.each_object(Plane) { |o| + next if o.disposed? + o.zoom_x = o.zoom_x + o.zoom_y = o.zoom_y + } +end + +def pbConfigureFullScreen + params = Win32API.fillScreen + fullgamew = gamew = SCREEN_WIDTH + fullgameh = gameh = SCREEN_HEIGHT + if !BORDER_FULLY_SHOWS && $PokemonSystem && $PokemonSystem.border==1 + fullgamew += BORDER_WIDTH * 2 + fullgameh += BORDER_HEIGHT * 2 + end +# factor_x = ((2*params[0])/fullgamew).floor +# factor_y = ((2*params[1])/fullgameh).floor +# factor = [factor_x,factor_y].min/2.0 + factor_x = (params[0]/fullgamew).floor + factor_y = (params[1]/fullgameh).floor + factor = [factor_x,factor_y].min + offset_x = (params[0]-gamew*factor)/(2*factor) + offset_y = (params[1]-gameh*factor)/(2*factor) + $ResizeOffsetX = offset_x + $ResizeOffsetY = offset_y + ObjectSpace.each_object(Viewport) { |o| + begin + next if o.rect.nil? + ox = o.rect.x-$ResizeOffsetX + oy = o.rect.y-$ResizeOffsetY + o.rect.x = ox+offset_x + o.rect.y = oy+offset_y + rescue RGSSError + end + } + pbSetResizeFactor2(factor,true) +end + +def pbConfigureWindowedScreen(value) + border = $PokemonSystem ? $PokemonSystem.border : 0 + $ResizeOffsetX = [0,BORDER_WIDTH][border] + $ResizeOffsetY = [0,BORDER_HEIGHT][border] + pbSetResizeFactor2(value,true) + Win32API.restoreScreen +end + +def setScreenBorderName(border) + if !$HaveResizeBorder + $ResizeBorder = ScreenBorder.new + $HaveResizeBorder = true + end + $ResizeBorder.bordername = border if $ResizeBorder +end + + + +module Graphics + ## Nominal screen size + @@width = SCREEN_WIDTH + @@height = SCREEN_HEIGHT + + def self.width + return @@width.to_i + end + + def self.height + return @@height.to_i + end + + @@fadeoutvp = Viewport.new(0,0,640,480) + @@fadeoutvp.z = 0x3FFFFFFF + @@fadeoutvp.color = Color.new(0,0,0,0) + + def self.brightness + return 255-@@fadeoutvp.color.alpha + end + + def self.brightness=(value) + value = 0 if value<0 + value = 255 if value>255 + @@fadeoutvp.color.alpha = 255-value + end + + def self.fadein(frames) + return if frames<=0 + curvalue = self.brightness + count = (255-self.brightness) + frames.times do |i| + self.brightness = curvalue+(count*i/frames) + self.update + end + end + + def self.wait(frames) + return if frames<=0 + frames.times do |i| + self.update + end + end + + def self.fadeout(frames) + return if frames<=0 + curvalue = self.brightness + count = self.brightness + frames.times do |i| + self.brightness = curvalue-(count*i/frames) + self.update + end + end + + class << self + begin + x = @@haveresizescreen + rescue NameError # If exception is caught, the class + if !method_defined?(:oldresizescreen) # variable wasn't defined yet + begin + alias oldresizescreen resize_screen + @@haveresizescreen = true + rescue + @@haveresizescreen = false + end + else + @@haveresizescreen = false + end + end + + def haveresizescreen + @@haveresizescreen + end + end + + def self.resize_screen(w,h) + @@width = w + @@height = h + pbSetResizeFactor($ResizeFactor,true) + end + + @@deletefailed = false + + def self.snap_to_bitmap(resize=true) + tempPath = ENV["TEMP"]+"\\tempscreen.bmp" + if safeExists?(tempPath) && @@deletefailed + begin + File.delete(tempPath) + @@deletefailed = false + rescue Errno::EACCES + @@deletefailed = true + return nil + end + end + if safeExists?("./rubyscreen.dll") + takescreen = Win32API.new("rubyscreen.dll","TakeScreenshot","p","i") + takescreen.call(tempPath) + end + bm = nil + if safeExists?(tempPath) + bm = Bitmap.new(tempPath) + begin + File.delete(tempPath) + @@deletefailed = false + rescue Errno::EACCES + @@deletefailed = true + end + end + bm.asOpaque if bm && bm.get_pixel(0,0).alpha==0 + if resize + if bm && $ResizeOffsetX && $ResizeOffsetY && ($ResizeOffsetX!=0 || $ResizeOffsetY!=0) + tmpbitmap = Bitmap.new(Graphics.width*$ResizeFactor,Graphics.height*$ResizeFactor) + tmpbitmap.blt(0,0,bm,Rect.new( + $ResizeOffsetX*$ResizeFactor,$ResizeOffsetY*$ResizeFactor,tmpbitmap.width,tmpbitmap.height)) + bm.dispose + bm = tmpbitmap + end + if bm && (bm.width!=Graphics.width || bm.height!=Graphics.height) + newbitmap = Bitmap.new(Graphics.width,Graphics.height) + newbitmap.stretch_blt(newbitmap.rect,bm,Rect.new(0,0,bm.width,bm.height)) + bm.dispose + bm = newbitmap + end + else + # Thise code is used only for taking screenshots with F8. + # Doesn't crop out the screen border, doesn't normalise to 1x zoom. + # Fixes screenshots being 1 pixel too tall. + fullw = (Graphics.width+$ResizeOffsetX*2)*$ResizeFactor + fullh = (Graphics.height+$ResizeOffsetY*2)*$ResizeFactor + if bm && $ResizeOffsetX && $ResizeOffsetY && $ResizeFactor && + (bm.width!=fullw || bm.height!=fullh) + tmpbitmap = Bitmap.new(fullw,fullh) + tmpbitmap.blt(0,0,bm,Rect.new(0,0,fullw,fullh)) + bm.dispose + bm = tmpbitmap + end + end + return bm + end +end + + + +class Sprite + unless @SpriteResizerMethodsAliased + alias _initialize_SpriteResizer initialize + alias _x_SpriteResizer x + alias _y_SpriteResizer y + alias _ox_SpriteResizer ox + alias _oy_SpriteResizer oy + alias _zoomx_SpriteResizer zoom_x + alias _zoomy_SpriteResizer zoom_y + alias _xeq_SpriteResizer x= + alias _yeq_SpriteResizer y= + alias _oxeq_SpriteResizer ox= + alias _oyeq_SpriteResizer oy= + alias _zoomxeq_SpriteResizer zoom_x= + alias _zoomyeq_SpriteResizer zoom_y= + alias _bushdeptheq_SpriteResizer bush_depth= + @SpriteResizerMethodsAliased = true + end + + def initialize(viewport=nil) + _initialize_SpriteResizer(viewport) + @resizedX=0 + @resizedY=0 + @resizedOx=0 + @resizedOy=0 + @resizedBushDepth=0 + @resizedZoomX=1.0 + @resizedZoomY=1.0 + if $ResizeOffsetX!=0 && $ResizeOffsetY!=0 && !viewport + _xeq_SpriteResizer($ResizeOffsetX.to_f*$ResizeFactorMul/100) + _yeq_SpriteResizer($ResizeOffsetY.to_f*$ResizeFactorMul/100) + end + _zoomxeq_SpriteResizer(@resizedZoomX*$ResizeFactorMul/100) + _zoomyeq_SpriteResizer(@resizedZoomY*$ResizeFactorMul/100) + end + + def x + return @resizedX + end + + def y + return @resizedY + end + + def x=(val) + if $ResizeFactorMul!=100 + offset=(self.viewport) ? 0 : $ResizeOffsetX + value=(val+offset).to_f*$ResizeFactorMul/100 + _xeq_SpriteResizer(value.to_i) + @resizedX=val.to_i + elsif self.viewport + _xeq_SpriteResizer(val) + @resizedX=val + else + _xeq_SpriteResizer(val + $ResizeOffsetX) + @resizedX=val + end + end + + def y=(val) + if $ResizeFactorMul!=100 + offset=(self.viewport) ? 0 : $ResizeOffsetY + value=(val+offset).to_f*$ResizeFactorMul/100 + _yeq_SpriteResizer(value.to_i) + @resizedY=val.to_i + elsif self.viewport + _yeq_SpriteResizer(val) + @resizedY=val + else + _yeq_SpriteResizer(val + $ResizeOffsetY) + @resizedY=val + end + end + + def ox + return @resizedOx + end + + def oy + return @resizedOy + end + + def ox=(val) + @resizedOx=val + _oxeq_SpriteResizer(val) + end + + def oy=(val) + @resizedOy=val + _oyeq_SpriteResizer(val) + end + + def zoom_x + return @resizedZoomX + end + + def zoom_y + return @resizedZoomY + end + + def zoom_x=(val) + value=val + if $ResizeFactorMul!=100 + value=(val.to_f*$ResizeFactorMul/100) + if (value-0.5).abs<=0.001 + value=0.5 + elsif (value-1.0).abs<=0.001 + value=1.0 + elsif (value-1.5).abs<=0.001 + value=1.5 + elsif (value-2.0).abs<=0.001 + value=2.0 + end + end + _zoomxeq_SpriteResizer(value) + @resizedZoomX=val + end + + def zoom_y=(val) + value=val + if $ResizeFactorMul!=100 + value=(val.to_f*$ResizeFactorMul/100) + if (value-0.5).abs<=0.001 + value=0.5 + elsif (value-1.0).abs<=0.001 + value=1.0 + elsif (value-1.5).abs<=0.001 + value=1.5 + elsif (value-2.0).abs<=0.001 + value=2.0 + end + end + _zoomyeq_SpriteResizer(value) + @resizedZoomY=val + end + + def bush_depth + return @resizedBushDepth + end + + def bush_depth=(val) + value=((val.to_i)*$ResizeFactorMul/100) + _bushdeptheq_SpriteResizer(value.to_i) + @resizedBushDepth=val.to_i + end +end + + + +class NotifiableRect < Rect + def setNotifyProc(proc) + @notifyProc = proc + end + + def set(x,y,width,height) + super + @notifyProc.call(self) if @notifyProc + end + + def x=(value) + super + @notifyProc.call(self) if @notifyProc + end + + def y=(value) + super + @notifyProc.call(self) if @notifyProc + end + + def width=(value) + super + @notifyProc.call(self) if @notifyProc + end + + def height=(value) + super + @notifyProc.call(self) if @notifyProc + end +end + + + +class Viewport + unless @SpriteResizerMethodsAliased + alias _initialize_SpriteResizer initialize + alias _rect_ViewportResizer rect + alias _recteq_SpriteResizer rect= + alias _oxeq_SpriteResizer ox= + alias _oyeq_SpriteResizer oy= + @SpriteResizerMethodsAliased=true + end + + def initialize(*arg) + args=arg.clone + @oldrect=Rect.new(0,0,100,100) + _initialize_SpriteResizer(@oldrect) + newRect=NotifiableRect.new(0,0,0,0) + @resizedRectProc=Proc.new { |r| + if $ResizeFactorMul==100 + @oldrect.set( + r.x.to_i+$ResizeOffsetX, + r.y.to_i+$ResizeOffsetY, + r.width.to_i, + r.height.to_i + ) + self._recteq_SpriteResizer(@oldrect) + else + @oldrect.set( + ((r.x+$ResizeOffsetX)*$ResizeFactorMul/100).to_i, + ((r.y+$ResizeOffsetY)*$ResizeFactorMul/100).to_i, + (r.width*$ResizeFactorMul/100).to_i, + (r.height*$ResizeFactorMul/100).to_i + ) + self._recteq_SpriteResizer(@oldrect) + end + } + newRect.setNotifyProc(@resizedRectProc) + if arg.length==1 + newRect.set(args[0].x,args[0].y,args[0].width,args[0].height) + else + newRect.set(args[0],args[1],args[2],args[3]) + end + @resizedRect=newRect + @resizedOx=0 + @resizedOy=0 + end + + def ox + return @resizedOx + end + + def ox=(val) + return if !val + _oxeq_SpriteResizer(val.to_f*$ResizeFactorMul/100) + @resizedOx=val + end + + def oy + return @resizedOy + end + + def oy=(val) + return if !val + _oyeq_SpriteResizer(val.to_f*$ResizeFactorMul/100) + @resizedOy=val + end + + def rect + return @resizedRect + end + + def rect=(val) + if val + newRect=NotifiableRect.new(0,0,100,100) + newRect.setNotifyProc(@resizedRectProc) + newRect.set(val.x.to_i,val.y.to_i,val.width.to_i,val.height.to_i) + @resizedRect=newRect + end + end +end + + + +class Plane + unless @SpriteResizerMethodsAliased + alias _initialize_SpriteResizer initialize + alias _zoomxeq_SpriteResizer zoom_x= + alias _zoomyeq_SpriteResizer zoom_y= + alias _oxeq_SpriteResizer ox= + alias _oyeq_SpriteResizer oy= + @SpriteResizerMethodsAliased=true + end + + def initialize(viewport=nil) + _initialize_SpriteResizer(viewport) + @resizedZoomX=1.0 + @resizedZoomY=1.0 + @resizedOx=0 + @resizedOy=0 + _zoomxeq_SpriteResizer(@resizedZoomX*$ResizeFactorMul/100) + _zoomyeq_SpriteResizer(@resizedZoomY*$ResizeFactorMul/100) + end + + def ox + return @resizedOx + end + + def ox=(val) + return if !val + _oxeq_SpriteResizer(val.to_f*$ResizeFactorMul/100) + @resizedOx=val + end + + def oy + return @resizedOy + end + + def oy=(val) + return if !val + _oyeq_SpriteResizer(val.to_f*$ResizeFactorMul/100) + @resizedOy=val + end + + def zoom_x + return @resizedZoomX + end + + def zoom_x=(val) + return if !val + _zoomxeq_SpriteResizer(val*$ResizeFactorMul/100) + @resizedZoomX=val + end + + def zoom_y + return @resizedZoomY + end + + def zoom_y=(val) + return if !val + _zoomyeq_SpriteResizer(val*$ResizeFactorMul/100) + @resizedZoomY=val + end +end + + + + +class ScreenBorder + def initialize + initializeInternal + refresh + end + + def initializeInternal + @maximumZ=500000 + @bordername="" + @sprite=IconSprite.new(0,0) rescue Sprite.new + @defaultwidth=640 + @defaultheight=480 + @defaultbitmap=Bitmap.new(@defaultwidth,@defaultheight) + end + + def dispose + @borderbitmap.dispose if @borderbitmap + @defaultbitmap.dispose + @sprite.dispose + end + + def adjustZ(z) + if z>=@maximumZ + @maximumZ=z+1 + @sprite.z=@maximumZ + end + end + + def bordername=(value) + @bordername=value + refresh + end + + def refresh + @sprite.z=@maximumZ + @sprite.x=-BORDER_WIDTH + @sprite.y=-BORDER_HEIGHT + @sprite.visible=($PokemonSystem && $PokemonSystem.border==1) + @sprite.bitmap=nil + if @sprite.visible + if @bordername!=nil && @bordername!="" + setSpriteBitmap("Graphics/Pictures/"+@bordername) + else + setSpriteBitmap(nil) + @sprite.bitmap=@defaultbitmap + end + end + @defaultbitmap.clear + @defaultbitmap.fill_rect(0,0,@defaultwidth,$ResizeOffsetY,Color.new(0,0,0)) + @defaultbitmap.fill_rect(0,$ResizeOffsetY, + $ResizeOffsetX,@defaultheight-$ResizeOffsetY,Color.new(0,0,0)) + @defaultbitmap.fill_rect(@defaultwidth-$ResizeOffsetX,$ResizeOffsetY, + $ResizeOffsetX,@defaultheight-$ResizeOffsetY,Color.new(0,0,0)) + @defaultbitmap.fill_rect($ResizeOffsetX,@defaultheight-$ResizeOffsetY, + @defaultwidth-$ResizeOffsetX*2,$ResizeOffsetY,Color.new(0,0,0)) + end + + private + + def setSpriteBitmap(x) + if (@sprite.is_a?(IconSprite) rescue false) + @sprite.setBitmap(x) + else + @sprite.bitmap=x ? RPG::Cache.load_bitmap("",x) : nil + end + end +end + + + +class Bitmap + # Fast methods for retrieving bitmap data + RtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') + RtlMoveMemory_ip = Win32API.new('kernel32', 'RtlMoveMemory', 'ipi', 'i') + SwapRgb = Win32API.new('./rubyscreen.dll', 'SwapRgb', 'pi', '') rescue nil + + def setData(x) + RtlMoveMemory_ip.call(self.address, x, x.length) + end + + def getData + data = "rgba" * width * height + RtlMoveMemory_pi.call(data, self.address, data.length) + return data + end + + def swap32(x) + return ((x>>24)&0x000000FF)| + ((x>>8)&0x0000FF00)| + ((x<<8)&0x00FF0000)| + ((x<<24)&0xFF000000) + end + + def asOpaque + data=getData + j=3 + for i in 0...width*height + data[j]=0xFF + j+=4 + end + setData(data) + end + + def saveToPng(filename) + bytes=[ + 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A,0x00,0x00,0x00,0x0D + ].pack("CCCCCCCCCCCC") + ihdr=[ + 0x49,0x48,0x44,0x52,swap32(self.width),swap32(self.height), + 0x08,0x06,0x00,0x00,0x00 + ].pack("CCCCVVCCCCC") + crc=Zlib::crc32(ihdr) + ihdr+=[swap32(crc)].pack("V") + bytesPerScan=self.width*4 + row=(self.height-1)*bytesPerScan + data=self.getData + data2=data.clone + width=self.width + x="" + len=bytesPerScan*self.height + ttt=Time.now + if SwapRgb + SwapRgb.call(data2,data2.length) + else + # the following is considerably slower + b=0;c=2;while b!=len + data2[b]=data[c] + data2[c]=data[b] + b+=4;c+=4; + end + end + #$times.push(Time.now-ttt) + filter="\0" + while row>=0 + thisRow=data2[row,bytesPerScan] + x.concat(filter) + x.concat(thisRow) + row-=bytesPerScan + end + x=Zlib::Deflate.deflate(x) + length=x.length + x="IDAT"+x + crc=Zlib::crc32(x) + idat=[swap32(length)].pack("V") + idat.concat(x) + idat.concat([swap32(crc)].pack("V")) + idat.concat([0,0x49,0x45,0x4E,0x44,0xAE,0x42,0x60,0x82].pack("VCCCCCCCC")) + File.open(filename,"wb") { |f| + f.write(bytes) + f.write(ihdr) + f.write(idat) + } + end + + def address + if !@address + buffer, ad = "rgba", object_id * 2 + 16 + RtlMoveMemory_pi.call(buffer, ad, 4) + ad = buffer.unpack("L")[0] + 8 + RtlMoveMemory_pi.call(buffer, ad, 4) + ad = buffer.unpack("L")[0] + 16 + RtlMoveMemory_pi.call(buffer, ad, 4) + @address=buffer.unpack("L")[0] + end + return @address + end +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/008_Plugin_Manager.rb b/Data/Scripts/001_Technical/008_Plugin_Manager.rb new file mode 100644 index 000000000..a8e4a2278 --- /dev/null +++ b/Data/Scripts/001_Technical/008_Plugin_Manager.rb @@ -0,0 +1,376 @@ +#==============================================================================# +# Plugin Manager # +# by Marin # +#------------------------------------------------------------------------------# +# Provides a simple interface that allows plugins to require dependencies # +# at specific versions, and to specify incompatibilities between plugins. # +#------------------------------------------------------------------------------# +# Usage: # +# # +# A Pokémon Essentials plugin should register itself using the PluginManager. # +# The simplest way to do so, for a plugin without dependencies, is as follows: # +# # +# PluginManager.register({ # +# :name => "Basic Plugin", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin" # +# }) # +# # +# The link portion here is optional, but recommended. This will be shown in # +# the error message if the PluginManager detects that this plugin needs to be # +# updated. # +# # +# A plugin's version is typically in the format X.Y.Z, but the number of # +# digits does not matter. You can also use Xa, Xb, Xc, Ya, etc. # +# What matters is that you use it consistently, so that it can be compared. # +# # +# # +# # +# Now let's say we create a new plugin titled "Simple Extension", which # +# requires our previously created "Basic Plugin" to work. # +# # +# PluginManager.register({ # +# :name => "Simple Extension", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => ["Marin", "Maruno"], # +# :dependencies => ["Basic Plugin"] # +# }) # +# # +# This plugin has two credits as an array, instead of one string. Furthermore, # +# this code will ensure that "Basic Plugin" is installed, ignoring its # +# version. If you have only one dependency, you can omit the array brackets # +# like so: # +# # +# :dependencies => "Basic Plugin" # +# # +# # +# # +# To require a minimum version of a dependency plugin, you should turn the # +# dependency's name into an array which contains the name and the version # +# (both as strings). For example, to require "Basic Plugin" version 1.2 or # +# higher, you would write: # +# # +# PluginManager.register({ # +# :name => "Simple Extension", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin", # +# :dependencies => [ # +# ["Basic Plugin", "1.2"] # +# ] # +# }) # +# # +# # +# # +# To require a specific version (no higher and no lower) of a dependency # +# plugin, you should add the :exact flag as the first thing in the array for # +# that dependency: # +# # +# PluginManager.register({ # +# :name => "Simple Extension", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin", # +# :dependencies => [ # +# [:exact, "Basic Plugin", "1.2"] # +# ] # +# }) # +# # +# # +# # +# If your plugin is known to be incompatible with another plugin, you should # +# list that other plugin as such. Only one of the two plugins needs to list # +# that it is incompatible with the other. # +# # +# PluginManager.register({ # +# :name => "QoL Improvements", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin", # +# :incompatibilities => [ # +# "Simple Extension" # +# ] # +# }) # +# # +# # +# # +# If your plugin can work without another plugin, but is known to be # +# incompatible with an old version of that other plugin, you should list it as # +# an optional dependency. If that other plugin is present in a game, then this # +# optional dependency will ensure it meets the minimum version required for # +# your plugin. Write it in the same way as any other dependency as described # +# above, but use the :optional flag instead. # +# You do not need to list a plugin as an optional dependency at all if all # +# versions of that other plugin are compatible with your plugin. # +# # +# PluginManager.register({ # +# :name => "Other Plugin", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin", # +# :dependencies => [ # +# [:optional, "QoL Improvements", "1.1"] # +# ] # +# }) # +# # +# The :optional_exact flag is a combination of :optional and :exact. # +#------------------------------------------------------------------------------# +# Please give credit when using this. # +#==============================================================================# + +module PluginManager + # Win32API MessageBox function for custom errors. + MBOX = Win32API.new('user32', 'MessageBox', ['I','P','P','I'], 'I') + # Holds all registered plugin data. + @@Plugins = {} + + # Registers a plugin and tests its dependencies and incompatibilities. + def self.register(options) + name = nil + version = nil + link = nil + dependencies = nil + incompats = nil + credits = [] + order = [:name, :version, :link, :dependencies, :incompatibilities, :credits] + # Ensure it first reads the plugin's name, which is used in error reporting, + # by sorting the keys + keys = options.keys.sort do |a, b| + idx_a = order.index(a) + idx_a = order.size if idx_a == -1 + idx_b = order.index(b) + idx_b = order.size if idx_b == -1 + next idx_a <=> idx_b + end + for key in keys + value = options[key] + case key + when :name # Plugin name + if nil_or_empty?(value) + self.error("Plugin name must be a non-empty string.") + end + if !@@Plugins[value].nil? + self.error("A plugin called '#{value}' already exists.") + end + name = value + when :version # Plugin version + if nil_or_empty?(value) + self.error("Plugin version must be a string.") + end + version = value + when :link # Plugin website + if nil_or_empty?(value) + self.error("Plugin link must be a non-empty string.") + end + link = value + when :dependencies # Plugin dependencies + dependencies = value + dependencies = [dependencies] if !dependencies.is_a?(Array) || !dependencies[0].is_a?(Array) + for dep in value + if dep.is_a?(String) # "plugin name" + if !self.installed?(dep) + self.error("Plugin '#{name}' requires plugin '#{dep}' to be installed above it.") + end + elsif dep.is_a?(Array) + case dep.size + when 1 # ["plugin name"] + if dep[0].is_a?(String) + dep_name = dep[0] + if !self.installed?(dep_name) + self.error("Plugin '#{name}' requires plugin '#{dep_name}' to be installed above it.") + end + else + self.error("Expected the plugin name as a string, but got #{dep[0].inspect}.") + end + when 2 # ["plugin name", "version"] + if dep[0].is_a?(Symbol) + self.error("A plugin version comparator symbol was given but no version was given.") + elsif dep[0].is_a?(String) && dep[1].is_a?(String) + dep_name = dep[0] + dep_version = dep[1] + next if self.installed?(dep_name, dep_version) + if self.installed?(dep_name) # Have plugin but lower version + msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} or higher, " + + "but the installed version is #{self.version(dep_name)}." + if dep_link = self.link(dep_name) + msg += "\r\nCheck #{dep_link} for an update to plugin '#{dep_name}'." + end + self.error(msg) + else # Don't have plugin + self.error("Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} " + + "or higher to be installed above it.") + end + end + when 3 # [:optional/:exact/:optional_exact, "plugin name", "version"] + if !dep[0].is_a?(Symbol) + self.error("Expected first dependency argument to be a symbol, but got #{dep[0].inspect}.") + end + if !dep[1].is_a?(String) + self.error("Expected second dependency argument to be a plugin name, but got #{dep[1].inspect}.") + end + if !dep[2].is_a?(String) + self.error("Expected third dependency argument to be the plugin version, but got #{dep[2].inspect}.") + end + dep_arg = dep[0] + dep_name = dep[1] + dep_version = dep[2] + optional = false + exact = false + case def_arg + when :optional; optional = true + when :exact; exact = true + when :optional_exact; optional = true; exact = true + else + self.error("Expected first dependency argument to be one of " + + ":optional, :exact or :optional_exact, but got #{dep_arg.inspect}.") + end + if optional + if self.installed?(dep_name) && # Have plugin but lower version + !self.installed?(dep_name, dep_version, exact) + msg = "Plugin '#{name}' requires plugin '#{dep_name}', if installed, to be version #{dep_version}" + msg << " or higher" if !exact + msg << ", but the installed version was #{self.version(dep_name)}." + if dep_link = self.link(dep_name) + msg << "\r\nCheck #{dep_link} for an update to plugin '#{dep_name}'." + end + self.error(msg) + end + elsif !self.installed?(dep_name, dep_version, exact) + if self.installed?(dep_name) # Have plugin but lower version + msg = "Plugin '#{name}' requires plugin '#{dep_name}' to be version #{dep_version}" + msg << " or later" if !exact + msg << ", but the installed version was #{self.version(dep_name)}." + if dep_link = self.link(dep_name) + msg << "\r\nCheck #{dep_link} for an update to plugin '#{dep_name}'." + end + self.error(msg) + else # Don't have plugin + msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} " + msg << "or later" if !exact + msg << "to be installed above it." + self.error(msg) + end + end + end + end + end + when :incompatibilities # Plugin incompatibilities + incompats = value + incompats = [incompats] if !incompats.is_a?(Array) + for incompat in incompats + if self.installed?(incompat) + self.error("Plugin '#{name}' is incompatible with '#{incompat}'. " + + "They cannot both be used at the same time.") + end + end + when :credits # Plugin credits + value = [value] if value.is_a?(String) + if value.is_a?(Array) + for entry in value + if !entry.is_a?(String) + self.error("Plugin '#{name}'s credits array contains a non-string value.") + else + credits << entry + end + end + else + self.error("Plugin '#{name}'s credits field must contain a string, or a string array.") + end + else + self.error("Invalid plugin registry key '#{key}'.") + end + end + for plugin in @@Plugins.values + if plugin[:incompatibilities] && plugin[:incompatibilities].include?(name) + self.error("Plugin '#{plugin[:name]}' is incompatible with '#{name}'. " + + "They cannot both be used at the same time.") + end + end + # Add plugin to class variable + @@Plugins[name] = { + :name => name, + :version => version, + :link => link, + :dependencies => dependencies, + :incompatibilities => incompats, + :credits => credits + } + end + + # Throws a pure error message without stack trace or any other useless info. + def self.error(msg) + Graphics.update + t = Thread.new do + MBOX.call(Win32API.pbFindRgssWindow, msg, "Plugin Error", 0x10) + Thread.exit + end + while t.status + Graphics.update + end + Kernel.exit! true + end + + # Returns true if the specified plugin is installed. + # If the version is specified, this version is taken into account. + # If mustequal is true, the version must be a match with the specified version. + def self.installed?(plugin_name, plugin_version = nil, mustequal = false) + plugin = @@Plugins[plugin_name] + return false if plugin.nil? + return true if plugin_version.nil? + comparison = compare_versions(plugin[:version], plugin_version) + return true if !mustequal && comparison >= 0 + return true if mustequal && comparison == 0 + end + + # Returns the string names of all installed plugins. + def self.plugins + return @@Plugins.keys + end + + # Returns the installed version of the specified plugin. + def self.version(plugin_name) + return if !installed?(plugin_name) + return @@Plugins[plugin_name][:version] + end + + # Returns the link of the specified plugin. + def self.link(plugin_name) + return if !installed?(plugin_name) + return @@Plugins[plugin_name][:link] + end + + # Returns the credits of the specified plugin. + def self.credits(plugin_name) + return if !installed?(plugin_name) + return @@Plugins[plugin_name][:credits] + end + + # Compares two versions given in string form. v1 should be the plugin version + # you actually have, and v2 should be the minimum/desired plugin version. + # Return values: + # 1 if v1 is higher than v2 + # 0 if v1 is equal to v2 + # -1 if v1 is lower than v2 + def self.compare_versions(v1, v2) + d1 = v1.split("") + d1.insert(0, "0") if d1[0] == "." # Turn ".123" into "0.123" + while d1[-1] == "."; d1 = d1[0..-2]; end # Turn "123." into "123" + d2 = v2.split("") + d2.insert(0, "0") if d2[0] == "." # Turn ".123" into "0.123" + while d2[-1] == "."; d2 = d2[0..-2]; end # Turn "123." into "123" + for i in 0...[d1.size, d2.size].max # Compare each digit in turn + c1 = d1[i] + c2 = d2[i] + if c1 + return 1 if !c2 + return 1 if c1.to_i(16) > c2.to_i(16) + return -1 if c1.to_i(16) < c2.to_i(16) + else + return -1 if c2 + end + end + return 0 + end +end \ No newline at end of file diff --git a/Data/Scripts/002_Switches and Variables/001_Game_Temp.rb b/Data/Scripts/002_Switches and Variables/001_Game_Temp.rb new file mode 100644 index 000000000..727043c2b --- /dev/null +++ b/Data/Scripts/002_Switches and Variables/001_Game_Temp.rb @@ -0,0 +1,104 @@ +#=============================================================================== +# ** Game_Temp +#------------------------------------------------------------------------------- +# This class handles temporary data that is not included with save data. +# Refer to "$game_temp" for the instance of this class. +#=============================================================================== +class Game_Temp + attr_accessor :map_bgm # map music (for battle memory) + attr_accessor :message_text # message text + attr_accessor :message_proc # message callback (Proc) + attr_accessor :choice_start # show choices: opening line + attr_accessor :choice_max # show choices: number of items + attr_accessor :choice_cancel_type # show choices: cancel + attr_accessor :choice_proc # show choices: callback (Proc) + attr_accessor :num_input_start # input number: opening line + attr_accessor :num_input_variable_id # input number: variable ID + attr_accessor :num_input_digits_max # input number: digit amount + attr_accessor :message_window_showing # message window showing + attr_accessor :common_event_id # common event ID + attr_accessor :in_battle # in-battle flag + attr_accessor :battle_calling # battle calling flag + attr_accessor :battle_troop_id # battle troop ID + attr_accessor :battle_can_escape # battle flag: escape possible + attr_accessor :battle_can_lose # battle flag: losing possible + attr_accessor :battle_proc # battle callback (Proc) + attr_accessor :battle_turn # number of battle turns + attr_accessor :battle_event_flags # battle event flags: completed + attr_accessor :battle_abort # battle flag: interrupt + attr_accessor :battle_main_phase # battle flag: main phase + attr_accessor :battleback_name # battleback file name + attr_accessor :forcing_battler # battler being forced into action + attr_accessor :shop_calling # shop calling flag + attr_accessor :shop_goods # list of shop goods + attr_accessor :name_calling # name input: calling flag + attr_accessor :name_actor_id # name input: actor ID + attr_accessor :name_max_char # name input: max character count + attr_accessor :menu_calling # menu calling flag + attr_accessor :menu_beep # menu: play sound effect flag + attr_accessor :in_menu # menu is open + attr_accessor :save_calling # save calling flag + attr_accessor :debug_calling # debug calling flag + attr_accessor :player_transferring # player place movement flag + attr_accessor :player_new_map_id # player destination: map ID + attr_accessor :player_new_x # player destination: x-coordinate + attr_accessor :player_new_y # player destination: y-coordinate + attr_accessor :player_new_direction # player destination: direction + attr_accessor :transition_processing # transition processing flag + attr_accessor :transition_name # transition file name + attr_accessor :gameover # game over flag + attr_accessor :to_title # return to title screen flag + attr_accessor :last_file_index # last save file no. + attr_accessor :map_refresh # map needs redrawing + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @map_bgm = nil + @message_text = nil + @message_proc = nil + @choice_start = 99 + @choice_max = 0 + @choice_cancel_type = 0 + @choice_proc = nil + @num_input_start = 99 + @num_input_variable_id = 0 + @num_input_digits_max = 0 + @message_window_showing = false + @common_event_id = 0 + @in_battle = false + @battle_calling = false + @battle_troop_id = 0 + @battle_can_escape = false + @battle_can_lose = false + @battle_proc = nil + @battle_turn = 0 + @battle_event_flags = {} + @battle_abort = false + @battle_main_phase = false + @battleback_name = '' + @forcing_battler = nil + @shop_calling = false + @shop_id = 0 + @name_calling = false + @name_actor_id = 0 + @name_max_char = 0 + @menu_calling = false + @menu_beep = false + @in_menu = false + @save_calling = false + @debug_calling = false + @player_transferring = false + @player_new_map_id = 0 + @player_new_x = 0 + @player_new_y = 0 + @player_new_direction = 0 + @transition_processing = false + @transition_name = "" + @gameover = false + @to_title = false + @last_file_index = 0 + @debug_top_row = 0 + @debug_index = 0 + end +end diff --git a/Data/Scripts/002_Switches and Variables/002_Game_Switches.rb b/Data/Scripts/002_Switches and Variables/002_Game_Switches.rb new file mode 100644 index 000000000..29b66871e --- /dev/null +++ b/Data/Scripts/002_Switches and Variables/002_Game_Switches.rb @@ -0,0 +1,36 @@ +#=============================================================================== +# ** Game_Switches +#------------------------------------------------------------------------------- +# This class handles switches. It's a wrapper for the built-in class "Array." +# Refer to "$game_switches" for the instance of this class. +#=============================================================================== + +class Game_Switches + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @data = [] + end + #----------------------------------------------------------------------------- + # * Get Switch + # switch_id : switch ID + #----------------------------------------------------------------------------- + def [](switch_id) + if switch_id<=5000 and @data[switch_id]!=nil + return @data[switch_id] + else + return false + end + end + #----------------------------------------------------------------------------- + # * Set Switch + # switch_id : switch ID + # value : ON (true) / OFF (false) + #----------------------------------------------------------------------------- + def []=(switch_id, value) + if switch_id<=5000 + @data[switch_id] = value + end + end +end \ No newline at end of file diff --git a/Data/Scripts/002_Switches and Variables/003_Game_Variables.rb b/Data/Scripts/002_Switches and Variables/003_Game_Variables.rb new file mode 100644 index 000000000..0ae70171c --- /dev/null +++ b/Data/Scripts/002_Switches and Variables/003_Game_Variables.rb @@ -0,0 +1,36 @@ +#=============================================================================== +# ** Game_Variables +#------------------------------------------------------------------------------- +# This class handles variables. It's a wrapper for the built-in class "Array." +# Refer to "$game_variables" for the instance of this class. +#=============================================================================== + +class Game_Variables + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @data = [] + end + #----------------------------------------------------------------------------- + # * Get Variable + # variable_id : variable ID + #----------------------------------------------------------------------------- + def [](variable_id) + if variable_id<=5000 and @data[variable_id]!=nil + return @data[variable_id] + else + return 0 + end + end + #----------------------------------------------------------------------------- + # * Set Variable + # variable_id : variable ID + # value : the variable's value + #----------------------------------------------------------------------------- + def []=(variable_id, value) + if variable_id<=5000 + @data[variable_id] = value + end + end +end \ No newline at end of file diff --git a/Data/Scripts/002_Switches and Variables/004_Game_SelfSwitches.rb b/Data/Scripts/002_Switches and Variables/004_Game_SelfSwitches.rb new file mode 100644 index 000000000..a65708df7 --- /dev/null +++ b/Data/Scripts/002_Switches and Variables/004_Game_SelfSwitches.rb @@ -0,0 +1,30 @@ +#=============================================================================== +# ** Game_SelfSwitches +#------------------------------------------------------------------------------- +# This class handles self switches. It's a wrapper for the built-in class +# "Hash." Refer to "$game_self_switches" for the instance of this class. +#=============================================================================== + +class Game_SelfSwitches + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @data = {} + end + #----------------------------------------------------------------------------- + # * Get Self Switch + # key : key + #----------------------------------------------------------------------------- + def [](key) + return (@data[key]==true) ? true : false + end + #----------------------------------------------------------------------------- + # * Set Self Switch + # key : key + # value : ON (true) / OFF (false) + #----------------------------------------------------------------------------- + def []=(key, value) + @data[key] = value + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/001_Game_Screen.rb b/Data/Scripts/003_Game classes/001_Game_Screen.rb new file mode 100644 index 000000000..6c659c6e9 --- /dev/null +++ b/Data/Scripts/003_Game classes/001_Game_Screen.rb @@ -0,0 +1,156 @@ +#=============================================================================== +# ** Game_Screen +#------------------------------------------------------------------------------- +# This class handles screen maintenance data, such as change in color tone, +# flashing, etc. Refer to "$game_screen" for the instance of this class. +#=============================================================================== + +class Game_Screen + #----------------------------------------------------------------------------- + # * Public Instance Variables + #----------------------------------------------------------------------------- + attr_reader :brightness # brightness + attr_reader :tone # color tone + attr_reader :flash_color # flash color + attr_reader :shake # shake positioning + attr_reader :pictures # pictures + attr_reader :weather_type # weather type + attr_reader :weather_max # max number of weather sprites + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @brightness = 255 + @fadeout_duration = 0 + @fadein_duration = 0 + @tone = Tone.new(0, 0, 0, 0) + @tone_target = Tone.new(0, 0, 0, 0) + @tone_duration = 0 + @flash_color = Color.new(0, 0, 0, 0) + @flash_duration = 0 + @shake_power = 0 + @shake_speed = 0 + @shake_duration = 0 + @shake_direction = 1 + @shake = 0 + @pictures = [nil] + for i in 1..100 + @pictures.push(Game_Picture.new(i)) + end + @weather_type = 0 + @weather_max = 0.0 + @weather_type_target = 0 + @weather_max_target = 0.0 + @weather_duration = 0 + end + #----------------------------------------------------------------------------- + # * Start Changing Color Tone + # tone : color tone + # duration : time + #----------------------------------------------------------------------------- + def start_tone_change(tone, duration) + @tone_target = tone.clone + @tone_duration = duration + if @tone_duration == 0 + @tone = @tone_target.clone + end + end + #----------------------------------------------------------------------------- + # * Start Flashing + # color : color + # duration : time + #----------------------------------------------------------------------------- + def start_flash(color, duration) + @flash_color = color.clone + @flash_duration = duration + end + #----------------------------------------------------------------------------- + # * Start Shaking + # power : strength + # speed : speed + # duration : time + #----------------------------------------------------------------------------- + def start_shake(power, speed, duration) + @shake_power = power + @shake_speed = speed + @shake_duration = duration + end + #----------------------------------------------------------------------------- + # * Set Weather + # type : type + # power : strength + # duration : time + #----------------------------------------------------------------------------- + def weather(type, power, duration) + @weather_type_target = type + if @weather_type_target!=0 + @weather_type = @weather_type_target + end + if @weather_type_target==0 + @weather_max_target = 0.0 + else + @weather_max_target = (power + 1) * 4.0 + end + @weather_duration = duration + if @weather_duration==0 + @weather_type = @weather_type_target + @weather_max = @weather_max_target + end + end + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + if @fadeout_duration && @fadeout_duration>=1 + d = @fadeout_duration + @brightness = (@brightness*(d-1))/d + @fadeout_duration -= 1 + end + if @fadein_duration && @fadein_duration>=1 + d = @fadein_duration + @brightness = (@brightness*(d-1)+255)/d + @fadein_duration -= 1 + end + if @tone_duration>=1 + d = @tone_duration + @tone.red = (@tone.red*(d-1)+@tone_target.red)/d + @tone.green = (@tone.green*(d-1)+@tone_target.green)/d + @tone.blue = (@tone.blue*(d-1)+@tone_target.blue)/d + @tone.gray = (@tone.gray*(d-1)+@tone_target.gray)/d + @tone_duration -= 1 + end + if @flash_duration>=1 + d = @flash_duration + @flash_color.alpha = @flash_color.alpha*(d-1)/d + @flash_duration -= 1 + end + if @shake_duration>=1 or @shake!=0 + delta = (@shake_power*@shake_speed*@shake_direction)/10.0 + if @shake_duration<=1 and @shake*(@shake+delta)<0 + @shake = 0 + else + @shake += delta + end + @shake_direction = -1 if @shake>@shake_power*2 + @shake_direction = 1 if @shake<-@shake_power*2 + @shake_duration -= 1 if @shake_duration>=1 + end + if @weather_duration>=1 + d = @weather_duration + @weather_max = (@weather_max*(d-1)+@weather_max_target)/d + @weather_duration -= 1 + if @weather_duration==0 + @weather_type = @weather_type_target + end + end + if $game_temp.in_battle + for i in 51..100 + @pictures[i].update + end + else + for i in 1..50 + @pictures[i].update + end + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/002_Game_System.rb b/Data/Scripts/003_Game classes/002_Game_System.rb new file mode 100644 index 000000000..8637bdba4 --- /dev/null +++ b/Data/Scripts/003_Game classes/002_Game_System.rb @@ -0,0 +1,289 @@ +#============================================================================== +# ** Game_System +#------------------------------------------------------------------------------ +# This class handles data surrounding the system. Backround music, etc. +# is managed here as well. Refer to "$game_system" for the instance of +# this class. +#============================================================================== + +class Game_System + attr_reader :map_interpreter # map event interpreter + attr_reader :battle_interpreter # battle event interpreter + attr_accessor :timer # timer + attr_accessor :timer_working # timer working flag + attr_accessor :save_disabled # save forbidden + attr_accessor :menu_disabled # menu forbidden + attr_accessor :encounter_disabled # encounter forbidden + attr_accessor :message_position # text option: positioning + attr_accessor :message_frame # text option: window frame + attr_accessor :save_count # save count + attr_accessor :magic_number # magic number + attr_accessor :autoscroll_x_speed + attr_accessor :autoscroll_y_speed + attr_accessor :bgm_position + + def initialize + if $RPGVX + @map_interpreter = Game_Interpreter.new(0,true) + @battle_interpreter = Game_Interpreter.new(0,false) + else + @map_interpreter = Interpreter.new(0,true) + @battle_interpreter = Interpreter.new(0,false) + end + @timer = 0 + @timer_working = false + @save_disabled = false + @menu_disabled = false + @encounter_disabled = false + @message_position = 2 + @message_frame = 0 + @save_count = 0 + @magic_number = 0 + @autoscroll_x_speed = 0 + @autoscroll_y_speed = 0 + @bgm_position = 0 + @bgs_position = 0 + end + +################################################################################ + + def bgm_play(bgm) + bgm_play_internal(bgm,0) + end + + def bgm_play_internal2(name,volume,pitch,position) # :nodoc: + vol = volume + vol *= $PokemonSystem.bgmvolume/100.0 + vol = vol.to_i + begin + Audio.bgm_play(name,vol,pitch,position) + rescue ArgumentError + Audio.bgm_play(name,vol,pitch) + end + end + + def bgm_play_internal(bgm,position) # :nodoc: + @bgm_position = position if !@bgm_paused + @playing_bgm = (bgm==nil) ? nil : bgm.clone + if bgm!=nil and bgm.name!="" + if FileTest.audio_exist?("Audio/BGM/"+bgm.name) + bgm_play_internal2("Audio/BGM/"+bgm.name, + bgm.volume,bgm.pitch,@bgm_position) if !@defaultBGM + end + else + @bgm_position = position if !@bgm_paused + @playing_bgm = nil + Audio.bgm_stop if !@defaultBGM + end + if @defaultBGM + bgm_play_internal2("Audio/BGM/"+@defaultBGM.name, + @defaultBGM.volume,@defaultBGM.pitch,@bgm_position) + end + Graphics.frame_reset + end + + def bgm_pause(fadetime=0.0) # :nodoc: + pos = Audio.bgm_position rescue 0 + self.bgm_fade(fadetime) if fadetime>0.0 + @bgm_position = pos + @bgm_paused = true + end + + def bgm_unpause # :nodoc: + @bgm_position = 0 + @bgm_paused = false + end + + def bgm_resume(bgm) # :nodoc: + if @bgm_paused + self.bgm_play_internal(bgm,@bgm_position) + @bgm_position = 0 + @bgm_paused = false + end + end + + def bgm_stop # :nodoc: + @bgm_position = 0 if !@bgm_paused + @playing_bgm = nil + Audio.bgm_stop if !@defaultBGM + end + + def bgm_fade(time) # :nodoc: + @bgm_position = 0 if !@bgm_paused + @playing_bgm = nil + Audio.bgm_fade((time*1000).floor) if !@defaultBGM + end + + def playing_bgm + return @playing_bgm + end + + # Saves the currently playing background music for later playback. + def bgm_memorize + @memorized_bgm = @playing_bgm + end + + # Plays the currently memorized background music + def bgm_restore + bgm_play(@memorized_bgm) + end + + # Returns an RPG::AudioFile object for the currently playing background music + def getPlayingBGM + return (@playing_bgm) ? @playing_bgm.clone : nil + end + + def setDefaultBGM(bgm,volume=80,pitch=100) + bgm = RPG::AudioFile.new(bgm,volume,pitch) if bgm.is_a?(String) + if bgm!=nil and bgm.name!="" + @defaultBGM = nil + self.bgm_play(bgm) + @defaultBGM = bgm.clone + else + @defaultBGM = nil + self.bgm_play(@playing_bgm) + end + end + +################################################################################ + + def me_play(me) + me = RPG::AudioFile.new(me) if me.is_a?(String) + if me!=nil and me.name!="" + if FileTest.audio_exist?("Audio/ME/"+me.name) + vol = me.volume + vol *= $PokemonSystem.bgmvolume/100.0 + vol = vol.to_i + Audio.me_play("Audio/ME/"+me.name,vol,me.pitch) + end + else + Audio.me_stop + end + Graphics.frame_reset + end + +################################################################################ + + def bgs_play(bgs) + @playing_bgs = (bgs==nil) ? nil : bgs.clone + if bgs!=nil and bgs.name!="" + if FileTest.audio_exist?("Audio/BGS/"+bgs.name) + vol = bgs.volume + vol *= $PokemonSystem.sevolume/100.0 + vol = vol.to_i + Audio.bgs_play("Audio/BGS/"+bgs.name,vol,bgs.pitch) + end + else + @bgs_position = 0 + @playing_bgs = nil + Audio.bgs_stop + end + Graphics.frame_reset + end + + def bgs_pause(fadetime=0.0) # :nodoc: + if fadetime>0.0 + self.bgs_fade(fadetime) + else + self.bgs_stop + end + @bgs_paused = true + end + + def bgs_unpause # :nodoc: + @bgs_paused = false + end + + def bgs_resume(bgs) # :nodoc: + if @bgs_paused + self.bgs_play(bgs) + @bgs_paused = false + end + end + + def bgs_stop + @bgs_position = 0 + @playing_bgs = nil + Audio.bgs_stop + end + + def bgs_fade(time) + @bgs_position = 0 + @playing_bgs = nil + Audio.bgs_fade((time*1000).floor) + end + + def playing_bgs + return @playing_bgs + end + + def bgs_memorize + @memorized_bgs = @playing_bgs + end + + def bgs_restore + bgs_play(@memorized_bgs) + end + + def getPlayingBGS + return (@playing_bgs) ? @playing_bgs.clone : nil + end + +################################################################################ + + def se_play(se) + se = RPG::AudioFile.new(se) if se.is_a?(String) + if se!=nil and se.name!="" && FileTest.audio_exist?("Audio/SE/"+se.name) + vol = se.volume + vol *= $PokemonSystem.sevolume/100.0 + vol = vol.to_i + Audio.se_play("Audio/SE/"+se.name,vol,se.pitch) + end + end + + def se_stop + Audio.se_stop + end + +################################################################################ + + def battle_bgm + return (@battle_bgm) ? @battle_bgm : $data_system.battle_bgm + end + + def battle_bgm=(battle_bgm) + @battle_bgm = battle_bgm + end + + def battle_end_me + return (@battle_end_me) ? @battle_end_me : $data_system.battle_end_me + end + + def battle_end_me=(battle_end_me) + @battle_end_me = battle_end_me + end + +################################################################################ + + def windowskin_name + if @windowskin_name==nil + return $data_system.windowskin_name + else + return @windowskin_name + end + end + + def windowskin_name=(windowskin_name) + @windowskin_name = windowskin_name + end + + def update + @timer -= 1 if @timer_working and @timer>0 + if Input.trigger?(Input::F5) && pbCurrentEventCommentInput(1,"Cut Scene") + event = @map_interpreter.get_character(0) + @map_interpreter.pbSetSelfSwitch(event.id,"A",true) + @map_interpreter.command_end + event.start + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/003_Game_Picture.rb b/Data/Scripts/003_Game classes/003_Game_Picture.rb new file mode 100644 index 000000000..6e30c3d13 --- /dev/null +++ b/Data/Scripts/003_Game classes/003_Game_Picture.rb @@ -0,0 +1,156 @@ +#=============================================================================== +# ** Game_Picture +#------------------------------------------------------------------------------- +# This class handles the picture. It's used within the Game_Screen class +# ($game_screen). +#=============================================================================== + +class Game_Picture + #----------------------------------------------------------------------------- + # * Public Instance Variables + #----------------------------------------------------------------------------- + attr_reader :number # picture number + attr_reader :name # file name + attr_reader :origin # starting point + attr_reader :x # x-coordinate + attr_reader :y # y-coordinate + attr_reader :zoom_x # x directional zoom rate + attr_reader :zoom_y # y directional zoom rate + attr_reader :opacity # opacity level + attr_reader :blend_type # blend method + attr_reader :tone # color tone + attr_reader :angle # rotation angle + #----------------------------------------------------------------------------- + # * Object Initialization + # number : picture number + #----------------------------------------------------------------------------- + def initialize(number) + @number = number + @name = "" + @origin = 0 + @x = 0.0 + @y = 0.0 + @zoom_x = 100.0 + @zoom_y = 100.0 + @opacity = 255.0 + @blend_type = 1 + @duration = 0 + @target_x = @x + @target_y = @y + @target_zoom_x = @zoom_x + @target_zoom_y = @zoom_y + @target_opacity = @opacity + @tone = Tone.new(0, 0, 0, 0) + @tone_target = Tone.new(0, 0, 0, 0) + @tone_duration = 0 + @angle = 0 + @rotate_speed = 0 + end + #----------------------------------------------------------------------------- + # * Show Picture + # name : file name + # origin : starting point + # x : x-coordinate + # y : y-coordinate + # zoom_x : x directional zoom rate + # zoom_y : y directional zoom rate + # opacity : opacity level + # blend_type : blend method + #----------------------------------------------------------------------------- + def show(name, origin, x, y, zoom_x, zoom_y, opacity, blend_type) + @name = name + @origin = origin + @x = x.to_f + @y = y.to_f + @zoom_x = zoom_x.to_f + @zoom_y = zoom_y.to_f + @opacity = opacity.to_f + @blend_type = blend_type ? blend_type : 0 + @duration = 0 + @target_x = @x + @target_y = @y + @target_zoom_x = @zoom_x + @target_zoom_y = @zoom_y + @target_opacity = @opacity + @tone = Tone.new(0, 0, 0, 0) + @tone_target = Tone.new(0, 0, 0, 0) + @tone_duration = 0 + @angle = 0 + @rotate_speed = 0 + end + #----------------------------------------------------------------------------- + # * Move Picture + # duration : time + # origin : starting point + # x : x-coordinate + # y : y-coordinate + # zoom_x : x directional zoom rate + # zoom_y : y directional zoom rate + # opacity : opacity level + # blend_type : blend method + #----------------------------------------------------------------------------- + def move(duration, origin, x, y, zoom_x, zoom_y, opacity, blend_type) + @duration = duration + @origin = origin + @target_x = x.to_f + @target_y = y.to_f + @target_zoom_x = zoom_x.to_f + @target_zoom_y = zoom_y.to_f + @target_opacity = opacity.to_f + @blend_type = blend_type ? blend_type : 0 + end + #----------------------------------------------------------------------------- + # * Change Rotation Speed + # speed : rotation speed + #----------------------------------------------------------------------------- + def rotate(speed) + @rotate_speed = speed + end + #----------------------------------------------------------------------------- + # * Start Change of Color Tone + # tone : color tone + # duration : time + #----------------------------------------------------------------------------- + def start_tone_change(tone, duration) + @tone_target = tone.clone + @tone_duration = duration + if @tone_duration == 0 + @tone = @tone_target.clone + end + end + #----------------------------------------------------------------------------- + # * Erase Picture + #----------------------------------------------------------------------------- + def erase + @name = "" + end + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + if @duration >= 1 + d = @duration + @x = (@x * (d - 1) + @target_x) / d + @y = (@y * (d - 1) + @target_y) / d + @zoom_x = (@zoom_x * (d - 1) + @target_zoom_x) / d + @zoom_y = (@zoom_y * (d - 1) + @target_zoom_y) / d + @opacity = (@opacity * (d - 1) + @target_opacity) / d + @duration -= 1 + end + if @tone_duration >= 1 + d = @tone_duration + @tone.red = (@tone.red * (d - 1) + @tone_target.red) / d + @tone.green = (@tone.green * (d - 1) + @tone_target.green) / d + @tone.blue = (@tone.blue * (d - 1) + @tone_target.blue) / d + @tone.gray = (@tone.gray * (d - 1) + @tone_target.gray) / d + @tone_duration -= 1 + end + if @rotate_speed != 0 + @angle += @rotate_speed / 2.0 + while @angle < 0 + @angle += 360 + end + @angle %= 360 + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/004_Game_CommonEvent.rb b/Data/Scripts/003_Game classes/004_Game_CommonEvent.rb new file mode 100644 index 000000000..978291c46 --- /dev/null +++ b/Data/Scripts/003_Game classes/004_Game_CommonEvent.rb @@ -0,0 +1,81 @@ +#=============================================================================== +# ** Game_CommonEvent +#------------------------------------------------------------------------------- +# This class handles common events. It includes execution of parallel process +# event. This class is used within the Game_Map class ($game_map). +#=============================================================================== +class Game_CommonEvent + #----------------------------------------------------------------------------- + # * Object Initialization + # common_event_id : common event ID + #----------------------------------------------------------------------------- + def initialize(common_event_id) + @common_event_id = common_event_id + @interpreter = nil + refresh + end + #----------------------------------------------------------------------------- + # * Get Name + #----------------------------------------------------------------------------- + def name + return $data_common_events[@common_event_id].name + end + #----------------------------------------------------------------------------- + # * Get Trigger + #----------------------------------------------------------------------------- + def trigger + return $data_common_events[@common_event_id].trigger + end + #----------------------------------------------------------------------------- + # * Get Condition Switch ID + #----------------------------------------------------------------------------- + def switch_id + return $data_common_events[@common_event_id].switch_id + end + #----------------------------------------------------------------------------- + # * Get List of Event Commands + #----------------------------------------------------------------------------- + def list + return $data_common_events[@common_event_id].list + end + #----------------------------------------------------------------------------- + # * Checks if switch is on + #----------------------------------------------------------------------------- + def switchIsOn?(id) + switchName = $data_system.switches[id] + return false if !switchName + if switchName[/^s\:/] + return eval($~.post_match) + else + return $game_switches[id] + end + end + #----------------------------------------------------------------------------- + # * Refresh + #----------------------------------------------------------------------------- + def refresh + # Create an interpreter for parallel process if necessary + if self.trigger == 2 and switchIsOn?(self.switch_id) + if @interpreter == nil + @interpreter = Interpreter.new + end + else + @interpreter = nil + end + end + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + # If parallel process is valid + if @interpreter != nil + # If not running + unless @interpreter.running? + # Set up event + @interpreter.setup(self.list, 0) + end + # Update interpreter + @interpreter.update + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/005_Game_Character.rb b/Data/Scripts/003_Game classes/005_Game_Character.rb new file mode 100644 index 000000000..1ec45ca10 --- /dev/null +++ b/Data/Scripts/003_Game classes/005_Game_Character.rb @@ -0,0 +1,869 @@ +class Game_Character + attr_reader :id + attr_reader :original_x + attr_reader :original_y + attr_reader :x + attr_reader :y + attr_reader :real_x + attr_reader :real_y + attr_accessor :sprite_size + attr_reader :tile_id + attr_accessor :character_name + attr_accessor :character_hue + attr_reader :opacity + attr_reader :blend_type + attr_reader :direction + attr_accessor :pattern + attr_reader :pattern_surf + attr_accessor :lock_pattern + attr_reader :move_route_forcing + attr_accessor :through + attr_accessor :animation_id + attr_accessor :transparent + attr_reader :move_speed + attr_accessor :walk_anime + attr_accessor :bob_height + + def initialize(map=nil) + @map = map + @id = 0 + @original_x = 0 + @original_y = 0 + @x = 0 + @y = 0 + @real_x = 0 + @real_y = 0 + @sprite_size = [Game_Map::TILE_WIDTH,Game_Map::TILE_HEIGHT] + @tile_id = 0 + @character_name = "" + @character_hue = 0 + @opacity = 255 + @blend_type = 0 + @direction = 2 + @pattern = 0 + @pattern_surf = 0 + @lock_pattern = false + @move_route_forcing = false + @through = false + @animation_id = 0 + @transparent = false + @original_direction = 2 + @original_pattern = 0 + @move_type = 0 + self.move_speed = 4 + self.move_frequency = 6 + @move_route = nil + @move_route_index = 0 + @original_move_route = nil + @original_move_route_index = 0 + @walk_anime = true # Whether character should animate while moving + @step_anime = false # Whether character should animate while still + @direction_fix = false + @always_on_top = false + @anime_count = 0 + @stop_count = 0 + @jump_count = 0 + @jump_peak = 0 + @bob_height = 0 + @wait_count = 0 + @moved_this_frame = false + @locked = false + @prelock_direction = 0 + end + + def move_speed=(val) + return if val==@move_speed + @move_speed = val + # @move_speed_real is the number of quarter-pixels to move each frame. There + # are 128 quarter-pixels per tile. By default, it is calculated from + # @move_speed and has these values (assuming 40 fps): + # 1 => 1.6 # 80 frames per tile + # 2 => 3.2 # 40 frames per tile + # 3 => 6.4 # 20 frames per tile + # 4 => 12.8 # 10 frames per tile - walking speed + # 5 => 25.6 # 5 frames per tile - running speed (2x walking speed) + # 6 => 32 # 4 frames per tile - cycling speed (1.25x running speed) + self.move_speed_real = (val == 6) ? 32 : (1 << val) * 0.8 + end + + def move_speed_real + self.move_speed = @move_speed if !@move_speed_real + return @move_speed_real + end + + def move_speed_real=(val) + @move_speed_real = val * 40.0 / Graphics.frame_rate + end + + def move_frequency=(val) + return if val==@move_frequency + @move_frequency = val + # @move_frequency_real is the number of frames to wait between each action + # in a move route (not forced). Specifically, this is the number of frames + # to wait after the character stops moving because of the previous action. + # By default, it is calculated from @move_frequency and has these values + # (assuming 40 fps): + # 1 => 190 # 4.75 seconds + # 2 => 144 # 3.6 seconds + # 3 => 102 # 2.55 seconds + # 4 => 64 # 1.6 seconds + # 5 => 30 # 0.75 seconds + # 6 => 0 # 0 seconds, i.e. continuous movement + self.move_frequency_real = (40 - val * 2) * (6 - val) + end + + def move_frequency_real + self.move_frequency = @move_frequency if !@move_frequency_real + return @move_frequency_real + end + + def move_frequency_real=(val) + @move_frequency_real = val * Graphics.frame_rate / 40.0 + end + + def bob_height + @bob_height = 0 if !@bob_height + return @bob_height + end + + def lock + return if @locked + @prelock_direction = 0 # Was @direction but disabled + turn_toward_player + @locked = true + end + + def minilock + @prelock_direction = 0 # Was @direction but disabled + @locked = true + end + + def lock? + return @locked + end + + def unlock + return unless @locked + @locked = false + @direction = @prelock_direction if !@direction_fix and @prelock_direction != 0 + end + + #============================================================================= + # Information from map data + #============================================================================= + def map + return (@map) ? @map : $game_map + end + + def terrain_tag + return self.map.terrain_tag(@x, @y) + end + + def bush_depth + return 0 if @tile_id > 0 or @always_on_top or @jump_count > 0 + xbehind = @x + (@direction==4 ? 1 : @direction==6 ? -1 : 0) + ybehind = @y + (@direction==8 ? 1 : @direction==2 ? -1 : 0) + return Game_Map::TILE_HEIGHT if self.map.deepBush?(@x, @y) and self.map.deepBush?(xbehind, ybehind) + return 12 if !moving? and self.map.bush?(@x, @y) + return 0 + end + + #============================================================================= + # Passability + #============================================================================= + def passableEx?(x, y, d, strict=false) + new_x = x + (d == 6 ? 1 : d == 4 ? -1 : 0) + new_y = y + (d == 2 ? 1 : d == 8 ? -1 : 0) + return false unless self.map.valid?(new_x, new_y) + return true if @through + if strict + return false unless self.map.passableStrict?(x, y, d, self) + return false unless self.map.passableStrict?(new_x, new_y, 10 - d, self) + else + return false unless self.map.passable?(x, y, d, self) + return false unless self.map.passable?(new_x, new_y, 10 - d, self) + end + for event in self.map.events.values + next if event.x != new_x || event.y != new_y || event.through + return false if self != $game_player || event.character_name != "" + end + if $game_player.x == new_x and $game_player.y == new_y + return false if !$game_player.through && @character_name != "" + end + return true + end + + def passable?(x,y,d) + return passableEx?(x,y,d,false) + end + + def passableStrict?(x,y,d) + return passableEx?(x,y,d,true) + end + + #============================================================================= + # Screen position of the character + #============================================================================= + def screen_x + ret = ((@real_x - self.map.display_x) / Game_Map::X_SUBPIXELS).round + ret += Game_Map::TILE_WIDTH/2 + return ret + end + + def screen_y_ground + ret = ((@real_y - self.map.display_y) / Game_Map::Y_SUBPIXELS).round + ret += Game_Map::TILE_HEIGHT + return ret + end + + def screen_y + ret = screen_y_ground + if jumping? + n = ((2 * @jump_count * 20 / Graphics.frame_rate) - @jump_peak).abs + return ret - (@jump_peak * @jump_peak - n * n) / 2 + end + return ret + end + + def screen_z(height = 0) + return 999 if @always_on_top + z = screen_y_ground + if @tile_id > 0 + begin + return z + self.map.priorities[@tile_id] * 32 + rescue + raise "Event's graphic is an out-of-range tile (event #{@id}, map #{self.map.map_id})" + end + end + # Add z if height exceeds 32 + return z + ((height > Game_Map::TILE_HEIGHT) ? Game_Map::TILE_HEIGHT - 1 : 0) + end + + #============================================================================= + # Movement + #============================================================================= + def moving? + return @real_x != @x * Game_Map::REAL_RES_X || + @real_y != @y * Game_Map::REAL_RES_Y + end + + def jumping? + return @jump_count > 0 + end + + def straighten + @pattern = 0 if @walk_anime or @step_anime + @anime_count = 0 + @prelock_direction = 0 + end + + def force_move_route(move_route) + if @original_move_route == nil + @original_move_route = @move_route + @original_move_route_index = @move_route_index + end + @move_route = move_route + @move_route_index = 0 + @move_route_forcing = true + @prelock_direction = 0 + @wait_count = 0 + move_type_custom + end + + def moveto(x, y) + @x = x % self.map.width + @y = y % self.map.height + @real_x = @x * Game_Map::REAL_RES_X + @real_y = @y * Game_Map::REAL_RES_Y + @prelock_direction = 0 + triggerLeaveTile + end + + def triggerLeaveTile + if @oldX && @oldY && @oldMap && + (@oldX!=self.x || @oldY!=self.y || @oldMap!=self.map.map_id) + Events.onLeaveTile.trigger(self,self,@oldMap,@oldX,@oldY) + end + @oldX = self.x + @oldY = self.y + @oldMap = self.map.map_id + end + + def increase_steps + @stop_count = 0 + triggerLeaveTile + end + + #============================================================================= + # Movement commands + #============================================================================= + def move_type_random + case rand(6) + when 0..3; move_random + when 4; move_forward + when 5; @stop_count = 0 + end + end + + def move_type_toward_player + sx = @x - $game_player.x + sy = @y - $game_player.y + if sx.abs + sy.abs >= 20 + move_random + return + end + case rand(6) + when 0..3; move_toward_player + when 4; move_random + when 5; move_forward + end + end + + def move_type_custom + return if jumping? or moving? + while @move_route_index < @move_route.list.size + command = @move_route.list[@move_route_index] + if command.code == 0 + if @move_route.repeat + @move_route_index = 0 + else + if @move_route_forcing + @move_route_forcing = false + @move_route = @original_move_route + @move_route_index = @original_move_route_index + @original_move_route = nil + end + @stop_count = 0 + end + return + end + if command.code <= 14 + case command.code + when 1; move_down + when 2; move_left + when 3; move_right + when 4; move_up + when 5; move_lower_left + when 6; move_lower_right + when 7; move_upper_left + when 8; move_upper_right + when 9; move_random + when 10; move_toward_player + when 11; move_away_from_player + when 12; move_forward + when 13; move_backward + when 14; jump(command.parameters[0], command.parameters[1]) + end + @move_route_index += 1 if @move_route.skippable or moving? or jumping? + return + end + if command.code == 15 # Wait + @wait_count = (command.parameters[0] * Graphics.frame_rate / 20) - 1 + @move_route_index += 1 + return + end + if command.code >= 16 and command.code <= 26 + case command.code + when 16; turn_down + when 17; turn_left + when 18; turn_right + when 19; turn_up + when 20; turn_right_90 + when 21; turn_left_90 + when 22; turn_180 + when 23; turn_right_or_left_90 + when 24; turn_random + when 25; turn_toward_player + when 26; turn_away_from_player + end + @move_route_index += 1 + return + end + if command.code >= 27 + case command.code + when 27 + $game_switches[command.parameters[0]] = true + self.map.need_refresh = true + when 28 + $game_switches[command.parameters[0]] = false + self.map.need_refresh = true + when 29; self.move_speed = command.parameters[0] + when 30; self.move_frequency = command.parameters[0] + when 31; @walk_anime = true + when 32; @walk_anime = false + when 33; @step_anime = true + when 34; @step_anime = false + when 35; @direction_fix = true + when 36; @direction_fix = false + when 37; @through = true + when 38; @through = false + when 39; @always_on_top = true + when 40; @always_on_top = false + when 41 + @tile_id = 0 + @character_name = command.parameters[0] + @character_hue = command.parameters[1] + if @original_direction != command.parameters[2] + @direction = command.parameters[2] + @original_direction = @direction + @prelock_direction = 0 + end + if @original_pattern != command.parameters[3] + @pattern = command.parameters[3] + @original_pattern = @pattern + end + when 42; @opacity = command.parameters[0] + when 43; @blend_type = command.parameters[0] + when 44; pbSEPlay(command.parameters[0]) + when 45; result = eval(command.parameters[0]) + end + @move_route_index += 1 + end + end + end + + def move_up(turn_enabled = true) + turn_up if turn_enabled + if passable?(@x, @y, 8) + turn_up + @y -= 1 + increase_steps + else + check_event_trigger_touch(@x, @y-1) + end + end + + def move_down(turn_enabled = true) + turn_down if turn_enabled + if passable?(@x, @y, 2) + turn_down + @y += 1 + increase_steps + else + check_event_trigger_touch(@x, @y+1) + end + end + + def move_left(turn_enabled = true) + turn_left if turn_enabled + if passable?(@x, @y, 4) + turn_left + @x -= 1 + increase_steps + else + check_event_trigger_touch(@x-1, @y) + end + end + + def move_right(turn_enabled = true) + turn_right if turn_enabled + if passable?(@x, @y, 6) + turn_right + @x += 1 + increase_steps + else + check_event_trigger_touch(@x+1, @y) + end + end + + def move_upper_left + unless @direction_fix + @direction = (@direction == 6 ? 4 : @direction == 2 ? 8 : @direction) + end + if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 4)) or + (passable?(@x, @y, 4) and passable?(@x - 1, @y, 8)) + @x -= 1 + @y -= 1 + increase_steps + end + end + + def move_upper_right + unless @direction_fix + @direction = (@direction == 4 ? 6 : @direction == 2 ? 8 : @direction) + end + if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 6)) or + (passable?(@x, @y, 6) and passable?(@x + 1, @y, 8)) + @x += 1 + @y -= 1 + increase_steps + end + end + + def move_lower_left + unless @direction_fix + @direction = (@direction == 6 ? 4 : @direction == 8 ? 2 : @direction) + end + if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 4)) or + (passable?(@x, @y, 4) and passable?(@x - 1, @y, 2)) + @x -= 1 + @y += 1 + increase_steps + end + end + + def move_lower_right + unless @direction_fix + @direction = (@direction == 4 ? 6 : @direction == 8 ? 2 : @direction) + end + if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 6)) or + (passable?(@x, @y, 6) and passable?(@x + 1, @y, 2)) + @x += 1 + @y += 1 + increase_steps + end + end + + def moveLeft90 # anticlockwise + case self.direction + when 2; move_right # down + when 4; move_down # left + when 6; move_up # right + when 8; move_left # up + end + end + + def moveRight90 # clockwise + case self.direction + when 2; move_left # down + when 4; move_up # left + when 6; move_down # right + when 8; move_right # up + end + end + + def move_random + case rand(4) + when 0; move_down(false) + when 1; move_left(false) + when 2; move_right(false) + when 3; move_up(false) + end + end + + def move_random_range(xrange=-1,yrange=-1) + dirs = [] # 0=down, 1=left, 2=right, 3=up + if xrange<0 + dirs.push(1); dirs.push(2) + elsif xrange>0 + dirs.push(1) if @x > @original_x - xrange + dirs.push(2) if @x < @original_x + xrange + end + if yrange<0 + dirs.push(0); dirs.push(3) + elsif yrange>0 + dirs.push(0) if @y < @original_y + yrange + dirs.push(3) if @y > @original_y - yrange + end + return if dirs.length==0 + case dirs[rand(dirs.length)] + when 0; move_down(false) + when 1; move_left(false) + when 2; move_right(false) + when 3; move_up(false) + end + end + + def move_random_UD(range=-1) + move_random_range(0,range) + end + + def move_random_LR(range=-1) + move_random_range(range,0) + end + + def move_toward_player + sx = @x - $game_player.x + sy = @y - $game_player.y + return if sx == 0 and sy == 0 + abs_sx = sx.abs + abs_sy = sy.abs + if abs_sx == abs_sy + (rand(2) == 0) ? abs_sx += 1 : abs_sy += 1 + end + if abs_sx > abs_sy + (sx > 0) ? move_left : move_right + if not moving? and sy != 0 + (sy > 0) ? move_up : move_down + end + else + (sy > 0) ? move_up : move_down + if not moving? and sx != 0 + (sx > 0) ? move_left : move_right + end + end + end + + def move_away_from_player + sx = @x - $game_player.x + sy = @y - $game_player.y + return if sx == 0 and sy == 0 + abs_sx = sx.abs + abs_sy = sy.abs + if abs_sx == abs_sy + (rand(2) == 0) ? abs_sx += 1 : abs_sy += 1 + end + if abs_sx > abs_sy + (sx > 0) ? move_right : move_left + if not moving? and sy != 0 + (sy > 0) ? move_down : move_up + end + else + (sy > 0) ? move_down : move_up + if not moving? and sx != 0 + (sx > 0) ? move_right : move_left + end + end + end + + def move_forward + case @direction + when 2; move_down(false) + when 4; move_left(false) + when 6; move_right(false) + when 8; move_up(false) + end + end + + def move_backward + last_direction_fix = @direction_fix + @direction_fix = true + case @direction + when 2; move_up(false) + when 4; move_right(false) + when 6; move_left(false) + when 8; move_down(false) + end + @direction_fix = last_direction_fix + end + + def jump(x_plus, y_plus) + if x_plus != 0 or y_plus != 0 + if x_plus.abs > y_plus.abs + (x_plus < 0) ? turn_left : turn_right + else + (y_plus < 0) ? turn_up : turn_down + end + end + new_x = @x + x_plus + new_y = @y + y_plus + if (x_plus == 0 and y_plus == 0) || passable?(new_x, new_y, 0) + straighten + @x = new_x + @y = new_y + distance = [4, x_plus * x_plus + y_plus * y_plus].max + @jump_peak = (6 + distance - move_speed).floor + @jump_count = @jump_peak * Graphics.frame_rate / 20 + @stop_count = 0 + if self.is_a?(Game_Player) + $PokemonTemp.dependentEvents.pbMoveDependentEvents + end + triggerLeaveTile + end + end + + def jumpForward + case self.direction + when 2; jump(0,1) # down + when 4; jump(-1,0) # left + when 6; jump(1,0) # right + when 8; jump(0,-1) # up + end + end + + def jumpBackward + case self.direction + when 2; jump(0,-1) # down + when 4; jump(1,0) # left + when 6; jump(-1,0) # right + when 8; jump(0,1) # up + end + end + + def turnGeneric(dir) + return if @direction_fix + oldDirection = @direction + @direction = dir + @stop_count = 0 + pbCheckEventTriggerAfterTurning if dir != oldDirection + end + + def turn_up; turnGeneric(8); end + def turn_down; turnGeneric(2); end + def turn_left; turnGeneric(4); end + def turn_right; turnGeneric(6); end + + def turn_right_90 + case @direction + when 2; turn_left + when 4; turn_up + when 6; turn_down + when 8; turn_right + end + end + + def turn_left_90 + case @direction + when 2; turn_right + when 4; turn_down + when 6; turn_up + when 8; turn_left + end + end + + def turn_180 + case @direction + when 2; turn_up + when 4; turn_right + when 6; turn_left + when 8; turn_down + end + end + + def turn_right_or_left_90 + (rand(2) == 0) ? turn_right_90 : turn_left_90 + end + + def turn_random + case rand(4) + when 0; turn_up + when 1; turn_right + when 2; turn_left + when 3; turn_down + end + end + + def turn_toward_player + sx = @x - $game_player.x + sy = @y - $game_player.y + return if sx == 0 and sy == 0 + if sx.abs > sy.abs + (sx > 0) ? turn_left : turn_right + else + (sy > 0) ? turn_up : turn_down + end + end + + def turn_away_from_player + sx = @x - $game_player.x + sy = @y - $game_player.y + return if sx == 0 and sy == 0 + if sx.abs > sy.abs + (sx > 0) ? turn_right : turn_left + else + (sy > 0) ? turn_down : turn_up + end + end + + #============================================================================= + # Updating + #============================================================================= + def update + @moved_last_frame = @moved_this_frame + if !$game_temp.in_menu + # Update command + update_command + # Update movement + if jumping?; update_jump + elsif moving?; update_move + else; update_stop + end + end + # Update animation + update_pattern + end + + def update_command + if @wait_count > 0 + @wait_count -= 1 + elsif @move_route_forcing + move_type_custom + elsif !@starting && !lock? && !moving? && !jumping? + update_command_new + end + end + + def update_command_new + # @stop_count is the number of frames since the last movement finished. + # @move_frequency has these values: + # 1 => @stop_count > 190 # 4.75 seconds + # 2 => @stop_count > 144 # 3.6 seconds + # 3 => @stop_count > 102 # 2.55 seconds + # 4 => @stop_count > 64 # 1.6 seconds + # 5 => @stop_count > 30 # 0.75 seconds + # 6 => @stop_count > 0 # 0 seconds + if @stop_count >= self.move_frequency_real + case @move_type + when 1; move_type_random + when 2; move_type_toward_player + when 3; move_type_custom + end + end + end + + def update_jump + @jump_count -= 1 + @real_x = (@real_x * @jump_count + @x * Game_Map::REAL_RES_X) / (@jump_count + 1) + @real_y = (@real_y * @jump_count + @y * Game_Map::REAL_RES_Y) / (@jump_count + 1) + @moved_this_frame = true + # End of a jump, so perform events that happen at this time + Events.onStepTakenFieldMovement.trigger(self,self) if !jumping? && !moving? + end + + def update_move + # Move the character (the 0.1 catches rounding errors) + distance = move_speed_real + dest_x = @x * Game_Map::REAL_RES_X + dest_y = @y * Game_Map::REAL_RES_Y + if @real_x < dest_x + @real_x += distance + @real_x = dest_x if @real_x > dest_x - 0.1 + else + @real_x -= distance + @real_x = dest_x if @real_x < dest_x + 0.1 + end + if @real_y < dest_y + @real_y += distance + @real_y = dest_y if @real_y > dest_y - 0.1 + else + @real_y -= distance + @real_y = dest_y if @real_y < dest_y + 0.1 + end + # End of a step, so perform events that happen at this time + Events.onStepTakenFieldMovement.trigger(self,self) if !jumping? && !moving? + # Increment animation counter + @anime_count += 1 if @walk_anime || @step_anime + @moved_this_frame = true + end + + def update_stop + @anime_count += 1 if @step_anime + @stop_count += 1 if !@starting && !lock? + @moved_this_frame = false + end + + def update_pattern + return if @lock_pattern + # Character has stopped moving, return to original pattern + if @moved_last_frame && !@moved_this_frame && !@step_anime + @pattern = @original_pattern + @anime_count = 0 + return + end + # Character has started to move, change pattern immediately + if !@moved_last_frame && @moved_this_frame && !jumping? && !@step_anime + @pattern = (@pattern + 1) % 4 if @walk_anime + @anime_count = 0 + return + end + # Calculate how many frames each pattern should display for, i.e. the time + # it takes to move half a tile (or a whole tile if cycling). We assume the + # game uses square tiles. + frames_per_pattern = Game_Map::REAL_RES_X / (move_speed_real * 2.0) + frames_per_pattern *= 2 if move_speed == 6 # Cycling/fastest speed + return if @anime_count < frames_per_pattern + # Advance to the next animation frame + @pattern = (@pattern + 1) % 4 + @anime_count -= frames_per_pattern + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/006_Game_Event.rb b/Data/Scripts/003_Game classes/006_Game_Event.rb new file mode 100644 index 000000000..52d8c9ad9 --- /dev/null +++ b/Data/Scripts/003_Game classes/006_Game_Event.rb @@ -0,0 +1,247 @@ +class Game_Event < Game_Character + attr_reader :map_id + attr_reader :trigger + attr_reader :list + attr_reader :starting + attr_reader :tempSwitches # Temporary self-switches + attr_accessor :need_refresh + + def initialize(map_id, event, map=nil) + super(map) + @map_id = map_id + @event = event + @id = @event.id + @original_x = @event.x + @original_y = @event.y + @erased = false + @starting = false + @need_refresh = false + @route_erased = false + @through = true + @to_update = true + @tempSwitches = {} + moveto(@event.x, @event.y) if map + refresh + end + + def id; return @event.id; end + def name; return @event.name; end + + def clear_starting + @starting = false + end + + def start + @starting = true if @list.size > 1 + end + + def erase + @erased = true + refresh + end + + def erase_route + @route_erased = true + refresh + end + + def tsOn?(c) + return @tempSwitches && @tempSwitches[c]==true + end + + def tsOff?(c) + return !@tempSwitches || !@tempSwitches[c] + end + + def setTempSwitchOn(c) + @tempSwitches[c]=true + refresh + end + + def setTempSwitchOff(c) + @tempSwitches[c]=false + refresh + end + + def isOff?(c) + return !$game_self_switches[[@map_id,@event.id,c]] + end + + def switchIsOn?(id) + switchname = $data_system.switches[id] + return false if !switchname + if switchname[/^s\:/] + return eval($~.post_match) + else + return $game_switches[id] + end + end + + def variable + return nil if !$PokemonGlobal.eventvars + return $PokemonGlobal.eventvars[[@map_id,@event.id]] + end + + def setVariable(variable) + $PokemonGlobal.eventvars[[@map_id,@event.id]]=variable + end + + def varAsInt + return 0 if !$PokemonGlobal.eventvars + return $PokemonGlobal.eventvars[[@map_id,@event.id]].to_i + end + + def expired?(secs=86400) + ontime=self.variable + time=pbGetTimeNow + return ontime && (time.to_i>ontime+secs) + end + + def expiredDays?(days=1) + ontime=self.variable.to_i + return false if !ontime + now=pbGetTimeNow + elapsed=(now.to_i-ontime)/86400 + elapsed+=1 if (now.to_i-ontime)%86400>(now.hour*3600+now.min*60+now.sec) + return elapsed>=days + end + + def onEvent? + return @map_id==$game_map.map_id && + $game_player.x==self.x && $game_player.y==self.y + end + + def over_trigger? + return false if @character_name!="" and not @through + return false if @event.name[/hiddenitem/i] + return false if !self.map.passable?(@x, @y, 0, $game_player) + return true + end + + def pbCheckEventTriggerAfterTurning + return if $game_system.map_interpreter.running? || @starting + if @event.name[/trainer\((\d+)\)/i] + distance = $~[1].to_i + if @trigger==2 && pbEventCanReachPlayer?(self,$game_player,distance) + start if !jumping? && !over_trigger? + end + end + end + + def check_event_trigger_touch(x, y) + return if $game_system.map_interpreter.running? + return if @trigger!=2 + return if x != $game_player.x || y != $game_player.y + return if jumping? || over_trigger? + start + end + + def check_event_trigger_auto + if @trigger == 2 # Event touch + if @x == $game_player.x and @y == $game_player.y + start if not jumping? and over_trigger? + end + elsif @trigger == 3 # Autorun + start + end + end + + def refresh + new_page = nil + unless @erased + for page in @event.pages.reverse + c = page.condition + next if c.switch1_valid && !switchIsOn?(c.switch1_id) + next if c.switch2_valid && !switchIsOn?(c.switch2_id) + next if c.variable_valid && $game_variables[c.variable_id] < c.variable_value + if c.self_switch_valid + key = [@map_id, @event.id, c.self_switch_ch] + next if $game_self_switches[key] != true + end + new_page = page + break + end + end + return if new_page == @page + @page = new_page + clear_starting + if @page == nil + @tile_id = 0 + @character_name = "" + @character_hue = 0 + @move_type = 0 + @through = true + @trigger = nil + @list = nil + @interpreter = nil + return + end + @tile_id = @page.graphic.tile_id + @character_name = @page.graphic.character_name + @character_hue = @page.graphic.character_hue + if @original_direction != @page.graphic.direction + @direction = @page.graphic.direction + @original_direction = @direction + @prelock_direction = 0 + end + if @original_pattern != @page.graphic.pattern + @pattern = @page.graphic.pattern + @original_pattern = @pattern + end + @opacity = @page.graphic.opacity + @blend_type = @page.graphic.blend_type + @move_type = @page.move_type + self.move_speed = @page.move_speed + self.move_frequency = @page.move_frequency + @move_route = (@route_erased) ? RPG::MoveRoute.new : @page.move_route + @move_route_index = 0 + @move_route_forcing = false + @walk_anime = @page.walk_anime + @step_anime = @page.step_anime + @direction_fix = @page.direction_fix + @through = @page.through + @always_on_top = @page.always_on_top + @trigger = @page.trigger + @list = @page.list + @interpreter = nil + if @trigger == 4 # Parallel Process + @interpreter = Interpreter.new + end + check_event_trigger_auto + end + + def should_update?(recalc=false) + return @to_update if !recalc + return true if $PokemonSystem.tilemap==2 + return true if @trigger && (@trigger == 3 || @trigger == 4) + return true if @move_route_forcing + return true if @event.name[/update/i] + range = 2 # Number of tiles + return false if self.screen_x - @sprite_size[0]/2 > Graphics.width + range * Game_Map::TILE_WIDTH + return false if self.screen_x + @sprite_size[0]/2 < -range * Game_Map::TILE_WIDTH + return false if self.screen_y_ground - @sprite_size[1] > Graphics.height + range * Game_Map::TILE_HEIGHT + return false if self.screen_y_ground < -range * Game_Map::TILE_HEIGHT + return true + end + + def update + @to_update = should_update?(true) + return if !@to_update + last_moving = moving? + super + if !moving? && last_moving + $game_player.pbCheckEventTriggerFromDistance([2]) + end + if @need_refresh + @need_refresh = false + refresh + end + check_event_trigger_auto + if @interpreter != nil + unless @interpreter.running? + @interpreter.setup(@list, @event.id, @map_id) + end + @interpreter.update + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/007_Game_Player.rb b/Data/Scripts/003_Game classes/007_Game_Player.rb new file mode 100644 index 000000000..e21709a56 --- /dev/null +++ b/Data/Scripts/003_Game classes/007_Game_Player.rb @@ -0,0 +1,505 @@ +#=============================================================================== +# ** Game_Player +#------------------------------------------------------------------------------- +# This class handles the player. Its functions include event starting +# determinants and map scrolling. Refer to "$game_player" for the one +# instance of this class. +#=============================================================================== +class Game_Player < Game_Character + attr_accessor :bump_se + attr_accessor :charsetData + attr_accessor :encounter_count + + def initialize(*arg) + super(*arg) + @lastdir=0 + @lastdirframe=0 + @bump_se=0 + end + + def map + @map = nil + return $game_map + end + + def bush_depth + return 0 if @tile_id > 0 or @always_on_top + xbehind = (@direction==4) ? @x+1 : (@direction==6) ? @x-1 : @x + ybehind = (@direction==8) ? @y+1 : (@direction==2) ? @y-1 : @y + # Both current tile and previous tile are on the same map; just return super + return super if $game_map.valid?(@x,@y) && $game_map.valid?(xbehind,ybehind) + # The current or the previous tile is on a different map; consult MapFactory + return 0 if !$MapFactory + # Get map and coordinates of the current tile + if $game_map.valid?(@x,@y) + heremap = self.map; herex = @x; herey = @y + else + newhere = $MapFactory.getNewMap(@x,@y) + return 0 unless newhere && newhere[0] # Map not found + heremap = newhere[0]; herex = newhere[1]; herey = newhere[2] + end + # Get map and coordinates of the previous tile + newbehind = $MapFactory.getNewMap(xbehind,ybehind) + if $game_map.valid?(xbehind,ybehind) + behindmap = self.map; behindx = xbehind; behindy = ybehind + else + return 0 unless newbehind && newbehind[0] # Map not found + behindmap = newbehind[0]; behindx = newbehind[1]; behindy = newbehind[2] + end + # Return bush depth + if @jump_count <= 0 + return 32 if heremap.deepBush?(herex, herey) && behindmap.deepBush?(behindx, behindy) + return 12 if heremap.bush?(herex, herey) && !moving? + end + return 0 + end + + def pbHasDependentEvents? + return $PokemonGlobal.dependentEvents.length>0 + end + + def bump_into_object + return if @bump_se && @bump_se>0 + pbSEPlay("Player bump") + @bump_se = Graphics.frame_rate/4 + end + + def move_down(turn_enabled = true) + turn_down if turn_enabled + if passable?(@x, @y, 2) + return if pbLedge(0,1) + return if pbEndSurf(0,1) + turn_down + @y += 1 + $PokemonTemp.dependentEvents.pbMoveDependentEvents + increase_steps + else + if !check_event_trigger_touch(@x, @y+1) + bump_into_object + end + end + end + + def move_left(turn_enabled = true) + turn_left if turn_enabled + if passable?(@x, @y, 4) + return if pbLedge(-1,0) + return if pbEndSurf(-1,0) + turn_left + @x -= 1 + $PokemonTemp.dependentEvents.pbMoveDependentEvents + increase_steps + else + if !check_event_trigger_touch(@x-1, @y) + bump_into_object + end + end + end + + def move_right(turn_enabled = true) + turn_right if turn_enabled + if passable?(@x, @y, 6) + return if pbLedge(1,0) + return if pbEndSurf(1,0) + turn_right + @x += 1 + $PokemonTemp.dependentEvents.pbMoveDependentEvents + increase_steps + else + if !check_event_trigger_touch(@x+1, @y) + bump_into_object + end + end + end + + def move_up(turn_enabled = true) + turn_up if turn_enabled + if passable?(@x, @y, 8) + return if pbLedge(0,-1) + return if pbEndSurf(0,-1) + turn_up + @y -= 1 + $PokemonTemp.dependentEvents.pbMoveDependentEvents + increase_steps + else + if !check_event_trigger_touch(@x, @y-1) + bump_into_object + end + end + end + + def pbTriggeredTrainerEvents(triggers,checkIfRunning=true) + result = [] + # If event is running + return result if checkIfRunning && $game_system.map_interpreter.running? + # All event loops + for event in $game_map.events.values + next if !event.name[/trainer\((\d+)\)/i] + distance = $~[1].to_i + # If event coordinates and triggers are consistent + if pbEventCanReachPlayer?(event,self,distance) and triggers.include?(event.trigger) + # If starting determinant is front event (other than jumping) + result.push(event) if not event.jumping? and not event.over_trigger? + end + end + return result + end + + def pbTriggeredCounterEvents(triggers,checkIfRunning=true) + result = [] + # If event is running + return result if checkIfRunning && $game_system.map_interpreter.running? + # All event loops + for event in $game_map.events.values + next if !event.name[/counter\((\d+)\)/i] + distance = $~[1].to_i + # If event coordinates and triggers are consistent + if pbEventFacesPlayer?(event,self,distance) and triggers.include?(event.trigger) + # If starting determinant is front event (other than jumping) + result.push(event) if not event.jumping? and not event.over_trigger? + end + end + return result + end + + def pbCheckEventTriggerAfterTurning + end + + def pbCheckEventTriggerFromDistance(triggers) + ret = pbTriggeredTrainerEvents(triggers) + ret.concat(pbTriggeredCounterEvents(triggers)) + return false if ret.length==0 + for event in ret + event.start + end + return true + end + + def pbFacingEvent(ignoreInterpreter=false) + return nil if $game_system.map_interpreter.running? && !ignoreInterpreter + new_x = @x + (@direction == 6 ? 1 : @direction == 4 ? -1 : 0) + new_y = @y + (@direction == 2 ? 1 : @direction == 8 ? -1 : 0) + return nil if !$game_map.valid?(new_x, new_y) + for event in $game_map.events.values + next if event.x != new_x || event.y != new_y + next if event.jumping? || event.over_trigger? + return event + end + if $game_map.counter?(new_x, new_y) + new_x += (@direction == 6 ? 1 : @direction == 4 ? -1 : 0) + new_y += (@direction == 2 ? 1 : @direction == 8 ? -1 : 0) + for event in $game_map.events.values + next if event.x != new_x || event.y != new_y + next if event.jumping? || event.over_trigger? + return event + end + end + return nil + end + + #----------------------------------------------------------------------------- + # * Passable Determinants + # x : x-coordinate + # y : y-coordinate + # d : direction (0,2,4,6,8) + # * 0 = Determines if all directions are impassable (for jumping) + #----------------------------------------------------------------------------- + def passable?(x, y, d) + # Get new coordinates + new_x = x + (d == 6 ? 1 : d == 4 ? -1 : 0) + new_y = y + (d == 2 ? 1 : d == 8 ? -1 : 0) + # If coordinates are outside of map + return false if !$game_map.validLax?(new_x, new_y) + if !$game_map.valid?(new_x, new_y) + return false if !$MapFactory + return $MapFactory.isPassableFromEdge?(new_x, new_y) + end + # If debug mode is ON and Ctrl key was pressed + return true if $DEBUG and Input.press?(Input::CTRL) + return super + end + + #----------------------------------------------------------------------------- + # * Set Map Display Position to Center of Screen + #----------------------------------------------------------------------------- + def center(x, y) + center_x = (Graphics.width/2 - Game_Map::TILE_WIDTH/2) * Game_Map::X_SUBPIXELS + center_y = (Graphics.height/2 - Game_Map::TILE_HEIGHT/2) * Game_Map::Y_SUBPIXELS + max_x = (self.map.width - Graphics.width*1.0/Game_Map::TILE_WIDTH) * Game_Map::REAL_RES_X + max_y = (self.map.height - Graphics.height*1.0/Game_Map::TILE_HEIGHT) * Game_Map::REAL_RES_Y + dispx = x * Game_Map::REAL_RES_X - center_x + dispy = y * Game_Map::REAL_RES_Y - center_y + self.map.display_x = dispx + self.map.display_y = dispy + end + + #----------------------------------------------------------------------------- + # * Move to Designated Position + # x : x-coordinate + # y : y-coordinate + #----------------------------------------------------------------------------- + def moveto(x, y) + super + # Centering + center(x, y) + # Make encounter count + make_encounter_count + end + + #----------------------------------------------------------------------------- + # * Make Encounter Count + #----------------------------------------------------------------------------- + def make_encounter_count + # Image of two dice rolling + if $game_map.map_id != 0 + n = $game_map.encounter_step + @encounter_count = rand(n) + rand(n) + 1 + end + end + + #----------------------------------------------------------------------------- + # * Refresh + #----------------------------------------------------------------------------- + def refresh + @opacity = 255 + @blend_type = 0 + end + + #----------------------------------------------------------------------------- + # * Same Position Starting Determinant + #----------------------------------------------------------------------------- + def check_event_trigger_here(triggers) + result = false + # If event is running + return result if $game_system.map_interpreter.running? + # All event loops + for event in $game_map.events.values + # If event coordinates and triggers are consistent + next if event.x != @x || event.y != @y + next if !triggers.include?(event.trigger) + # If starting determinant is same position event (other than jumping) + next if event.jumping? || !event.over_trigger? + event.start + result = true + end + return result + end + + #----------------------------------------------------------------------------- + # * Front Event Starting Determinant + #----------------------------------------------------------------------------- + def check_event_trigger_there(triggers) + result = false + # If event is running + return result if $game_system.map_interpreter.running? + # Calculate front event coordinates + new_x = @x + (@direction == 6 ? 1 : @direction == 4 ? -1 : 0) + new_y = @y + (@direction == 2 ? 1 : @direction == 8 ? -1 : 0) + return false if !$game_map.valid?(new_x, new_y) + # All event loops + for event in $game_map.events.values + # If event coordinates and triggers are consistent + next if event.x != new_x || event.y != new_y + next if !triggers.include?(event.trigger) + # If starting determinant is front event (other than jumping) + next if event.jumping? || event.over_trigger? + event.start + result = true + end + # If fitting event is not found + if result == false + # If front tile is a counter + if $game_map.counter?(new_x, new_y) + # Calculate coordinates of 1 tile further away + new_x += (@direction == 6 ? 1 : @direction == 4 ? -1 : 0) + new_y += (@direction == 2 ? 1 : @direction == 8 ? -1 : 0) + return false if !$game_map.valid?(new_x, new_y) + # All event loops + for event in $game_map.events.values + # If event coordinates and triggers are consistent + next if event.x != new_x || event.y != new_y + next if !triggers.include?(event.trigger) + # If starting determinant is front event (other than jumping) + next if event.jumping? || event.over_trigger? + event.start + result = true + end + end + end + return result + end + + #----------------------------------------------------------------------------- + # * Touch Event Starting Determinant + #----------------------------------------------------------------------------- + def check_event_trigger_touch(x, y) + result = false + # If event is running + return result if $game_system.map_interpreter.running? + # All event loops + for event in $game_map.events.values + # If event coordinates and triggers are consistent + next if event.x != x || event.y != y + if event.name[/trainer\((\d+)\)/i] + distance = $~[1].to_i + next if !pbEventCanReachPlayer?(event,self,distance) + elsif event.name[/counter\((\d+)\)/i] + distance = $~[1].to_i + next if !pbEventFacesPlayer?(event,self,distance) + end + next if ![1,2].include?(event.trigger) + # If starting determinant is front event (other than jumping) + next if event.jumping? || event.over_trigger? + event.start + result = true + end + return result + end + + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + last_real_x = @real_x + last_real_y = @real_y + super + update_screen_position(last_real_x, last_real_y) + # Update dependent events + $PokemonTemp.dependentEvents.updateDependentEvents + # Count down the time between allowed bump sounds + @bump_se -= 1 if @bump_se && @bump_se>0 + # Finish up dismounting from surfing + if $PokemonTemp.endSurf && !moving? + pbCancelVehicles + $PokemonTemp.surfJump = nil + $PokemonTemp.endSurf = false + end + update_event_triggering + end + + def update_command_new + dir = Input.dir4 + unless pbMapInterpreterRunning? or $game_temp.message_window_showing or + $PokemonTemp.miniupdate or $game_temp.in_menu + # Move player in the direction the directional button is being pressed + if dir==@lastdir && Graphics.frame_count-@lastdirframe>Graphics.frame_rate/20 + case dir + when 2; move_down + when 4; move_left + when 6; move_right + when 8; move_up + end + elsif dir!=@lastdir + case dir + when 2; turn_down + when 4; turn_left + when 6; turn_right + when 8; turn_up + end + end + end + # Record last direction input + @lastdirframe = Graphics.frame_count if dir!=@lastdir + @lastdir = dir + end + + # Center player on-screen + def update_screen_position(last_real_x, last_real_y) + return if !@moved_this_frame + center_x = (Graphics.width/2 - Game_Map::TILE_WIDTH/2) * Game_Map::X_SUBPIXELS + center_y = (Graphics.height/2 - Game_Map::TILE_HEIGHT/2) * Game_Map::Y_SUBPIXELS + if @real_y < last_real_y and @real_y - $game_map.display_y < center_y + $game_map.scroll_up(last_real_y - @real_y) + end + if @real_y > last_real_y and @real_y - $game_map.display_y > center_y + $game_map.scroll_down(@real_y - last_real_y) + end + if @real_x < last_real_x and @real_x - $game_map.display_x < center_x + $game_map.scroll_left(last_real_x - @real_x) + end + if @real_x > last_real_x and @real_x - $game_map.display_x > center_x + $game_map.scroll_right(@real_x - last_real_x) + end + end + + def update_event_triggering + return if moving? + # Try triggering events upon walking into them/in front of them + if @moved_this_frame + $PokemonTemp.dependentEvents.pbTurnDependentEvents + result = pbCheckEventTriggerFromDistance([2]) + # Event determinant is via touch of same position event + result |= check_event_trigger_here([1,2]) + # No events triggered, try other event triggers upon finishing a step + pbOnStepTaken(result) + end + # If C button was pressed, try to manually interact with events + if Input.trigger?(Input::C) && !$PokemonTemp.miniupdate + # Same position and front event determinant + check_event_trigger_here([0]) + check_event_trigger_there([0,2]) + end + end +end + + + +def pbGetPlayerCharset(meta,charset,trainer=nil,force=false) + trainer = $Trainer if !trainer + outfit = (trainer) ? trainer.outfit : 0 + if $game_player && $game_player.charsetData && !force + return nil if $game_player.charsetData[0]==$PokemonGlobal.playerID && + $game_player.charsetData[1]==charset && + $game_player.charsetData[2]==outfit + end + $game_player.charsetData = [$PokemonGlobal.playerID,charset,outfit] if $game_player + ret = meta[charset] + ret = meta[1] if !ret || ret=="" + if pbResolveBitmap("Graphics/Characters/"+ret+"_"+outfit.to_s) + ret = ret+"_"+outfit.to_s + end + return ret +end + +def pbUpdateVehicle + meta = pbGetMetadata(0,MetadataPlayerA+$PokemonGlobal.playerID) + if meta + newCharName = nil + charset = 1 # Regular graphic + if $PokemonGlobal.diving; charset = 5 # Diving graphic + elsif $PokemonGlobal.surfing; charset = 3 # Surfing graphic + elsif $PokemonGlobal.bicycle; charset = 2 # Bicycle graphic + end + newCharName = pbGetPlayerCharset(meta,charset) + $game_player.character_name = newCharName if newCharName + end +end + +def pbCancelVehicles(destination=nil) + $PokemonGlobal.surfing = false + $PokemonGlobal.diving = false + $PokemonGlobal.bicycle = false if !destination || !pbCanUseBike?(destination) + pbUpdateVehicle +end + +def pbCanUseBike?(mapid) + return true if pbGetMetadata(mapid,MetadataBicycleAlways) + val = pbGetMetadata(mapid,MetadataBicycle) + val = pbGetMetadata(mapid,MetadataOutdoor) if val==nil + return (val) ? true : false +end + +def pbMountBike + return if $PokemonGlobal.bicycle + $PokemonGlobal.bicycle = true + pbUpdateVehicle + bikebgm = pbGetMetadata(0,MetadataBicycleBGM) + pbCueBGM(bikebgm,0.5) if bikebgm +end + +def pbDismountBike + return if !$PokemonGlobal.bicycle + $PokemonGlobal.bicycle = false + pbUpdateVehicle + $game_map.autoplayAsCue +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/008_Game_Player_Visuals.rb b/Data/Scripts/003_Game classes/008_Game_Player_Visuals.rb new file mode 100644 index 000000000..371254968 --- /dev/null +++ b/Data/Scripts/003_Game classes/008_Game_Player_Visuals.rb @@ -0,0 +1,103 @@ +class Game_Player < Game_Character + @@bobFrameSpeed = 1.0/15 + + def fullPattern + case self.direction + when 2; return self.pattern + when 4; return 4+self.pattern + when 6; return 8+self.pattern + when 8; return 12+self.pattern + end + return 0 + end + + def setDefaultCharName(chname,pattern,lockpattern=false) + return if pattern<0 || pattern>=16 + @defaultCharacterName = chname + @direction = [2,4,6,8][pattern/4] + @pattern = pattern%4 + @lock_pattern = lockpattern + end + + def pbCanRun? + return false if $game_temp.in_menu || $game_temp.in_battle || + @move_route_forcing || $game_temp.message_window_showing || + pbMapInterpreterRunning? + terrain = pbGetTerrainTag + input = ($PokemonSystem.runstyle==1) ? $PokemonGlobal.runtoggle : Input.press?(Input::A) + return input && $PokemonGlobal.runningShoes && + !$PokemonGlobal.diving && !$PokemonGlobal.surfing && + !$PokemonGlobal.bicycle && !PBTerrain.onlyWalk?(terrain) + end + + def pbIsRunning? + return moving? && !@move_route_forcing && pbCanRun? + end + + def character_name + @defaultCharacterName = "" if !@defaultCharacterName + return @defaultCharacterName if @defaultCharacterName!="" + if !@move_route_forcing && $PokemonGlobal.playerID>=0 + meta = pbGetMetadata(0,MetadataPlayerA+$PokemonGlobal.playerID) + if meta && !$PokemonGlobal.bicycle && !$PokemonGlobal.diving && !$PokemonGlobal.surfing + charset = 1 # Display normal character sprite + if pbCanRun? && (moving? || @wasmoving) && Input.dir4!=0 && meta[4] && meta[4]!="" + charset = 4 # Display running character sprite + end + newCharName = pbGetPlayerCharset(meta,charset) + @character_name = newCharName if newCharName + @wasmoving = moving? + end + end + return @character_name + end + + def update_command + if PBTerrain.isIce?(pbGetTerrainTag) + self.move_speed = 5 # Sliding on ice + elsif !moving? && !@move_route_forcing && $PokemonGlobal + if $PokemonGlobal.bicycle + self.move_speed = 6 # Cycling + elsif pbCanRun? || $PokemonGlobal.surfing || $PokemonGlobal.diving + self.move_speed = 5 # Running, surfing or diving + else + self.move_speed = 4 # Walking + end + end + super + end + + def update_pattern + if $PokemonGlobal.surfing || $PokemonGlobal.diving + p = ((Graphics.frame_count%60)*@@bobFrameSpeed).floor + @pattern = p if !@lock_pattern + @pattern_surf = p + @bob_height = (p>=2) ? 2 : 0 + else + @bob_height = 0 + super + end + end +end + + +=begin +class Game_Character + alias update_old2 update + + def update + if self.is_a?(Game_Event) + if @dependentEvents + for i in 0...@dependentEvents.length + if @dependentEvents[i][0]==$game_map.map_id && + @dependentEvents[i][1]==self.id + self.move_speed_real = $game_player.move_speed_real + break + end + end + end + end + update_old2 + end +end +=end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/009_Game_Map.rb b/Data/Scripts/003_Game classes/009_Game_Map.rb new file mode 100644 index 000000000..a069cf087 --- /dev/null +++ b/Data/Scripts/003_Game classes/009_Game_Map.rb @@ -0,0 +1,438 @@ +#============================================================================== +# ** Game_Map +#------------------------------------------------------------------------------ +# This class handles the map. It includes scrolling and passable determining +# functions. Refer to "$game_map" for the instance of this class. +#============================================================================== +class Game_Map + attr_accessor :map_id + attr_accessor :tileset_name # tileset file name + attr_accessor :autotile_names # autotile file name + attr_reader :passages # passage table + attr_reader :priorities # prioroty table + attr_reader :terrain_tags # terrain tag table + attr_reader :events # events + attr_accessor :panorama_name # panorama file name + attr_accessor :panorama_hue # panorama hue + attr_accessor :fog_name # fog file name + attr_accessor :fog_hue # fog hue + attr_accessor :fog_opacity # fog opacity level + attr_accessor :fog_blend_type # fog blending method + attr_accessor :fog_zoom # fog zoom rate + attr_accessor :fog_sx # fog sx + attr_accessor :fog_sy # fog sy + attr_reader :fog_ox # fog x-coordinate starting point + attr_reader :fog_oy # fog y-coordinate starting point + attr_reader :fog_tone # fog color tone + attr_accessor :battleback_name # battleback file name + attr_accessor :display_x # display x-coordinate * 128 + attr_accessor :display_y # display y-coordinate * 128 + attr_accessor :need_refresh # refresh request flag + + TILE_WIDTH = 32 + TILE_HEIGHT = 32 + X_SUBPIXELS = ($RPGVX) ? 8 : 4 + Y_SUBPIXELS = ($RPGVX) ? 8 : 4 + REAL_RES_X = TILE_WIDTH * X_SUBPIXELS + REAL_RES_Y = TILE_HEIGHT * Y_SUBPIXELS + + def initialize + @map_id = 0 + @display_x = 0 + @display_y = 0 + end + + def setup(map_id) + @map_id = map_id + @map = load_data(sprintf("Data/Map%03d.%s",map_id,($RPGVX) ? "rvdata" : "rxdata")) + tileset = $data_tilesets[@map.tileset_id] + updateTileset + @fog_ox = 0 + @fog_oy = 0 + @fog_tone = Tone.new(0, 0, 0, 0) + @fog_tone_target = Tone.new(0, 0, 0, 0) + @fog_tone_duration = 0 + @fog_opacity_duration = 0 + @fog_opacity_target = 0 + self.display_x = 0 + self.display_y = 0 + @need_refresh = false + Events.onMapCreate.trigger(self,map_id,@map,tileset) + @events = {} + for i in @map.events.keys + @events[i] = Game_Event.new(@map_id, @map.events[i],self) + end + @common_events = {} + for i in 1...$data_common_events.size + @common_events[i] = Game_CommonEvent.new(i) + end + @scroll_direction = 2 + @scroll_rest = 0 + @scroll_speed = 4 + end + + def updateTileset + tileset = $data_tilesets[@map.tileset_id] + @tileset_name = tileset.tileset_name + @autotile_names = tileset.autotile_names + @panorama_name = tileset.panorama_name + @panorama_hue = tileset.panorama_hue + @fog_name = tileset.fog_name + @fog_hue = tileset.fog_hue + @fog_opacity = tileset.fog_opacity + @fog_blend_type = tileset.fog_blend_type + @fog_zoom = tileset.fog_zoom + @fog_sx = tileset.fog_sx + @fog_sy = tileset.fog_sy + @battleback_name = tileset.battleback_name + @passages = tileset.passages + @priorities = tileset.priorities + @terrain_tags = tileset.terrain_tags + end + + def width; return @map.width; end + def height; return @map.height; end + def encounter_list; return @map.encounter_list; end + def encounter_step; return @map.encounter_step; end + def data; return @map.data; end + + def name + ret = pbGetMessage(MessageTypes::MapNames,@map_id) + ret.gsub!(/\\PN/,$Trainer.name) if $Trainer + return ret + end + #----------------------------------------------------------------------------- + # * Autoplays background music + # Plays music called "[normal BGM]n" if it's night time and it exists + #----------------------------------------------------------------------------- + def autoplayAsCue + if @map.autoplay_bgm + if PBDayNight.isNight? && FileTest.audio_exist?("Audio/BGM/"+ @map.bgm.name+ "_n") + pbCueBGM(@map.bgm.name+"_n",1.0,@map.bgm.volume,@map.bgm.pitch) + else + pbCueBGM(@map.bgm,1.0) + end + end + if @map.autoplay_bgs + pbBGSPlay(@map.bgs) + end + end + #----------------------------------------------------------------------------- + # * Plays background music + # Plays music called "[normal BGM]n" if it's night time and it exists + #----------------------------------------------------------------------------- + def autoplay + if @map.autoplay_bgm + if PBDayNight.isNight? && FileTest.audio_exist?("Audio/BGM/"+ @map.bgm.name+ "n") + pbBGMPlay(@map.bgm.name+"n",@map.bgm.volume,@map.bgm.pitch) + else + pbBGMPlay(@map.bgm) + end + end + if @map.autoplay_bgs + pbBGSPlay(@map.bgs) + end + end + + def valid?(x, y) + return (x>=0 and x=0 and y=-10 and x<=width+10 and y>=-10 and y<=height+10) + end + + def passable?(x, y, d, self_event = nil) + return false if !valid?(x, y) + bit = (1 << (d / 2 - 1)) & 0x0f + for event in events.values + next if event.tile_id <= 0 + terrain = @terrain_tags[event.tile_id] + next if terrain == PBTerrain::Neutral + next if event == self_event + next if event.x != x || event.y != y + next if event.through + passage = @passages[event.tile_id] + return false if passage & bit != 0 + return false if passage & 0x0f == 0x0f + return true if @priorities[event.tile_id] == 0 + end + return playerPassable?(x, y, d, self_event) if self_event==$game_player + # All other events + newx = x; newy = y + case d + when 1; newx -= 1; newy += 1 + when 2; newy += 1 + when 3; newx += 1; newy += 1 + when 4; newx -= 1 + when 6; newx += 1 + when 7; newx -= 1; newy -= 1 + when 8; newy -= 1 + when 9; newx += 1; newy -= 1 + end + return false if !valid?(newx, newy) + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + passage = @passages[tile_id] + # If already on water, only allow movement to another water tile + if self_event!=nil && PBTerrain.isJustWater?(terrain) + for j in [2, 1, 0] + facing_tile_id = data[newx, newy, j] + return false if facing_tile_id==nil + facing_terrain = @terrain_tags[facing_tile_id] + if facing_terrain!=0 && facing_terrain!=PBTerrain::Neutral + return PBTerrain.isJustWater?(facing_terrain) + end + end + return false + # Can't walk onto ice + elsif PBTerrain.isIce?(terrain) + return false + elsif self_event!=nil && self_event.x==x && self_event.y==y + # Can't walk onto ledges + for j in [2, 1, 0] + facing_tile_id = data[newx, newy, j] + return false if facing_tile_id==nil + facing_terrain = @terrain_tags[facing_tile_id] + if facing_terrain!=0 && facing_terrain!=PBTerrain::Neutral + return false if PBTerrain.isLedge?(facing_terrain) + break + end + end + # Regular passability checks + if terrain!=PBTerrain::Neutral + if passage & bit != 0 || passage & 0x0f == 0x0f + return false + elsif @priorities[tile_id] == 0 + return true + end + end + # Regular passability checks + elsif terrain!=PBTerrain::Neutral + if passage & bit != 0 || passage & 0x0f == 0x0f + return false + elsif @priorities[tile_id] == 0 + return true + end + end + end + return true + end + + def playerPassable?(x, y, d, self_event = nil) + bit = (1 << (d / 2 - 1)) & 0x0f + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + passage = @passages[tile_id] + # Ignore bridge tiles if not on a bridge + next if PBTerrain.isBridge?(terrain) && $PokemonGlobal.bridge==0 + # Make water tiles passable if player is surfing + if $PokemonGlobal.surfing && PBTerrain.isPassableWater?(terrain) + return true + # Prevent cycling in really tall grass/on ice + elsif $PokemonGlobal.bicycle && PBTerrain.onlyWalk?(terrain) + return false + # Depend on passability of bridge tile if on bridge + elsif PBTerrain.isBridge?(terrain) && $PokemonGlobal.bridge>0 + return (passage & bit == 0 && passage & 0x0f != 0x0f) + # Regular passability checks + elsif terrain!=PBTerrain::Neutral + if passage & bit != 0 || passage & 0x0f == 0x0f + return false + elsif @priorities[tile_id] == 0 + return true + end + end + end + return true + end + + # Returns whether the position x,y is fully passable (there is no blocking + # event there, and the tile is fully passable in all directions) + def passableStrict?(x, y, d, self_event = nil) + return false if !valid?(x, y) + for event in events.values + next if event == self_event || event.tile_id < 0 || event.through + next if event.x != x || event.y != y + terrain = @terrain_tags[event.tile_id] + next if terrain == PBTerrain::Neutral + return false if @passages[event.tile_id] & 0x0f != 0 + return true if @priorities[event.tile_id] == 0 + end + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + next if terrain == PBTerrain::Neutral + return false if @passages[tile_id] & 0x0f != 0 + return true if @priorities[tile_id] == 0 + end + return true + end + + def bush?(x,y) + for i in [2, 1, 0] + tile_id = data[x, y, i] + return false if PBTerrain.isBridge?(@terrain_tags[tile_id]) && $PokemonGlobal.bridge>0 + return true if @passages[tile_id] & 0x40 == 0x40 + end + return false + end + + def deepBush?(x,y) + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + return false if $PokemonGlobal.bridge>0 && PBTerrain.isBridge?(terrain) + return true if terrain==PBTerrain::TallGrass && @passages[tile_id] & 0x40 == 0x40 + end + return false + end + + def counter?(x,y) + for i in [2, 1, 0] + tile_id = data[x, y, i] + passage = @passages[tile_id] + return true if passage & 0x80 == 0x80 + end + return false + end + + def terrain_tag(x,y,countBridge=false) + return 0 if !valid?(x, y) + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + next if !countBridge && PBTerrain.isBridge?(terrain) && $PokemonGlobal.bridge==0 + return terrain if terrain > 0 && terrain!=PBTerrain::Neutral + end + return 0 + end + + def check_event(x,y) + for event in self.events.values + return event.id if event.x == x and event.y == y + end + end + + def display_x=(value) + @display_x = value + if pbGetMetadata(self.map_id,MetadataSnapEdges) + max_x = (self.width - Graphics.width*1.0/TILE_WIDTH) * REAL_RES_X + @display_x = [0, [@display_x, max_x].min].max + end + $MapFactory.setMapsInRange if $MapFactory + end + + def display_y=(value) + @display_y = value + if pbGetMetadata(self.map_id,MetadataSnapEdges) + max_y = (self.height - Graphics.height*1.0/TILE_HEIGHT) * REAL_RES_Y + @display_y = [0, [@display_y, max_y].min].max + end + $MapFactory.setMapsInRange if $MapFactory + end + + def scroll_up(distance) + self.display_y -= distance + end + def scroll_down(distance) + self.display_y += distance + end + + def scroll_left(distance) + self.display_x -= distance + end + + def scroll_right(distance) + self.display_x += distance + end + + def start_scroll(direction, distance, speed) + @scroll_direction = direction + if direction==2 || direction==8 # down or up + @scroll_rest = distance * REAL_RES_Y + else + @scroll_rest = distance * REAL_RES_X + end + @scroll_speed = speed + end + + def scrolling? + return @scroll_rest > 0 + end + + def start_fog_tone_change(tone,duration) + @fog_tone_target = tone.clone + @fog_tone_duration = duration + if @fog_tone_duration == 0 + @fog_tone = @fog_tone_target.clone + end + end + + def start_fog_opacity_change(opacity,duration) + @fog_opacity_target = opacity*1.0 + @fog_opacity_duration = duration + if @fog_opacity_duration==0 + @fog_opacity = @fog_opacity_target + end + end + + def refresh + for event in @events.values + event.refresh + end + for common_event in @common_events.values + common_event.refresh + end + @need_refresh = false + end + + def update + # refresh maps if necessary + if $MapFactory + for i in $MapFactory.maps + i.refresh if i.need_refresh + end + $MapFactory.setCurrentMap + end + # If scrolling + if @scroll_rest>0 + distance = (1<<@scroll_speed)*40.0/Graphics.frame_rate + distance = @scroll_rest if distance>@scroll_rest + case @scroll_direction + when 2; scroll_down(distance) + when 4; scroll_left(distance) + when 6; scroll_right(distance) + when 8; scroll_up(distance) + end + @scroll_rest -= distance + end + # Only update events that are on-screen + for event in @events.values + event.update + end + # Update common events + for common_event in @common_events.values + common_event.update + end + # Update fog + @fog_ox -= @fog_sx/8.0 + @fog_oy -= @fog_sy/8.0 + if @fog_tone_duration>=1 + d = @fog_tone_duration + target = @fog_tone_target + @fog_tone.red = (@fog_tone.red * (d - 1) + target.red) / d + @fog_tone.green = (@fog_tone.green * (d - 1) + target.green) / d + @fog_tone.blue = (@fog_tone.blue * (d - 1) + target.blue) / d + @fog_tone.gray = (@fog_tone.gray * (d - 1) + target.gray) / d + @fog_tone_duration -= 1 + end + if @fog_opacity_duration >= 1 + d = @fog_opacity_duration + @fog_opacity = (@fog_opacity * (d - 1) + @fog_opacity_target) / d + @fog_opacity_duration -= 1 + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/010_Game_Map_Autoscroll.rb b/Data/Scripts/003_Game classes/010_Game_Map_Autoscroll.rb new file mode 100644 index 000000000..00c8e0e40 --- /dev/null +++ b/Data/Scripts/003_Game classes/010_Game_Map_Autoscroll.rb @@ -0,0 +1,194 @@ +#=============================================================================== +# ** Map Autoscroll +#------------------------------------------------------------------------------- +# Wachunga +# Version 1.02 +# 2005-12-18 +#=============================================================================== +=begin + + This script supplements the built-in "Scroll Map" event command with the + aim of simplifying cutscenes (and map scrolling in general). Whereas the + normal event command requires a direction and number of tiles to scroll, + Map Autoscroll scrolls the map to center on the tile whose x and y + coordinates are given. + + FEATURES + - automatic map scrolling to given x,y coordinate (or player) + - destination is fixed, so it's possible to scroll to same place even if + origin is variable (e.g. moving NPC) + - variable speed (just like "Scroll Map" event command) + - diagonal scrolling supported + + SETUP + Instead of a "Scroll Map" event command, use the "Call Script" command + and enter on the following on the first line: + + autoscroll(x,y) + + (replacing "x" and "y" with the x and y coordinates of the tile to scroll to) + + To specify a scroll speed other than the default (4), use: + + autoscroll(x,y,speed) + + (now also replacing "speed" with the scroll speed from 1-6) + + Diagonal scrolling happens automatically when the destination is diagonal + relative to the starting point (i.e., not directly up, down, left or right). + + To scroll to the player, instead use the following: + + autoscroll_player(speed) + + Note: because of how the interpreter and the "Call Script" event command + are setup, the call to autoscroll(...) can only be on the first line of + the "Call Script" event command (and not flowing down to subsequent lines). + + For example, the following call may not work as expected: + + autoscroll($game_variables[1], + $game_variables[2]) + + (since the long argument names require dropping down to a second line) + A work-around is to setup new variables with shorter names in a preceding + (separate) "Call Script" event command: + + @x = $game_variables[1] + @y = $game_variables[2] + + and then use those as arguments: + + autoscroll(@x,@y) + + The renaming must be in a separate "Call Script" because otherwise + the call to autoscroll(...) isn't on the first line. + + Originally requested by militantmilo80: + http://www.rmxp.net/forums/index.php?showtopic=29519 + +=end + +class Interpreter + SCROLL_SPEED_DEFAULT = 4 + #----------------------------------------------------------------------------- + # * Map Autoscroll to Coordinates + # x : x coordinate to scroll to and center on + # y : y coordinate to scroll to and center on + # speed : (optional) scroll speed (from 1-6, default being 4) + #----------------------------------------------------------------------------- + def autoscroll(x,y,speed=SCROLL_SPEED_DEFAULT) + if $game_map.scrolling? + return false + elsif not $game_map.valid?(x,y) + print 'Map Autoscroll: given x,y is invalid' + return command_skip + elsif not (1..6).include?(speed) + print 'Map Autoscroll: invalid speed (1-6 only)' + return command_skip + end + center_x = (Graphics.width/2 - Game_Map::TILE_WIDTH/2) * 4 # X coordinate in the center of the screen + center_y = (Graphics.height/2 - Game_Map::TILE_HEIGHT/2) * 4 # Y coordinate in the center of the screen + max_x = ($game_map.width - Graphics.width*1.0/Game_Map::TILE_WIDTH) * 4 * Game_Map::TILE_WIDTH + max_y = ($game_map.height - Graphics.height*1.0/Game_Map::TILE_HEIGHT) * 4 * Game_Map::TILE_HEIGHT + count_x = ($game_map.display_x - [0,[x*Game_Map::REAL_RES_X-center_x,max_x].min].max)/Game_Map::REAL_RES_X + count_y = ($game_map.display_y - [0,[y*Game_Map::REAL_RES_Y-center_y,max_y].min].max)/Game_Map::REAL_RES_Y + if not @diag + @diag = true + dir = nil + if count_x > 0 + if count_y > 0 + dir = 7 + elsif count_y < 0 + dir = 1 + end + elsif count_x < 0 + if count_y > 0 + dir = 9 + elsif count_y < 0 + dir = 3 + end + end + count = [count_x.abs,count_y.abs].min + else + @diag = false + dir = nil + if count_x != 0 and count_y != 0 + return false + elsif count_x > 0 + dir = 4 + elsif count_x < 0 + dir = 6 + elsif count_y > 0 + dir = 8 + elsif count_y < 0 + dir = 2 + end + count = count_x != 0 ? count_x.abs : count_y.abs + end + $game_map.start_scroll(dir, count, speed) if dir != nil + if @diag + return false + else + return true + end + end + + #----------------------------------------------------------------------------- + # * Map Autoscroll (to Player) + # speed : (optional) scroll speed (from 1-6, default being 4) + #----------------------------------------------------------------------------- + def autoscroll_player(speed=SCROLL_SPEED_DEFAULT) + autoscroll($game_player.x,$game_player.y,speed) + end +end + + + +class Game_Map + def scroll_downright(distance) + @display_x = [@display_x + distance, + (self.width - Graphics.width*1.0/TILE_WIDTH) * REAL_RES_X].min + @display_y = [@display_y + distance, + (self.height - Graphics.height*1.0/TILE_HEIGHT) * REAL_RES_Y].min + end + + def scroll_downleft(distance) + @display_x = [@display_x - distance, 0].max + @display_y = [@display_y + distance, + (self.height - Graphics.height*1.0/TILE_HEIGHT) * REAL_RES_Y].min + end + + def scroll_upright(distance) + @display_x = [@display_x + distance, + (self.width - Graphics.width*1.0/TILE_WIDTH) * REAL_RES_X].min + @display_y = [@display_y - distance, 0].max + end + + def scroll_upleft(distance) + @display_x = [@display_x - distance, 0].max + @display_y = [@display_y - distance, 0].max + end + + def update_scrolling + # If scrolling + if @scroll_rest > 0 + # Change from scroll speed to distance in map coordinates + distance = (1<<@scroll_speed)*40/Graphics.frame_rate + distance = @scroll_rest if distance>@scroll_rest + # Execute scrolling + case @scroll_direction + when 1; scroll_downleft(distance) + when 2; scroll_down(distance) + when 3; scroll_downright(distance) + when 4; scroll_left(distance) + when 6; scroll_right(distance) + when 7; scroll_upleft(distance) + when 8; scroll_up(distance) + when 9; scroll_upright(distance) + end + # Subtract distance scrolled + @scroll_rest -= distance + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/011_MapFactory.rb b/Data/Scripts/003_Game classes/011_MapFactory.rb new file mode 100644 index 000000000..121d142e5 --- /dev/null +++ b/Data/Scripts/003_Game classes/011_MapFactory.rb @@ -0,0 +1,488 @@ +#=============================================================================== +# Map Factory (allows multiple maps to be loaded at once and connected) +#=============================================================================== +class PokemonMapFactory + attr_reader :maps + + def initialize(id) + @maps = [] + @fixup = false + @mapChanged = false # transient instance variable + setup(id) + end + + # Clears all maps and sets up the current map with id. This function also sets + # the positions of neighboring maps and notifies the game system of a map + # change. + def setup(id) + @maps.clear + @maps[0] = Game_Map.new + @mapIndex = 0 + oldID = ($game_map) ? $game_map.map_id : 0 + setMapChanging(id,@maps[0]) if oldID!=0 && oldID!=@maps[0].map_id + $game_map = @maps[0] + @maps[0].setup(id) + setMapsInRange + setMapChanged(oldID) + end + + def map + @mapIndex = 0 if !@mapIndex || @mapIndex<0 + return @maps[@mapIndex] if @maps[@mapIndex] + raise "No maps in save file... (mapIndex=#{@mapIndex})" if @maps.length==0 + for i in 0...@maps.length + if @maps[i] + echo("Using next map, may be incorrect (mapIndex=#{@mapIndex}, length=#{@maps.length})") + return @maps[i] + end + raise "No maps in save file... (all maps empty; mapIndex=#{@mapIndex})" + end + end + + def hasMap?(id) + for map in @maps + return true if map.map_id==id + end + return false + end + + def getMapIndex(id) + for i in 0...@maps.length + return i if @maps[i].map_id==id + end + return -1 + end + + def getMap(id,add=true) + for map in @maps + return map if map.map_id==id + end + map = Game_Map.new + map.setup(id) + @maps.push(map) if add + return map + end + + def getMapNoAdd(id) + return getMap(id,false) + end + + def getNewMap(playerX,playerY) + id = $game_map.map_id + conns = MapFactoryHelper.getMapConnections + for conn in conns + next if conn[0]!=id && conn[3]!=id + mapidB = nil + newx = 0 + newy = 0 + if conn[0]==id + mapidB = conn[3] + mapB = MapFactoryHelper.getMapDims(conn[3]) + newx = conn[4] - conn[1] + playerX + newy = conn[5] - conn[2] + playerY + else + mapidB = conn[0] + mapB = MapFactoryHelper.getMapDims(conn[0]) + newx = conn[1] - conn[4] + playerX + newy = conn[2] - conn[5] + playerY + end + if newx>=0 && newx=0 && newy=dims[0] || newY>=dims[1] + return [conn[3],newX,newY] + elsif conn[3]==id + newX = x + conn[1] - conn[4] + newY = y + conn[2] - conn[5] + next if newX<0 || newY<0 + dims = MapFactoryHelper.getMapDims(conn[0]) + next if newX>=dims[0] || newY>=dims[1] + return [conn[0],newX,newY] + end + end + return nil + end + + def getFacingCoords(x,y,direction=0,steps=1) + case direction + when 1; x -= steps; y += steps + when 2; y += steps + when 3; x += steps; y += steps + when 4; x -= steps + when 6; x += steps + when 7; x -= steps; y -= steps + when 8; y -= steps + when 9; x += steps; y -= steps + end + return [x,y] + end + + def updateMaps(scene) + updateMapsInternal + $MapFactory.setSceneStarted(scene) if @mapChanged + end + + def updateMapsInternal + return if $game_player.moving? + if !MapFactoryHelper.hasConnections?($game_map.map_id) + return if @maps.length==1 + for i in 0...@maps.length + @maps[i] = nil if $game_map.map_id!=@maps[i].map_id + end + @maps.compact! + @mapIndex = getMapIndex($game_map.map_id) + return + end + setMapsInRange + deleted = false + for i in 0...@maps.length + next if MapFactoryHelper.mapInRange?(@maps[i]) + @maps[i] = nil + deleted = true + end + if deleted + @maps.compact! + @mapIndex = getMapIndex($game_map.map_id) + end + end +end + + + +#=============================================================================== +# Map Factory Helper (stores map connection and size data and calculations +# involving them) +#=============================================================================== +module MapFactoryHelper + @@MapConnections = nil + @@MapDims = nil + + def self.clear + @@MapConnections = nil + @@MapDims = nil + end + + def self.getMapConnections + if !@@MapConnections + @@MapConnections = [] + begin + conns = load_data("Data/map_connections.dat") + rescue + conns = [] + end + for i in 0...conns.length + conn = conns[i] + v = getMapEdge(conn[0],conn[1]) + dims = getMapDims(conn[0]) + next if dims[0]==0 || dims[1]==0 + if conn[1]=="N" || conn[1]=="S" + conn[1] = conn[2] + conn[2] = v + elsif conn[1]=="E" || conn[1]=="W" + conn[1] = v + end + v = getMapEdge(conn[3],conn[4]) + dims = getMapDims(conn[3]) + next if dims[0]==0 || dims[1]==0 + if conn[4]=="N" || conn[4]=="S" + conn[4] = conn[5] + conn[5] = v + elsif conn[4]=="E" || conn[4]=="W" + conn[4] = v + end + @@MapConnections.push(conn) + end + end + return @@MapConnections + end + + def self.hasConnections?(id) + conns = MapFactoryHelper.getMapConnections + for conn in conns + return true if conn[0]==id || conn[3]==id + end + return false + end + + # Gets the height and width of the map with id + def self.getMapDims(id) + # Create cache if doesn't exist + @@MapDims = [] if !@@MapDims + # Add map to cache if can't be found + if !@@MapDims[id] + begin + map = pbLoadRxData(sprintf("Data/Map%03d", id)) + @@MapDims[id] = [map.width,map.height] + rescue + @@MapDims[id] = [0,0] + end + end + # Return map in cache + return @@MapDims[id] + end + + # Returns the X or Y coordinate of an edge on the map with id. + # Considers the special strings "N","W","E","S" + def self.getMapEdge(id,edge) + return 0 if edge=="N" || edge=="W" + dims = getMapDims(id) # Get dimensions + return dims[0] if edge=="E" + return dims[1] if edge=="S" + return dims[0] # real dimension (use width) + end + + def self.mapInRange?(map) + range = 6 # Number of tiles + dispx = map.display_x + dispy = map.display_y + return false if dispx >= (map.width + range) * Game_Map::REAL_RES_X + return false if dispy >= (map.height + range) * Game_Map::REAL_RES_Y + return false if dispx <= -(Graphics.width + range * Game_Map::TILE_WIDTH) * Game_Map::X_SUBPIXELS + return false if dispy <= -(Graphics.height + range * Game_Map::TILE_HEIGHT) * Game_Map::Y_SUBPIXELS + return true + end + + def self.mapInRangeById?(id,dispx,dispy) + range = 6 # Number of tiles + dims = MapFactoryHelper.getMapDims(id) + return false if dispx >= (dims[0] + range) * Game_Map::REAL_RES_X + return false if dispy >= (dims[1] + range) * Game_Map::REAL_RES_Y + return false if dispx <= -(Graphics.width + range * Game_Map::TILE_WIDTH) * Game_Map::X_SUBPIXELS + return false if dispy <= -(Graphics.height + range * Game_Map::TILE_HEIGHT) * Game_Map::Y_SUBPIXELS + return true + end +end + + + +def updateTilesets + maps = $MapFactory.maps + for map in maps + map.updateTileset if map + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/001_Sprite_Picture.rb b/Data/Scripts/004_Sprites/001_Sprite_Picture.rb new file mode 100644 index 000000000..afd0d43e1 --- /dev/null +++ b/Data/Scripts/004_Sprites/001_Sprite_Picture.rb @@ -0,0 +1,58 @@ +class Sprite_Picture + def initialize(viewport, picture) + @viewport = viewport + @picture = picture + @sprite = nil + update + end + + def dispose + @sprite.dispose if @sprite + end + + def update + @sprite.update if @sprite + # If picture file name is different from current one + if @picture_name != @picture.name + # Remember file name to instance variables + @picture_name = @picture.name + # If file name is not empty + if @picture_name != "" + # Get picture graphic + @sprite=IconSprite.new(0,0,@viewport) if !@sprite + @sprite.setBitmap("Graphics/Pictures/"+@picture_name) + end + end + # If file name is empty + if @picture_name == "" + # Set sprite to invisible + if @sprite + @sprite.dispose if @sprite + @sprite=nil + end + return + end + # Set sprite to visible + @sprite.visible = true + # Set transfer starting point + if @picture.origin == 0 + @sprite.ox = 0 + @sprite.oy = 0 + else + @sprite.ox = @sprite.bitmap.width / 2 + @sprite.oy = @sprite.bitmap.height / 2 + end + # Set sprite coordinates + @sprite.x = @picture.x + @sprite.y = @picture.y + @sprite.z = @picture.number + # Set zoom rate, opacity level, and blend method + @sprite.zoom_x = @picture.zoom_x / 100.0 + @sprite.zoom_y = @picture.zoom_y / 100.0 + @sprite.opacity = @picture.opacity + @sprite.blend_type = @picture.blend_type + # Set rotation angle and color tone + @sprite.angle = @picture.angle + @sprite.tone = @picture.tone + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/002_Sprite_Timer.rb b/Data/Scripts/004_Sprites/002_Sprite_Timer.rb new file mode 100644 index 000000000..ae37ac026 --- /dev/null +++ b/Data/Scripts/004_Sprites/002_Sprite_Timer.rb @@ -0,0 +1,44 @@ +class Sprite_Timer + def initialize(viewport=nil) + @viewport=viewport + @timer=nil + @total_sec=nil + @disposed=false + end + + def dispose + @timer.dispose if @timer + @timer=nil + @disposed=true + end + + def disposed? + @disposed + end + + def update + return if disposed? + if $game_system.timer_working + if !@timer + @timer=Window_AdvancedTextPokemon.newWithSize("",Graphics.width-120,0,120,64) + @timer.width=@timer.borderX+96 + @timer.x=Graphics.width-@timer.width + @timer.viewport=@viewport + @timer.z=99998 + end + curtime=$game_system.timer / Graphics.frame_rate + curtime=0 if curtime<0 + if curtime != @total_sec + # Calculate total number of seconds + @total_sec = curtime + # Make a string for displaying the timer + min = @total_sec / 60 + sec = @total_sec % 60 + @timer.text = _ISPRINTF("{1:02d}:{2:02d}", min, sec) + end + @timer.update + else + @timer.visible=false if @timer + end + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/003_Sprite_Character.rb b/Data/Scripts/004_Sprites/003_Sprite_Character.rb new file mode 100644 index 000000000..6f21809d8 --- /dev/null +++ b/Data/Scripts/004_Sprites/003_Sprite_Character.rb @@ -0,0 +1,174 @@ +class BushBitmap + def initialize(bitmap,isTile,depth) + @bitmaps = [] + @bitmap = bitmap + @isTile = isTile + @isBitmap = @bitmap.is_a?(Bitmap) + @depth = depth + end + + def dispose + for b in @bitmaps + b.dispose if b + end + end + + def bitmap + thisBitmap = (@isBitmap) ? @bitmap : @bitmap.bitmap + current = (@isBitmap) ? 0 : @bitmap.currentIndex + if !@bitmaps[current] + if @isTile + @bitmaps[current] = pbBushDepthTile(thisBitmap,@depth) + else + @bitmaps[current] = pbBushDepthBitmap(thisBitmap,@depth) + end + end + return @bitmaps[current] + end + + def pbBushDepthBitmap(bitmap,depth) + ret = Bitmap.new(bitmap.width,bitmap.height) + charheight = ret.height/4 + cy = charheight-depth-2 + for i in 0...4 + y = i*charheight + if cy>=0 + ret.blt(0,y,bitmap,Rect.new(0,y,ret.width,cy)) + ret.blt(0,y+cy,bitmap,Rect.new(0,y+cy,ret.width,2),170) + end + ret.blt(0,y+cy+2,bitmap,Rect.new(0,y+cy+2,ret.width,2),85) if cy+2>=0 + end + return ret + end + + def pbBushDepthTile(bitmap,depth) + ret = Bitmap.new(bitmap.width,bitmap.height) + charheight = ret.height + cy = charheight-depth-2 + y = charheight + if cy>=0 + ret.blt(0,y,bitmap,Rect.new(0,y,ret.width,cy)) + ret.blt(0,y+cy,bitmap,Rect.new(0,y+cy,ret.width,2),170) + end + ret.blt(0,y+cy+2,bitmap,Rect.new(0,y+cy+2,ret.width,2),85) if cy+2>=0 + return ret + end +end + + + +class Sprite_Character < RPG::Sprite + attr_accessor :character + + def initialize(viewport, character = nil) + super(viewport) + @character = character + @oldbushdepth = 0 + @spriteoffset = false + if !character || character==$game_player || (character.name[/reflection/i] rescue false) + @reflection = Sprite_Reflection.new(self,character,viewport) + end + @surfbase = Sprite_SurfBase.new(self,character,viewport) if character==$game_player + update + end + + def groundY + return @character.screen_y_ground + end + + def visible=(value) + super(value) + @reflection.visible = value if @reflection + end + + def dispose + @bushbitmap.dispose if @bushbitmap + @bushbitmap = nil + @charbitmap.dispose if @charbitmap + @charbitmap = nil + @reflection.dispose if @reflection + @reflection = nil + @surfbase.dispose if @surfbase + @surfbase = nil + super + end + + def update + return if @character.is_a?(Game_Event) && !@character.should_update? + super + if @tile_id!=@character.tile_id or + @character_name!=@character.character_name or + @character_hue!=@character.character_hue or + @oldbushdepth!=@character.bush_depth + @tile_id = @character.tile_id + @character_name = @character.character_name + @character_hue = @character.character_hue + @oldbushdepth = @character.bush_depth + if @tile_id>=384 + @charbitmap.dispose if @charbitmap + @charbitmap = pbGetTileBitmap(@character.map.tileset_name,@tile_id,@character.character_hue) + @charbitmapAnimated = false + @bushbitmap.dispose if @bushbitmap + @bushbitmap = nil + @spriteoffset = false + @cw = Game_Map::TILE_WIDTH + @ch = Game_Map::TILE_HEIGHT + self.src_rect.set(0,0,@cw,@ch) + self.ox = @cw/2 + self.oy = @ch + @character.sprite_size = [@cw,@ch] + else + @charbitmap.dispose if @charbitmap + @charbitmap = AnimatedBitmap.new( + "Graphics/Characters/"+@character.character_name,@character.character_hue) + @charbitmapAnimated = true + @bushbitmap.dispose if @bushbitmap + @bushbitmap = nil + @spriteoffset = @character_name[/offset/i] + @cw = @charbitmap.width/4 + @ch = @charbitmap.height/4 + self.ox = @cw/2 + @character.sprite_size = [@cw,@ch] + end + end + @charbitmap.update if @charbitmapAnimated + bushdepth = @character.bush_depth + if bushdepth==0 + self.bitmap = (@charbitmapAnimated) ? @charbitmap.bitmap : @charbitmap + else + @bushbitmap = BushBitmap.new(@charbitmap,(@tile_id>=384),bushdepth) if !@bushbitmap + self.bitmap = @bushbitmap.bitmap + end + self.visible = !@character.transparent + if @tile_id==0 + sx = @character.pattern*@cw + sy = ((@character.direction-2)/2)*@ch + self.src_rect.set(sx,sy,@cw,@ch) + self.oy = (@spriteoffset rescue false) ? @ch-16 : @ch + self.oy -= @character.bob_height + end + if self.visible + if $PokemonSystem.tilemap==0 || + (@character.is_a?(Game_Event) && @character.name[/regulartone/i]) + self.tone.set(0,0,0,0) + else + pbDayNightTint(self) + end + end + self.x = @character.screen_x + self.y = @character.screen_y + self.z = @character.screen_z(@ch) +# self.zoom_x = Game_Map::TILE_WIDTH/32.0 +# self.zoom_y = Game_Map::TILE_HEIGHT/32.0 + self.opacity = @character.opacity + self.blend_type = @character.blend_type +# self.bush_depth = @character.bush_depth + if @character.animation_id!=0 + animation = $data_animations[@character.animation_id] + animation(animation,true) + @character.animation_id = 0 + end + @reflection.update if @reflection + @surfbase.update if @surfbase + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/004_Sprite_WaterReflection.rb b/Data/Scripts/004_Sprites/004_Sprite_WaterReflection.rb new file mode 100644 index 000000000..7645c7eba --- /dev/null +++ b/Data/Scripts/004_Sprites/004_Sprite_WaterReflection.rb @@ -0,0 +1,90 @@ +class Sprite_Reflection + attr_reader :visible + attr_accessor :event + + def initialize(sprite,event,viewport=nil) + @rsprite = sprite + @sprite = nil + @event = event + @height = 0 + @fixedheight = false + if @event && @event!=$game_player + if @event.name[/reflection\((\d+)\)/i] + @height = $~[1].to_i || 0 + @fixedheight = true + end + end + @viewport = viewport + @disposed = false + update + end + + def dispose + if !@disposed + @sprite.dispose if @sprite + @sprite = nil + @disposed = true + end + end + + def disposed? + @disposed + end + + def visible=(value) + @visible = value + @sprite.visible = value if @sprite && !@sprite.disposed? + end + + def update + return if disposed? + shouldShow = @rsprite.visible + if !shouldShow + # Just-in-time disposal of sprite + if @sprite + @sprite.dispose + @sprite = nil + end + return + end + # Just-in-time creation of sprite + @sprite = Sprite.new(@viewport) if !@sprite + if @sprite + x = @rsprite.x-@rsprite.ox + y = @rsprite.y-@rsprite.oy + y -= 32 if @rsprite.character.character_name[/offset/i] + @height = $PokemonGlobal.bridge if !@fixedheight + y += @height*16 + width = @rsprite.src_rect.width + height = @rsprite.src_rect.height + @sprite.x = x+width/2 + @sprite.y = y+height+height/2 + @sprite.ox = width/2 + @sprite.oy = height/2-2 # Hard-coded 2 pixel shift up + @sprite.oy -= @rsprite.character.bob_height*2 + @sprite.z = -50 # Still water is -100, map is 0 and above + @sprite.zoom_x = @rsprite.zoom_x + @sprite.zoom_y = @rsprite.zoom_y + frame = (Graphics.frame_count%40)/10 + case frame + when 1; @sprite.zoom_x *= 0.95 + when 3; @sprite.zoom_x *= 1.05 + else; @sprite.zoom_x *= 1.0 + end + @sprite.angle = 180.0 + @sprite.mirror = true + @sprite.bitmap = @rsprite.bitmap + @sprite.tone = @rsprite.tone + if @height>0 + @sprite.color = Color.new(48,96,160,255) # Dark still water + @sprite.opacity = @rsprite.opacity + @sprite.visible = !TIME_SHADING # Can't time-tone a colored sprite + else + @sprite.color = Color.new(224,224,224,96) + @sprite.opacity = @rsprite.opacity*3/4 + @sprite.visible = true + end + @sprite.src_rect = @rsprite.src_rect + end + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/005_Sprite_SurfBase.rb b/Data/Scripts/004_Sprites/005_Sprite_SurfBase.rb new file mode 100644 index 000000000..1b22a4616 --- /dev/null +++ b/Data/Scripts/004_Sprites/005_Sprite_SurfBase.rb @@ -0,0 +1,77 @@ +class Sprite_SurfBase + attr_reader :visible + attr_accessor :event + + def initialize(sprite,event,viewport=nil) + @rsprite = sprite + @sprite = nil + @event = event + @viewport = viewport + @disposed = false + @surfbitmap = AnimatedBitmap.new("Graphics/Characters/base_surf") + @divebitmap = AnimatedBitmap.new("Graphics/Characters/base_dive") + @cws = @surfbitmap.width/4 + @chs = @surfbitmap.height/4 + @cwd = @divebitmap.width/4 + @chd = @divebitmap.height/4 + update + end + + def dispose + if !@disposed + @sprite.dispose if @sprite + @sprite = nil + @surfbitmap.dispose + @disposed = true + end + end + + def disposed? + @disposed + end + + def visible=(value) + @visible = value + @sprite.visible = value if @sprite && !@sprite.disposed? + end + + def update + return if disposed? + if !$PokemonGlobal.surfing && !$PokemonGlobal.diving + # Just-in-time disposal of sprite + if @sprite + @sprite.dispose + @sprite = nil + end + return + end + # Just-in-time creation of sprite + @sprite = Sprite.new(@viewport) if !@sprite + if @sprite + if $PokemonGlobal.surfing + @sprite.bitmap = @surfbitmap.bitmap; cw = @cws; ch = @chs + elsif $PokemonGlobal.diving + @sprite.bitmap = @divebitmap.bitmap; cw = @cwd; ch = @chd + end + sx = @event.pattern_surf*cw + sy = ((@event.direction-2)/2)*ch + @sprite.src_rect.set(sx,sy,cw,ch) + if $PokemonTemp.surfJump + @sprite.x = ($PokemonTemp.surfJump[0]*Game_Map::REAL_RES_X-@event.map.display_x+3)/4+(Game_Map::TILE_WIDTH/2) + @sprite.y = ($PokemonTemp.surfJump[1]*Game_Map::REAL_RES_Y-@event.map.display_y+3)/4+(Game_Map::TILE_HEIGHT/2)+16 + else + @sprite.x = @rsprite.x + @sprite.y = @rsprite.y + end + @sprite.ox = cw/2 + @sprite.oy = ch-16 # Assume base needs offsetting + @sprite.oy -= @event.bob_height + @sprite.z = @event.screen_z(ch)-1 + @sprite.zoom_x = @rsprite.zoom_x + @sprite.zoom_y = @rsprite.zoom_y + @sprite.tone = @rsprite.tone + @sprite.color = @rsprite.color + @sprite.opacity = @rsprite.opacity + end + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/006_Spriteset_Map.rb b/Data/Scripts/004_Sprites/006_Spriteset_Map.rb new file mode 100644 index 000000000..4578f7111 --- /dev/null +++ b/Data/Scripts/004_Sprites/006_Spriteset_Map.rb @@ -0,0 +1,167 @@ +class ClippableSprite < Sprite_Character + def initialize(viewport,event,tilemap) + @tilemap = tilemap + @_src_rect = Rect.new(0,0,0,0) + super(viewport,event) + end + + def update + super + @_src_rect = self.src_rect + tmright = @tilemap.map_data.xsize*Game_Map::TILE_WIDTH-@tilemap.ox + echoln("x=#{self.x},ox=#{self.ox},tmright=#{tmright},tmox=#{@tilemap.ox}") + if @tilemap.ox-self.ox<-self.x + # clipped on left + diff = -self.x-@tilemap.ox+self.ox + self.src_rect = Rect.new(@_src_rect.x+diff,@_src_rect.y, + @_src_rect.width-diff,@_src_rect.height) + echoln("clipped out left: #{diff} #{@tilemap.ox-self.ox} #{self.x}") + elsif tmright-self.ox0 + @weather.max -= 2 + if @weather.max<=0 + @weather.max = 0 + @weather.type = 0 + @weather.ox = 0 + @weather.oy = 0 + end + end + else + @weather.type = $game_screen.weather_type + @weather.max = $game_screen.weather_max + @weather.ox = tmox + @weather.oy = tmoy + end + @weather.update + @@viewport1.tone = $game_screen.tone + @@viewport3.color = $game_screen.flash_color + @@viewport1.update + @@viewport3.update + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/007_Spriteset_Global.rb b/Data/Scripts/004_Sprites/007_Spriteset_Global.rb new file mode 100644 index 000000000..111434113 --- /dev/null +++ b/Data/Scripts/004_Sprites/007_Spriteset_Global.rb @@ -0,0 +1,34 @@ +class Spriteset_Global + attr_reader :playersprite + @@viewport2 = Viewport.new(0,0,Graphics.width,Graphics.height) + @@viewport2.z = 200 + + def initialize + @playersprite = Sprite_Character.new(Spriteset_Map.viewport,$game_player) + @picture_sprites = [] + for i in 1..100 + @picture_sprites.push(Sprite_Picture.new(@@viewport2,$game_screen.pictures[i])) + end + @timer_sprite = Sprite_Timer.new + update + end + + def dispose + @playersprite.dispose + for sprite in @picture_sprites + sprite.dispose + end + @timer_sprite.dispose + @playersprite = nil + @picture_sprites.clear + @timer_sprite = nil + end + + def update + @playersprite.update + for sprite in @picture_sprites + sprite.update + end + @timer_sprite.update + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/008_Sprite_AnimationSprite.rb b/Data/Scripts/004_Sprites/008_Sprite_AnimationSprite.rb new file mode 100644 index 000000000..432c10621 --- /dev/null +++ b/Data/Scripts/004_Sprites/008_Sprite_AnimationSprite.rb @@ -0,0 +1,94 @@ +=begin +A sprite whose sole purpose is to display an animation. This sprite +can be displayed anywhere on the map and is disposed +automatically when its animation is finished. +Used for grass rustling and so forth. +=end +class AnimationSprite < RPG::Sprite + def initialize(animID,map,tileX,tileY,viewport=nil,tinting=false,height=3) + super(viewport) + @tileX = tileX + @tileY = tileY + self.bitmap = Bitmap.new(1, 1) + self.bitmap.clear + @map = map + setCoords + pbDayNightTint(self) if tinting + self.animation($data_animations[animID],true,height) + end + + def setCoords + self.x = ((@tileX * Game_Map::REAL_RES_X - @map.display_x) / Game_Map::X_SUBPIXELS).ceil + self.x += Game_Map::TILE_WIDTH / 2 + self.y = ((@tileY * Game_Map::REAL_RES_Y - @map.display_y) / Game_Map::Y_SUBPIXELS).ceil + self.y += Game_Map::TILE_HEIGHT + end + + def dispose + self.bitmap.dispose + super + end + + def update + if !self.disposed? + setCoords + super + self.dispose if !self.effect? + end + end +end + + + +class Spriteset_Map + alias _animationSprite_initialize initialize + alias _animationSprite_update update + alias _animationSprite_dispose dispose + + def initialize(map=nil) + @usersprites=[] + _animationSprite_initialize(map) + end + + def addUserAnimation(animID,x,y,tinting=false,height=3) + sprite=AnimationSprite.new(animID,$game_map,x,y,@@viewport1,tinting,height) + addUserSprite(sprite) + return sprite + end + + def addUserSprite(sprite) + for i in 0...@usersprites.length + if @usersprites[i]==nil || @usersprites[i].disposed? + @usersprites[i]=sprite + return + end + end + @usersprites.push(sprite) + end + + def dispose + _animationSprite_dispose + for i in 0...@usersprites.length + @usersprites[i].dispose + end + @usersprites.clear + end + + def update + return if @tilemap.disposed? + if $RPGVX || $PokemonSystem.tilemap==0 + if self.map==$game_map + pbDayNightTint(@@viewport3) + else + @@viewport3.tone.set(0,0,0,0) + end + else + pbDayNightTint(@tilemap) + @@viewport3.tone.set(0,0,0,0) + end + _animationSprite_update + for i in 0...@usersprites.length + @usersprites[i].update if !@usersprites[i].disposed? + end + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/009_Sprite_DynamicShadows.rb b/Data/Scripts/004_Sprites/009_Sprite_DynamicShadows.rb new file mode 100644 index 000000000..7a2c8d242 --- /dev/null +++ b/Data/Scripts/004_Sprites/009_Sprite_DynamicShadows.rb @@ -0,0 +1,255 @@ +#=============================================================================== +# Sprite_Shadow (Sprite_Ombre ) +# Based on Genzai Kawakami's shadows, dynamisme & features by Rataime, extra +# features Boushy +# Modified by Peter O. to be compatible with Pokémon Essentials +#=============================================================================== +SHADOW_WARN = true + +class Sprite_Shadow < RPG::Sprite + attr_accessor :character + + def initialize(viewport, character = nil,params=[]) + super(viewport) + @source = params[0] + @anglemin = (params.size>1) ? params[1] : 0 + @anglemax = (params.size>2) ? params[2] : 0 + @self_opacity = (params.size>4) ? params[4] : 100 + @distancemax = (params.size>3) ? params[3] : 350 + @character = character + update + end + + def dispose + @chbitmap.dispose if @chbitmap + super + end + + def update + if !in_range?(@character, @source, @distancemax) + self.opacity = 0 + return + end + super + if @tile_id != @character.tile_id or + @character_name != @character.character_name or + @character_hue != @character.character_hue + @tile_id = @character.tile_id + @character_name = @character.character_name + @character_hue = @character.character_hue + if @tile_id >= 384 + @chbitmap.dispose if @chbitmap + @chbitmap = pbGetTileBitmap(@character.map.tileset_name, + @tile_id, @character.character_hue) + self.src_rect.set(0, 0, 32, 32) + @ch = 32 + @cw = 32 + self.ox = 16 + self.oy = 32 + else + @chbitmap.dispose if @chbitmap + @chbitmap = AnimatedBitmap.new( + "Graphics/Characters/"+@character.character_name,@character.character_hue) + @cw = @chbitmap.width / 4 + @ch = @chbitmap.height / 4 + self.ox = @cw / 2 + self.oy = @ch + end + end + if @chbitmap.is_a?(AnimatedBitmap) + @chbitmap.update + self.bitmap = @chbitmap.bitmap + else + self.bitmap = @chbitmap + end + self.visible = (not @character.transparent) + if @tile_id == 0 + sx = @character.pattern * @cw + sy = (@character.direction - 2) / 2 * @ch + if self.angle > 90 or angle < -90 + case @character.direction + when 2; sy = (8- 2) / 2 * @ch + when 4; sy = (6- 2) / 2 * @ch + when 6; sy = (4- 2) / 2 * @ch + when 8; sy = (2- 2) / 2 * @ch + end + end + self.src_rect.set(sx, sy, @cw, @ch) + end + self.x = ScreenPosHelper.pbScreenX(@character) + self.y = ScreenPosHelper.pbScreenY(@character)-5 + self.z = ScreenPosHelper.pbScreenZ(@character,@ch)-1 + self.zoom_x = ScreenPosHelper.pbScreenZoomX(@character) + self.zoom_y = ScreenPosHelper.pbScreenZoomY(@character) + self.blend_type = @character.blend_type + self.bush_depth = @character.bush_depth + if @character.animation_id != 0 + animation = $data_animations[@character.animation_id] + animation(animation, true) + @character.animation_id = 0 + end + @deltax = ScreenPosHelper.pbScreenX(@source) - self.x + @deltay = ScreenPosHelper.pbScreenY(@source) - self.y + self.color = Color.new(0, 0, 0) + @distance = ((@deltax ** 2) + (@deltay ** 2)) + self.opacity = @self_opacity * 13000 / ((@distance * 370 / @distancemax) + 6000) + self.angle = 57.3 * Math.atan2(@deltax, @deltay) + @angle_trigo = self.angle+90 + @angle_trigo += 360 if @angle_trigo < 0 + if @anglemin != 0 or @anglemax != 0 + if (@angle_trigo < @anglemin or @angle_trigo > @anglemax) and @anglemin < @anglemax + self.opacity = 0 + return + end + if (@angle_trigo < @anglemin and @angle_trigo > @anglemax) and @anglemin > @anglemax + self.opacity = 0 + return + end + end + end + + def in_range?(element, object, range) # From Near's Anti Lag Script, edited + elemScreenX = ScreenPosHelper.pbScreenX(element) + elemScreenY = ScreenPosHelper.pbScreenY(element) + objScreenX = ScreenPosHelper.pbScreenX(object) + objScreenY = ScreenPosHelper.pbScreenY(object) + x = (elemScreenX - objScreenX) * (elemScreenX - objScreenX) + y = (elemScreenY - objScreenY) * (elemScreenY - objScreenY) + r = x + y + return r <= range * range + end +end + + + +#=================================================== +# ? CLASS Sprite_Character edit +#=================================================== +class Sprite_Character < RPG::Sprite + alias :shadow_initialize :initialize + + def initialize(viewport, character = nil) + @ombrelist = [] + @character = character + shadow_initialize(viewport, @character) + end + + def setShadows(map,shadows) + if character.is_a?(Game_Event) and shadows.length > 0 + params = XPML_read(map,"Shadow",@character,4) + if params != nil + for i in 0...shadows.size + @ombrelist.push(Sprite_Shadow.new(viewport, @character, shadows[i])) + end + end + end + if character.is_a?(Game_Player) and shadows.length > 0 + for i in 0...shadows.size + @ombrelist.push(Sprite_Shadow.new(viewport, $game_player, shadows[i])) + end + end + update + end + + alias shadow_update update + + def update + shadow_update + if @ombrelist.length>0 + for i in 0...@ombrelist.size + @ombrelist[i].update + end + end + end +end + + + +#=================================================== +# ? CLASS Game_Event edit +#=================================================== +class Game_Event + attr_accessor :id +end + + + +#=================================================== +# ? CLASS Spriteset_Map edit +#=================================================== +class Spriteset_Map + attr_accessor :shadows + + alias shadow_initialize initialize + def initialize(map=nil) + @shadows = [] + warn = false + map = $game_map if !map + for k in map.events.keys.sort + ev = map.events[k] + warn = true if (ev.list != nil and ev.list.length > 0 and + ev.list[0].code == 108 and + (ev.list[0].parameters == ["s"] or ev.list[0].parameters == ["o"])) + params = XPML_read(map,"Shadow Source", ev, 4) + @shadows.push([ev] + params) if params != nil + end + if warn == true and SHADOW_WARN + p "Warning : At least one event on this map uses the obsolete way to add shadows" + end + shadow_initialize(map) + for sprite in @character_sprites + sprite.setShadows(map, @shadows) + end + $scene.spritesetGlobal.playersprite.setShadows(map, @shadows) + end +end + + + +#=================================================== +# ? XPML Definition, by Rataime, using ideas from Near Fantastica +# +# Returns nil if the markup wasn't present at all, +# returns [] if there wasn't any parameters, else +# returns a parameters list with "int" converted as int +# eg : +# begin first +# begin second +# param1 1 +# param2 two +# begin third +# anything 3 +# +# p XPML_read("first", event_id) -> [] +# p XPML_read("second", event_id) -> [1, "two"] +# p XPML_read("third", event_id) -> [3] +# p XPML_read("forth", event_id) -> nil +#=================================================== +def XPML_read(map,markup,event,max_param_number=0) + parameter_list = nil + return nil if !event || event.list == nil + for i in 0...event.list.size + if event.list[i].code == 108 and + event.list[i].parameters[0].downcase == "begin " + markup.downcase + parameter_list = [] if parameter_list == nil + for j in i+1...event.list.size + if event.list[j].code == 108 + parts = event.list[j].parameters[0].split + if parts.size != 1 and parts[0].downcase != "begin" + if parts[1].to_i != 0 or parts[1] == "0" + parameter_list.push(parts[1].to_i) + else + parameter_list.push(parts[1]) + end + else + return parameter_list + end + else + return parameter_list + end + return parameter_list if max_param_number != 0 and j == i + max_param_number + end + end + end + return parameter_list +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/010_ParticleEngine.rb b/Data/Scripts/004_Sprites/010_ParticleEngine.rb new file mode 100644 index 000000000..61b348dad --- /dev/null +++ b/Data/Scripts/004_Sprites/010_ParticleEngine.rb @@ -0,0 +1,586 @@ +# Particle Engine, Peter O., 2007-11-03 +# Based on version 2 by Near Fantastica, 04.01.06 +# In turn based on the Particle Engine designed by PinkMan +class Particle_Engine + def initialize(viewport=nil,map=nil) + @map = (map) ? map : $game_map + @viewport = viewport + @effect = [] + @disposed = false + @firsttime = true + @effects = { + # PinkMan's Effects + "fire" => Particle_Engine::Fire, + "smoke" => Particle_Engine::Smoke, + "teleport" => Particle_Engine::Teleport, + "spirit" => Particle_Engine::Spirit, + "explosion" => Particle_Engine::Explosion, + "aura" => Particle_Engine::Aura, + # BlueScope's Effects + "soot" => Particle_Engine::Soot, + "sootsmoke" => Particle_Engine::SootSmoke, + "rocket" => Particle_Engine::Rocket, + "fixteleport" => Particle_Engine::FixedTeleport, + "smokescreen" => Particle_Engine::Smokescreen, + "flare" => Particle_Engine::Flare, + "splash" => Particle_Engine::Splash, + # By Peter O. + "starteleport" => Particle_Engine::StarTeleport + } + end + + def dispose + return if disposed? + for particle in @effect + next if particle.nil? + particle.dispose + end + @effect.clear + @map = nil + @disposed = true + end + + def disposed? + return @disposed + end + + def add_effect(event) + @effect[event.id] = pbParticleEffect(event) + end + + def remove_effect(event) + return if @effect[event.id].nil? + @effect[event.id].dispose + @effect.delete_at(event.id) + end + + def realloc_effect(event,particle) + type = pbEventCommentInput(event, 1, "Particle Engine Type") + if type.nil? + particle.dispose if particle + return nil + end + type = type[0].downcase + cls = @effects[type] + if cls.nil? + particle.dispose if particle + return nil + end + if !particle || !particle.is_a?(cls) + particle.dispose if particle + particle = cls.new(event,@viewport) + end + return particle + end + + def pbParticleEffect(event) + return realloc_effect(event,nil) + end + + def update + if @firsttime + @firsttime = false + for event in @map.events.values + remove_effect(event) + add_effect(event) + end + end + for i in 0...@effect.length + particle = @effect[i] + next if particle.nil? + if particle.event.pe_refresh + event = particle.event + event.pe_refresh = false + particle = realloc_effect(event,particle) + @effect[i] = particle + end + particle.update if particle + end + end +end + + + +class ParticleEffect + attr_accessor :x, :y, :z + + def initialize + @x = 0 + @y = 0 + @z = 0 + end + + def update; end + def dispose; end +end + + + +class ParticleSprite + attr_accessor :x, :y, :z, :ox, :oy, :opacity, :bitmap, :blend_type + + def initialize(viewport) + @viewport = viewport + @sprite = nil + @x = 0 + @y = 0 + @z = 0 + @ox = 0 + @oy = 0 + @opacity = 255 + @bitmap = nil + @blend_type = 0 + @minleft = 0 + @mintop = 0 + end + + def dispose + @sprite.dispose if @sprite + end + + def bitmap=(value) + @bitmap = value + if value + @minleft = -value.width + @mintop = -value.height + else + @minleft = 0 + @mintop = 0 + end + end + + def update + w = Graphics.width + h = Graphics.height + if !@sprite && @x>=@minleft && @y>=@mintop && @x=w || @y>=h) + @sprite.dispose + @sprite = nil + end + if @sprite + @sprite.x = @x if @sprite.x!=@x + @sprite.x -= @ox + @sprite.y = @y if @sprite.y!=@y + @sprite.y -= @oy + @sprite.z = @z if @sprite.z!=@z + @sprite.opacity = @opacity if @sprite.opacity!=@opacity + @sprite.blend_type = @blend_type if @sprite.blend_type!=@blend_type + @sprite.bitmap = @bitmap if @sprite.bitmap!=@bitmap + end + end +end + + + +class ParticleEffect_Event < ParticleEffect + attr_accessor :event + + def initialize(event,viewport=nil) + @event = event + @viewport = viewport + @particles = [] + @bitmaps = {} + end + + def setParameters(params) + @randomhue,@leftright,@fade, + @maxparticless,@hue,@slowdown, + @ytop,@ybottom,@xleft,@xright, + @xgravity,@ygravity,@xoffset,@yoffset, + @opacityvar,@originalopacity = params + end + + def loadBitmap(filename,hue) + key = [filename,hue] + bitmap = @bitmaps[key] + if !bitmap || bitmap.disposed? + bitmap = AnimatedBitmap.new("Graphics/Fogs/"+filename,hue).deanimate + @bitmaps[key] = bitmap + end + return bitmap + end + + def initParticles(filename,opacity,zOffset=0,blendtype=1) + @particles = [] + @particlex = [] + @particley = [] + @opacity = [] + @startingx = self.x + @xoffset + @startingy = self.y + @yoffset + @screen_x = self.x + @screen_y = self.y + @real_x = @event.real_x + @real_y = @event.real_y + @filename = filename + @zoffset = zOffset + @bmwidth = 32 + @bmheight = 32 + for i in 0...@maxparticless + @particlex[i] = -@xoffset + @particley[i] = -@yoffset + @particles[i] = ParticleSprite.new(@viewport) + @particles[i].bitmap = loadBitmap(filename, @hue) if filename + if i==0 && @particles[i].bitmap + @bmwidth = @particles[i].bitmap.width + @bmheight = @particles[i].bitmap.height + end + @particles[i].blend_type = blendtype + @particles[i].y = @startingy + @particles[i].x = @startingx + @particles[i].z = self.z+zOffset + @opacity[i] = rand(opacity/4) + @particles[i].opacity = @opacity[i] + @particles[i].update + end + end + + def x; return ScreenPosHelper.pbScreenX(@event); end + def y; return ScreenPosHelper.pbScreenY(@event); end + def z; return ScreenPosHelper.pbScreenZ(@event); end + + def update + if @viewport && + (@viewport.rect.x >= Graphics.width || + @viewport.rect.y >= Graphics.height) + return + end + selfX = self.x + selfY = self.y + selfZ = self.z + newRealX = @event.real_x + newRealY = @event.real_y + @startingx = selfX + @xoffset + @startingy = selfY + @yoffset + @__offsetx = (@real_x==newRealX) ? 0 : selfX-@screen_x + @__offsety = (@real_y==newRealY) ? 0 : selfY-@screen_y + @screen_x = selfX + @screen_y = selfY + @real_x = newRealX + @real_y = newRealY + if @opacityvar>0 && @viewport + opac = 255.0/@opacityvar + minX = opac*(-@xgravity*1.0 / @slowdown).floor + @startingx + maxX = opac*(@xgravity*1.0 / @slowdown).floor + @startingx + minY = opac*(-@ygravity*1.0 / @slowdown).floor + @startingy + maxY = @startingy + minX -= @bmwidth + minY -= @bmheight + maxX += @bmwidth + maxY += @bmheight + if maxX<0 || maxY<0 || minX>=Graphics.width || minY>=Graphics.height +# echo "skipped" + return + end + end + particleZ = selfZ+@zoffset + for i in 0...@maxparticless + @particles[i].z = particleZ + if @particles[i].y <= @ytop + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + if @particles[i].x <= @xleft + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + if @particles[i].y >= @ybottom + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + if @particles[i].x >= @xright + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + if @fade == 0 + if @opacity[i] <= 0 + @opacity[i] = @originalopacity + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + else + if @opacity[i] <= 0 + @opacity[i] = 250 + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + end + calcParticlePos(i) + if @randomhue == 1 + @hue += 0.5 + @hue = 0 if @hue >= 360 + @particles[i].bitmap = loadBitmap(@filename, @hue) if @filename + end + @opacity[i] = @opacity[i] - rand(@opacityvar) + @particles[i].opacity = @opacity[i] + @particles[i].update + end + end + + def calcParticlePos(i) + @leftright = rand(2) + if @leftright == 1 + xo = -@xgravity*1.0 / @slowdown + else + xo = @xgravity*1.0 / @slowdown + end + yo = -@ygravity*1.0 / @slowdown + @particlex[i] += xo + @particley[i] += yo + @particlex[i] -= @__offsetx + @particley[i] -= @__offsety + @particlex[i] = @particlex[i].floor + @particley[i] = @particley[i].floor + @particles[i].x = @particlex[i]+@startingx+@xoffset + @particles[i].y = @particley[i]+@startingy+@yoffset + end + + def dispose + for particle in @particles + particle.dispose + end + for bitmap in @bitmaps.values + bitmap.dispose + end + @particles.clear + @bitmaps.clear + end +end + + + +class Particle_Engine::Fire < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,20,40,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-13,30,0]) + initParticles("particle",250) + end +end + + + +class Particle_Engine::Smoke < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,80,20,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-15,5,80]) + initParticles("smoke",250) + end +end + + + +class Particle_Engine::Teleport < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([1,1,1,10,rand(360),1,-64, + Graphics.height,-64,Graphics.width,0,3,-8,-15,20,0]) + initParticles("wideportal",250) + for i in 0...@maxparticless + @particles[i].ox = 16 + @particles[i].oy = 16 + end + end +end + + + +class Particle_Engine::Spirit < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([1,0,1,20,rand(360),0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-13,30,0]) + initParticles("particle",250) + end +end + + + +class Particle_Engine::Explosion < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,20,0,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-13,30,0]) + initParticles("explosion",250) + end +end + + + +class Particle_Engine::Aura < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,20,0,1,-64, + Graphics.height,-64,Graphics.width,2,2,-5,-13,30,0]) + initParticles("particle",250) + end +end + + + +class Particle_Engine::Soot < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,20,0,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-15,5,80]) + initParticles("smoke",100,0,2) + end +end + + + +class Particle_Engine::SootSmoke < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,30,0,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-15,5,80]) + initParticles("smoke",100,0) + for i in 0...@maxparticless + @particles[i].blend_type = rand(6) < 3 ? 1 : 2 + end + end +end + + + +class Particle_Engine::Rocket < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,60,0,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0,-5,-15,5,80]) + initParticles("smoke",100,-1) + end +end + + + +class Particle_Engine::FixedTeleport < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([1,0,1,10,rand(360),1, + -Graphics.height,Graphics.height,0,Graphics.width,0,3,-8,-15,20,0]) + initParticles("wideportal",250) + for i in 0...@maxparticless + @particles[i].ox = 16 + @particles[i].oy = 16 + end + end +end + + + +# By Peter O. +class Particle_Engine::StarTeleport < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,10,0,1, + -Graphics.height,Graphics.height,0,Graphics.width,0,3,-8,-15,10,0]) + initParticles("star",250) + for i in 0...@maxparticless + @particles[i].ox = 48 + @particles[i].oy = 48 + end + end +end + + + +class Particle_Engine::Smokescreen < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,250,0,0.2,-64, + Graphics.height,-64,Graphics.width,0.8,0.8,-5,-15,5,80]) + initParticles(nil,100) + for i in 0...@maxparticless + rnd = rand(3) + @opacity[i] = (rnd==0) ? 1 : 100 + filename = (rnd==0) ? "explosionsmoke" : "smoke" + @particles[i].bitmap = loadBitmap(filename, @hue) + end + end + + def calcParticlePos(i) + if @randomhue==1 + filename = (rand(3)==0) ? "explosionsmoke" : "smoke" + @particles[i].bitmap = loadBitmap(filename, @hue) + end + multiple = 1.7 + xgrav = @xgravity*multiple/@slowdown + xgrav = -xgrav if (rand(2)==1) + ygrav = @ygravity*multiple/@slowdown + ygrav = -ygrav if (rand(2)==1) + @particlex[i] += xgrav + @particley[i] += ygrav + @particlex[i] -= @__offsetx + @particley[i] -= @__offsety + @particlex[i] = @particlex[i].floor + @particley[i] = @particley[i].floor + @particles[i].x = @particlex[i]+@startingx+@xoffset + @particles[i].y = @particley[i]+@startingy+@yoffset + end +end + + + +class Particle_Engine::Flare < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,30,10,1,-64, + Graphics.height,-64,Graphics.width,2,2,-5,-12,30,0]) + initParticles("particle",255) + end +end + + + +class Particle_Engine::Splash < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,30,255,1,-64, + Graphics.height,-64,Graphics.width,4,2,-5,-12,30,0]) + initParticles("smoke",50) + end + + def update + super + for i in 0...@maxparticless + @particles[i].opacity = 50 + @particles[i].update + end + end +end + + + +class Game_Event < Game_Character + attr_accessor :pe_refresh + + alias nf_particles_game_map_initialize initialize + def initialize(map_id,event,map=nil) + @pe_refresh = false + begin + nf_particles_game_map_initialize(map_id, event, map) + rescue ArgumentError + nf_particles_game_map_initialize(map_id, event) + end + end + + alias nf_particles_game_map_refresh refresh + def refresh + nf_particles_game_map_refresh + @pe_refresh = true + end +end \ No newline at end of file diff --git a/Data/Scripts/005_Map renderer/001_Tilemap_XP.rb b/Data/Scripts/005_Map renderer/001_Tilemap_XP.rb new file mode 100644 index 000000000..c027af1b8 --- /dev/null +++ b/Data/Scripts/005_Map renderer/001_Tilemap_XP.rb @@ -0,0 +1,947 @@ +#=============================================================================== +# +#=============================================================================== +class CustomTilemapAutotiles + attr_accessor :changed + + def initialize + @changed = true + @tiles = [nil,nil,nil,nil,nil,nil,nil] + end + + def [](i) + return @tiles[i] + end + + def []=(i,value) + @tiles[i] = value + @changed = true + end +end + + + +#Console::setup_console +class CustomTilemapSprite < Sprite +end + + + +#=============================================================================== +# +#=============================================================================== +class CustomTilemap + attr_reader :tileset + attr_reader :autotiles + attr_reader :map_data + attr_reader :flash_data + attr_reader :priorities + attr_reader :terrain_tags + attr_reader :visible + attr_reader :viewport + attr_reader :graphicsWidth + attr_reader :graphicsHeight + attr_accessor :ox + attr_accessor :oy + attr_accessor :tone + attr_accessor :color + + Autotiles = [ + [ [27, 28, 33, 34], [ 5, 28, 33, 34], [27, 6, 33, 34], [ 5, 6, 33, 34], + [27, 28, 33, 12], [ 5, 28, 33, 12], [27, 6, 33, 12], [ 5, 6, 33, 12] ], + [ [27, 28, 11, 34], [ 5, 28, 11, 34], [27, 6, 11, 34], [ 5, 6, 11, 34], + [27, 28, 11, 12], [ 5, 28, 11, 12], [27, 6, 11, 12], [ 5, 6, 11, 12] ], + [ [25, 26, 31, 32], [25, 6, 31, 32], [25, 26, 31, 12], [25, 6, 31, 12], + [15, 16, 21, 22], [15, 16, 21, 12], [15, 16, 11, 22], [15, 16, 11, 12] ], + [ [29, 30, 35, 36], [29, 30, 11, 36], [ 5, 30, 35, 36], [ 5, 30, 11, 36], + [39, 40, 45, 46], [ 5, 40, 45, 46], [39, 6, 45, 46], [ 5, 6, 45, 46] ], + [ [25, 30, 31, 36], [15, 16, 45, 46], [13, 14, 19, 20], [13, 14, 19, 12], + [17, 18, 23, 24], [17, 18, 11, 24], [41, 42, 47, 48], [ 5, 42, 47, 48] ], + [ [37, 38, 43, 44], [37, 6, 43, 44], [13, 18, 19, 24], [13, 14, 43, 44], + [37, 42, 43, 48], [17, 18, 47, 48], [13, 18, 43, 48], [ 1, 2, 7, 8] ] + ] + Animated_Autotiles_Frames = 5*Graphics.frame_rate/20 # Frequency of updating animated autotiles + FlashOpacity = [100,90,80,70,80,90] + + def initialize(viewport) + @tileset = nil # Refers to Map Tileset Name + @autotiles = CustomTilemapAutotiles.new + @map_data = nil # Refers to 3D Array Of Tile Settings + @flash_data = nil # Refers to 3D Array of Tile Flashdata + @priorities = nil # Refers to Tileset Priorities + @terrain_tags = nil # Refers to Tileset Terrain Tags + @visible = true # Refers to Tileset Visibleness + @ox = 0 # Bitmap Offsets + @oy = 0 # Bitmap Offsets + @plane = false + @haveGraphicsWH = (Graphics.width!=nil rescue false) + if @haveGraphicsWH + @graphicsWidth = Graphics.width + @graphicsHeight = Graphics.height + else + @graphicsWidth = 640 + @graphicsHeight = 480 + end + @tileWidth = Game_Map::TILE_WIDTH rescue 32 + @tileHeight = Game_Map::TILE_HEIGHT rescue 32 + @tileSrcWidth = 32 + @tileSrcHeight = 32 + @diffsizes = (@tileWidth!=@tileSrcWidth) || (@tileHeight!=@tileSrcHeight) + @tone = Tone.new(0,0,0,0) + @oldtone = Tone.new(0,0,0,0) + @color = Color.new(0,0,0,0) + @oldcolor = Color.new(0,0,0,0) + @selfviewport = Viewport.new(0,0,graphicsWidth,graphicsHeight) + @viewport = (viewport) ? viewport : @selfviewport + @tiles = [] + @autotileInfo = [] + @regularTileInfo = [] + @oldOx = 0 + @oldOy = 0 + @oldViewportOx = 0 + @oldViewportOy = 0 + @layer0 = CustomTilemapSprite.new(viewport) + @layer0.visible = true + @nowshown = false + @layer0.bitmap = Bitmap.new([graphicsWidth+320,1].max,[graphicsHeight+320,1].max) + @layer0.z = 0 + @layer0.ox = 0 + @layer0.oy = 0 + @oxLayer0 = 0 + @oyLayer0 = 0 + @flash = nil + @oxFlash = 0 + @oyFlash = 0 + @priotiles = [] + @priotilesfast = [] + @prioautotiles = [] + @autosprites = [] + @framecount = [0,0,0,0,0,0,0,0] # For autotiles + @tilesetChanged = true + @flashChanged = false + @firsttime = true + @disposed = false + @usedsprites = false + @layer0clip = true + @firsttimeflash = true + @fullyrefreshed = false + @fullyrefreshedautos = false + end + + def dispose + return if disposed? + @help.dispose if @help + @help = nil + i = 0; len = @autotileInfo.length; while i=xsize + xEnd = (@ox+@viewport.rect.width)/@tileWidth + 1 + xEnd = 0 if xEnd<0 + xEnd = xsize-1 if xEnd>=xsize + return false if xStart>=xEnd + ysize = @map_data.ysize + yStart = @oy/@tileHeight - 1 + yStart = 0 if yStart<0 + yStart = ysize-1 if yStart>=ysize + yEnd = (@oy+@viewport.rect.height)/@tileHeight + 1 + yEnd = 0 if yEnd<0 + yEnd = ysize-1 if yEnd>=ysize + return false if yStart>=yEnd + return true + end + + def autotileNumFrames(id) + autotile = @autotiles[id/48-1] + return 0 if !autotile || autotile.disposed? + frames = 1 + if autotile.height==@tileHeight + frames = autotile.width/@tileWidth + else + frames = autotile.width/(3*@tileWidth) + end + return frames + end + + def autotileFrame(id) + autotile = @autotiles[id/48-1] + return -1 if !autotile || autotile.disposed? + frames = 1 + if autotile.height==@tileHeight + frames = autotile.width/@tileWidth + else + frames = autotile.width/(3*@tileWidth) + end + return (Graphics.frame_count/Animated_Autotiles_Frames)%frames + end + + def repaintAutotiles + for i in 0...@autotileInfo.length + next if !@autotileInfo[i] + frame = autotileFrame(i) + @autotileInfo[i].clear + bltAutotile(@autotileInfo[i],0,0,i,frame) + end + end + + def bltAutotile(bitmap,x,y,id,frame) + return if frame<0 + autotile = @autotiles[id/48-1] + return if !autotile || autotile.disposed? + if autotile.height==@tileSrcHeight + anim = frame*@tileSrcWidth + src_rect = Rect.new(anim,0,@tileSrcWidth,@tileSrcHeight) + if @diffsizes + bitmap.stretch_blt(Rect.new(x,y,@tileWidth,@tileHeight),autotile,src_rect) + else + bitmap.blt(x,y,autotile,src_rect) + end + else + anim = frame*3*@tileSrcWidth + id %= 48 + tiles = Autotiles[id>>3][id&7] + src = Rect.new(0,0,0,0) + halfTileWidth = @tileWidth>>1 + halfTileHeight = @tileHeight>>1 + halfTileSrcWidth = @tileSrcWidth>>1 + halfTileSrcHeight = @tileSrcHeight>>1 + for i in 0...4 + tile_position = tiles[i] - 1 + src.set( (tile_position % 6)*halfTileSrcWidth + anim, + (tile_position / 6)*halfTileSrcHeight, halfTileSrcWidth, halfTileSrcHeight) + if @diffsizes + bitmap.stretch_blt( + Rect.new(i%2*halfTileWidth+x,i/2*halfTileHeight+y,halfTileWidth,halfTileHeight), + autotile,src) + else + bitmap.blt(i%2*halfTileWidth+x,i/2*halfTileHeight+y, autotile, src) + end + end + end + end + + def getAutotile(sprite,id) + frames = @framecount[id/48-1] + if frames<=1 + anim = 0 + else + anim = (Graphics.frame_count/Animated_Autotiles_Frames)%frames + end + return if anim<0 + bitmap = @autotileInfo[id] + if !bitmap + bitmap = Bitmap.new(@tileWidth,@tileHeight) + bltAutotile(bitmap,0,0,id,anim) + @autotileInfo[id] = bitmap + end + sprite.bitmap = bitmap if sprite.bitmap!=bitmap + end + + def getRegularTile(sprite,id) + if @diffsizes + bitmap = @regularTileInfo[id] + if !bitmap + bitmap = Bitmap.new(@tileWidth,@tileHeight) + rect = Rect.new(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + bitmap.stretch_blt(Rect.new(0,0,@tileWidth,@tileHeight),@tileset,rect) + @regularTileInfo[id] = bitmap + end + sprite.bitmap = bitmap if sprite.bitmap!=bitmap + else + sprite.bitmap = @tileset if sprite.bitmap!=@tileset + sprite.src_rect.set(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + end + end + + def addTile(tiles,count,xpos,ypos,id) + terrain = @terrain_tags[id] + priority = @priorities[id] + if id>=384 + if count>=tiles.length + sprite = CustomTilemapSprite.new(@viewport) + tiles.push(sprite,0) + else + sprite = tiles[count] + tiles[count+1] = 0 + end + sprite.visible = @visible + sprite.x = xpos + sprite.y = ypos + sprite.tone = @tone + sprite.color = @color + getRegularTile(sprite,id) + else + if count>=tiles.length + sprite = CustomTilemapSprite.new(@viewport) + tiles.push(sprite,1) + else + sprite = tiles[count] + tiles[count+1] = 1 + end + sprite.visible = @visible + sprite.x = xpos + sprite.y = ypos + sprite.tone = @tone + sprite.color = @color + getAutotile(sprite,id) + end + if PBTerrain.hasReflections?(terrain) + spriteZ = -100 + elsif $PokemonGlobal.bridge>0 && PBTerrain.isBridge?(terrain) + spriteZ = 1 + else + spriteZ = (priority==0) ? 0 : ypos+priority*32+32 + end + sprite.z = spriteZ + count += 2 + return count + end + + def refresh_flash + if @flash_data && !@flash + @flash = CustomTilemapSprite.new(viewport) + @flash.visible = true + @flash.z = 1 + @flash.tone = tone + @flash.color = color + @flash.blend_type = 1 + @flash.bitmap = Bitmap.new([graphicsWidth*2,1].max,[graphicsHeight*2,1].max) + @firsttimeflash = true + elsif !@flash_data && @flash + @flash.bitmap.dispose if @flash.bitmap + @flash.dispose + @flash = nil + @firsttimeflash = false + end + end + + def refreshFlashSprite + return if !@flash || @flash_data.nil? + ptX = @ox-@oxFlash + ptY = @oy-@oyFlash + if !@firsttimeflash && !@usedsprites && + ptX>=0 && ptX+@viewport.rect.width<=@flash.bitmap.width && + ptY>=0 && ptY+@viewport.rect.height<=@flash.bitmap.height + @flash.ox = 0 + @flash.oy = 0 + @flash.src_rect.set(ptX.round,ptY.round, + @viewport.rect.width,@viewport.rect.height) + return + end + width = @flash.bitmap.width + height = @flash.bitmap.height + bitmap = @flash.bitmap + ysize = @map_data.ysize + xsize = @map_data.xsize + zsize = @map_data.zsize + @firsttimeflash = false + @oxFlash = @ox-(width>>2) + @oyFlash = @oy-(height>>2) + @flash.ox = 0 + @flash.oy = 0 + @flash.src_rect.set(width>>2,height>>2, + @viewport.rect.width,@viewport.rect.height) + @flash.bitmap.clear + @oxFlash = @oxFlash.floor + @oyFlash = @oyFlash.floor + xStart = @oxFlash/@tileWidth + xStart = 0 if xStart<0 + yStart = @oyFlash/@tileHeight + yStart = 0 if yStart<0 + xEnd = xStart+(width/@tileWidth)+1 + yEnd = yStart+(height/@tileHeight)+1 + xEnd = xsize if xEnd>=xsize + yEnd = ysize if yEnd>=ysize + if xStart>8)&15 + g = (id>>4)&15 + b = (id)&15 + tmpcolor.set(r<<4,g<<4,b<<4) + bitmap.fill_rect(xpos,ypos,@tileWidth,@tileHeight,tmpcolor) + end + end + end + end + + def refresh_tileset + i = 0; len = @regularTileInfo.length; while i100 || ysize>100 + @fullyrefreshed = false + else + for z in 0...zsize + for y in 0...ysize + for x in 0...xsize + id = @map_data[x, y, z] + next if id==0 + next if @priorities[id]==0 && !PBTerrain.hasReflections?(@terrain_tags[id]) + @priotiles.push([x,y,z,id]) + end + end + end + @fullyrefreshed = true + end + end + + def refresh_autotiles + i = 0; len = @autotileInfo.length; while i=2 + @framecount[i] = numframes + end + if hasanimated + ysize = @map_data.ysize + xsize = @map_data.xsize + zsize = @map_data.zsize + if xsize>100 || ysize>100 + @fullyrefreshedautos = false + else + for y in 0...ysize + for x in 0...xsize + haveautotile = false + for z in 0...zsize + id = @map_data[x, y, z] + next if id==0 || id>=384 + next if @priorities[id]!=0 || PBTerrain.hasReflections?(@terrain_tags[id]) + next if @framecount[id/48-1]<2 + haveautotile = true + break + end + @prioautotiles.push([x,y]) if haveautotile + end + end + @fullyrefreshedautos = true + end + else + @fullyrefreshedautos = true + end + end + + def refreshLayer0(autotiles=false) + return true if autotiles && !shown? + ptX = @ox-@oxLayer0 + ptY = @oy-@oyLayer0 + if !autotiles && !@firsttime && !@usedsprites && + ptX>=0 && ptX+@viewport.rect.width<=@layer0.bitmap.width && + ptY>=0 && ptY+@viewport.rect.height<=@layer0.bitmap.height + if @layer0clip && @viewport.ox==0 && @viewport.oy==0 + @layer0.ox = 0 + @layer0.oy = 0 + @layer0.src_rect.set(ptX.round,ptY.round, + @viewport.rect.width,@viewport.rect.height) + else + @layer0.ox = ptX.round + @layer0.oy = ptY.round + @layer0.src_rect.set(0,0,@layer0.bitmap.width,@layer0.bitmap.height) + end + return true + end + width = @layer0.bitmap.width + height = @layer0.bitmap.height + bitmap = @layer0.bitmap + ysize = @map_data.ysize + xsize = @map_data.xsize + zsize = @map_data.zsize + twidth = @tileWidth + theight = @tileHeight + mapdata = @map_data + if autotiles + return true if @fullyrefreshedautos && @prioautotiles.length==0 + xStart = @oxLayer0/twidth + xStart = 0 if xStart<0 + yStart = @oyLayer0/theight + yStart = 0 if yStart<0 + xEnd = xStart+(width/twidth)+1 + yEnd = yStart+(height/theight)+1 + xEnd = xsize if xEnd>xsize + yEnd = ysize if yEnd>ysize + return true if xStart>=xEnd || yStart>=yEnd + trans = Color.new(0,0,0,0) + temprect = Rect.new(0,0,0,0) + tilerect = Rect.new(0,0,twidth,theight) + zrange = 0...zsize + overallcount = 0 + count = 0 + if !@fullyrefreshedautos + for y in yStart..yEnd + for x in xStart..xEnd + haveautotile = false + for z in zrange + id = mapdata[x, y, z] + next if !id || id<48 || id>=384 + prioid = @priorities[id] + next if prioid!=0 || PBTerrain.hasReflections?(@terrain_tags[id]) + fcount = @framecount[id/48-1] + next if !fcount || fcount<2 + if !haveautotile + haveautotile = true + overallcount += 1 + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + bitmap.fill_rect(xpos,ypos,twidth,theight,trans) if overallcount<=2000 + break + end + end + for z in zrange + id = mapdata[x,y,z] + next if !id || id<48 + prioid = @priorities[id] + next if prioid!=0 || PBTerrain.hasReflections?(@terrain_tags[id]) + if overallcount>2000 + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + count = addTile(@autosprites,count,xpos,ypos,id) + next + elsif id>=384 + temprect.set(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + if @diffsizes + bitmap.stretch_blt(Rect.new(xpos,ypos,twidth,theight),@tileset,temprect) + else + bitmap.blt(xpos,ypos,@tileset,temprect) + end + else + tilebitmap = @autotileInfo[id] + if !tilebitmap + anim = autotileFrame(id) + next if anim<0 + tilebitmap = Bitmap.new(twidth,theight) + bltAutotile(tilebitmap,0,0,id,anim) + @autotileInfo[id] = tilebitmap + end + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + bitmap.blt(xpos,ypos,tilebitmap,tilerect) + end + end + end + end + Graphics.frame_reset + else + if !@priorect || !@priorectautos || + @priorect[0]!=xStart || @priorect[1]!=yStart || + @priorect[2]!=xEnd || @priorect[3]!=yEnd + @priorectautos = @prioautotiles.find_all { |tile| + x = tile[0] + y = tile[1] + # "next" means "return" here + next !(xxEnd || yyEnd) + } + @priorect = [xStart,yStart,xEnd,yEnd] + end +# echoln ["autos",@priorect,@priorectautos.length,@prioautotiles.length] + for tile in @priorectautos + x = tile[0] + y = tile[1] + overallcount+=1 + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + bitmap.fill_rect(xpos,ypos,twidth,theight,trans) + z = 0 + while z=384 + temprect.set(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + if @diffsizes + bitmap.stretch_blt(Rect.new(xpos,ypos,twidth,theight),@tileset,temprect) + else + bitmap.blt(xpos,ypos,@tileset,temprect) + end + else + tilebitmap = @autotileInfo[id] + if !tilebitmap + anim = autotileFrame(id) + next if anim<0 + tilebitmap = Bitmap.new(twidth,theight) + bltAutotile(tilebitmap,0,0,id,anim) + @autotileInfo[id] = tilebitmap + end + bitmap.blt(xpos,ypos,tilebitmap,tilerect) + end + end + end + Graphics.frame_reset if overallcount>500 + end + @usedsprites = false + return true + end + return false if @usedsprites + @firsttime = false + @oxLayer0 = @ox-(width>>2) + @oyLayer0 = @oy-(height>>2) + if @layer0clip + @layer0.ox = 0 + @layer0.oy = 0 + @layer0.src_rect.set(width>>2,height>>2, + @viewport.rect.width,@viewport.rect.height) + else + @layer0.ox = (width>>2) + @layer0.oy = (height>>2) + end + @layer0.bitmap.clear + @oxLayer0 = @oxLayer0.round + @oyLayer0 = @oyLayer0.round + xStart = @oxLayer0/twidth + xStart = 0 if xStart<0 + yStart = @oyLayer0/theight + yStart = 0 if yStart<0 + xEnd = xStart+(width/twidth)+1 + yEnd = yStart+(height/theight)+1 + xEnd = xsize if xEnd>=xsize + yEnd = ysize if yEnd>=ysize + if xStart=384 + tmprect.set( ((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + if @diffsizes + bitmap.stretch_blt(Rect.new(xpos,ypos,twidth,theight),@tileset,tmprect) + else + bitmap.blt(xpos,ypos,@tileset,tmprect) + end + else + frames = @framecount[id/48-1] + if frames<=1 + frame = 0 + else + frame = (Graphics.frame_count/Animated_Autotiles_Frames)%frames + end + bltAutotile(bitmap,xpos,ypos,id,frame) + end + end + end + end + Graphics.frame_reset + end + return true + end + + def refresh(autotiles=false) + @oldOx = @ox + @oldOy = @oy + usesprites = false + if @layer0 + @layer0.visible = @visible + usesprites = !refreshLayer0(autotiles) + return if autotiles && !usesprites + else + usesprites = true + end + refreshFlashSprite + vpx = @viewport.rect.x + vpy = @viewport.rect.y + vpr = @viewport.rect.width+vpx + vpb = @viewport.rect.height+vpy + xsize = @map_data.xsize + ysize = @map_data.ysize + minX = (@ox/@tileWidth)-1 + minX = 0 if minX<0 + minX = xsize-1 if minX>=xsize + maxX = ((@ox+@viewport.rect.width)/@tileWidth)+1 + maxX = 0 if maxX<0 + maxX = xsize-1 if maxX>=xsize + minY = (@oy/@tileHeight)-1 + minY = 0 if minY<0 + minY = ysize-1 if minY>=ysize + maxY = ((@oy+@viewport.rect.height)/@tileHeight)+1 + maxY = 0 if maxY<0 + maxY = ysize-1 if maxY>=ysize + count = 0 + if minXmaxX || ymaxY) + } + @priotilesrect = [minX,minY,maxX,maxY] + end + # echoln [minX,minY,maxX,maxY,@priotilesfast.length,@priotiles.length] + for prio in @priotilesfast + xpos = (prio[0]*@tileWidth)-@ox + ypos = (prio[1]*@tileHeight)-@oy + count = addTile(@tiles,count,xpos,ypos,prio[3]) + end + else + if !@priotilesrect || !@priotilesfast || + @priotilesrect[0]!=minX || + @priotilesrect[1]!=minY || + @priotilesrect[2]!=maxX || + @priotilesrect[3]!=maxY + @priotilesfast=[] + for z in 0...@map_data.zsize + for y in minY..maxY + for x in minX..maxX + id = @map_data[x, y, z] + next if id==0 + next if @priorities[id]==0 && !PBTerrain.hasReflections?(@terrain_tags[id]) + @priotilesfast.push([x,y,z,id]) + end + end + end + @priotilesrect = [minX,minY,maxX,maxY] + end + for prio in @priotilesfast + xpos = (prio[0]*@tileWidth)-@ox + ypos = (prio[1]*@tileHeight)-@oy + count = addTile(@tiles,count,xpos,ypos,prio[3]) + end + end + end + if count<@tiles.length + bigchange = (count<=(@tiles.length*2/3)) && (@tiles.length*2/3)>25 + j = count; len = @tiles.length; while j=48 || !srcBitmap || srcBitmap.disposed? + anim=0 + cxTile=3 + cyTile=3 + tiles = TileDrawingHelper::Autotiles[id>>3][id&7] + src=Rect.new(0,0,0,0) + for i in 0...4 + tile_position = tiles[i] - 1 + src.set( + tile_position % 6 * cxTile + anim, + tile_position / 6 * cyTile, cxTile, cyTile) + dstBitmap.blt(i%2*cxTile+x,i/2*cyTile+y, srcBitmap, src) + end +end + +def passable?(passages,tile_id) + return false if tile_id == nil + passage = passages[tile_id] + return (passage && passage<15) +end + +def getPassabilityMinimap(mapid) + map = load_data(sprintf("Data/Map%03d.rxdata",mapid)) + tileset = $data_tilesets[map.tileset_id] + minimap = AnimatedBitmap.new("Graphics/Pictures/minimap_tiles") + ret = Bitmap.new(map.width*6,map.height*6) + passtable = Table.new(map.width,map.height) + passages = tileset.passages + for i in 0...map.width + for j in 0...map.height + pass=true + for z in [2,1,0] + if !passable?(passages,map.data[i,j,z]) + pass=false + break + end + end + passtable[i,j]=pass ? 1 : 0 + end + end + neighbors=TileDrawingHelper::NeighborsToTiles + for i in 0...map.width + for j in 0...map.height + if passtable[i,j]==0 + nb=TileDrawingHelper.tableNeighbors(passtable,i,j) + tile=neighbors[nb] + bltMinimapAutotile(ret,i*6,j*6,minimap.bitmap,tile) + end + end + end + minimap.disposes + return ret +end + + + +module ScreenPosHelper + def self.pbScreenZoomX(ch) + zoom=1.0 + if $PokemonSystem.tilemap==2 + zoom=((ch.screen_y - 16) - (Graphics.height / 2)) * + (Draw_Tilemap::Pitch*1.0 / (Graphics.height * 25)) + 1 + end + return zoom*Game_Map::TILE_WIDTH/32.0 + end + + def self.pbScreenZoomY(ch) + zoom=1.0 + if $PokemonSystem.tilemap==2 + zoom=((ch.screen_y - 16) - (Graphics.height / 2)) * + (Draw_Tilemap::Pitch*1.0 / (Graphics.height * 25)) + 1 + end + return zoom*Game_Map::TILE_HEIGHT/32.0 + end + + def self.pbScreenX(ch) + ret=ch.screen_x + if $PokemonSystem.tilemap==2 + widthdiv2=(Graphics.width / 2) + ret=widthdiv2+(ret-widthdiv2)*pbScreenZoomX(ch) + end + return ret + end + + def self.pbScreenY(ch) + ret=ch.screen_y + if $PokemonSystem.tilemap==2 && Draw_Tilemap::Curve && Draw_Tilemap::Pitch != 0 + zoomy=pbScreenZoomY(ch) + oneMinusZoomY=1-zoomy + ret += (8 * oneMinusZoomY * (oneMinusZoomY / + (2 * ((Draw_Tilemap::Pitch*1.0 / 100) / (Graphics.height*1.0 / 16.0))) + 0.5)) + end + return ret + end + + @heightcache={} + + def self.bmHeight(bm) + h=@heightcache[bm] + if !h + bmap=AnimatedBitmap.new("Graphics/Characters/"+bm,0) + h=bmap.height + @heightcache[bm]=h + bmap.dispose + end + return h + end + + def self.pbScreenZ(ch,height=nil) + if height==nil + height=0 + if ch.tile_id > 0 + height=32 + elsif ch.character_name!="" + height=bmHeight(ch.character_name)/4 + end + end + ret=ch.screen_z(height) + if $PokemonSystem.tilemap==2 + ret-=(pbScreenZoomY(ch) < 0.5 ? 1000 : 0) + end + return ret + end +end + +############################################### + + + +class Draw_Tilemap # This class controls a set of sprites, with + attr_reader :tileset # different Z values, arranged into horizontal bars + attr_reader :map_data + attr_reader :flash_data + attr_reader :priorities + attr_reader :terrain_tags + attr_reader :autotiles + attr_accessor :bitmaps + attr_accessor :pitch + attr_accessor :ox + attr_accessor :oy + attr_accessor :visible + attr_reader :viewport + attr_accessor :color + attr_accessor :tone + StripSize = 16 + Curve = true + Pitch = 3 + FlashOpacity = [100,90,80,70,80,90] + + def initialize(viewport=nil) + @tileset=nil + @map_data=nil + @priorities=nil + @terrain_tags=nil + @autotiles=[nil,nil,nil,nil,nil,nil,nil] + @viewport=viewport + @visible=true + @helper=TileDrawingHelper.new(nil,@autotiles) + @drawnstrips=[] + @contentstrips=[] + @disposed=false + @bitmaps=[] + @sprites=[] + @ox=0 + @oy=0 + @tone=Tone.new(0,0,0,0) + @color=Color.new(0,0,0,0) + @flash_data=nil + @numsprites=0 + end + + def tileset=(value) + @tileset=value + @helper.tileset=value + @doredraw=true + end + + def map_data=(value) + @map_data=value + @doredraw=true + end + + def flash_data=(value) + @flash_data=value + @doredraw=true + end + + def priorities=(value) + @priorities=value + @doredraw=true + end + + def terrain_tags=(value) + @terrain_tags=value + @doredraw=true + end + + def redrawmap + # Provide blank data in proper object form + self.clear + xsize=@map_data.xsize + ysize=@map_data.ysize + # Bitmaps used for each priority's drawing. Priorities 2-5 are combined. + @bitmaps = [Bitmap.new(xsize*32, ysize*32+StripSize), + Bitmap.new(xsize*32, ysize*32+StripSize), + Bitmap.new(xsize*32, ysize*32+StripSize)] + for i in @bitmaps + i.clear + end + if @flash_data + @bitmaps.push(Bitmap.new(xsize*32, ysize*32+StripSize)) + end + @drawnstrips.clear + @contentstrips.clear + # Generate blank sprites + @sprites.clear + @numsprites=ysize * (32 / StripSize) + for i in 0...@map_data.zsize # For each layer + @sprites.push([]) + @contentstrips.push([]) + end + if @flash_data + @sprites.push([]) + @contentstrips.push([]) + end + end + + def update + oyunchanged=false + if !@flash_data.nil? && @sprites.length>0 + flashindex=@sprites.length-1 + for j in 0...@numsprites + sprite=@sprites[flashindex][j] + next if !sprite.is_a?(Sprite) + sprite.opacity=FlashOpacity[(Graphics.frame_count/2) % 6] + end + end + for s in @sprites + for sprite in s + next if !sprite.is_a?(Sprite) + # sprite.tone=@tone + # sprite.color=@color + end + end + if @doredraw + @drawnstrips=[] + redrawmap + @doredraw=false + elsif @oldOx==@ox && @oldOy==@oy + return + elsif @oldOy==@oy + oyunchanged=true + end + @oldOx=@ox + @oldOy=@oy + @pitch = Pitch + minvalue=[0, ((Graphics.height / 2) - + ((Graphics.height * 60) / @pitch) + @oy) / StripSize].max.to_i + maxvalue=[@numsprites - 1,(@oy + Graphics.height) / StripSize].min.to_i + return if minvalue>maxvalue + for j in 0...@numsprites + if jmaxvalue + for i in 0...@sprites.length + sprite=@sprites[i][j] + if sprite + sprite.dispose if sprite.is_a?(Sprite) + @sprites[i][j]=nil + end + end + else + drawStrip(j) + end + end + vpy=@viewport.rect.y + vpr=@viewport.rect.x+@viewport.rect.width + vpb=@viewport.rect.y+@viewport.rect.height + numsprites=0 + for i in @sprites + numsprites+=i.compact.length + end + for j in minvalue..maxvalue + # For each strip within the visible screen, update OX/Y + x=Graphics.width/2 + sox=@ox+x + y = (j * StripSize - @oy) + zoom_x=1.0 + zoom_y=1.0 + unless @pitch == 0 # Apply X Zoom + zoom_x = (y - Graphics.height*1.0 / 2) * (@pitch*1.0 / (Graphics.height * 25)) + 1 + if Curve # Zoom Y values same as X, and compensate + zoom_y = zoom_x + yadd = StripSize*1.0 * (1 - zoom_y) * ((1 - zoom_y) / + (2 * ((@pitch*1.0 / 100) / (Graphics.height*1.0 / (StripSize * 2)))) + 0.5) + y+=yadd + end + end + xstart=(x-sox*zoom_x) + yend=(y+(StripSize*2)*zoom_y) + if xstart>vpr || yend<=vpy + for i in 0...@sprites.length + sprite=@sprites[i][j] + if sprite.is_a?(Sprite) + sprite.dispose + @sprites[i][j]=nil + end + end + else + for i in 0...@sprites.length + sprite=@sprites[i][j] + next if !sprite + if sprite==true + sprite=newSprite(i,j) + @sprites[i][j]=sprite + end + sprite.visible=@visible + sprite.x = x + sprite.ox = sox + sprite.y = y + sprite.zoom_x = zoom_x + sprite.zoom_y = zoom_y + end + end + end + end + + def clear + for i in @bitmaps + i.dispose + end + @bitmaps.clear + for i in 0...@sprites.length + for j in 0...@sprites[i].length + @sprites[i][j].dispose if @sprites[i][j].is_a?(Sprite) + end + @sprites[i].clear + end + @sprites.clear + end + + def dispose + return if @disposed + self.clear + for i in 0...7 + self.autotiles[i]=nil + end + @helper=nil + @sprites=nil + @bitmaps=nil + @disposed = true + end + + def disposed? + return @disposed + end + + def newSprite(i,j) + sprite=Sprite.new(@viewport) + sprite.bitmap=@bitmaps[i] + sprite.src_rect.set(0, j * StripSize, @map_data.xsize * 32, StripSize * 2) + sprite.x = Graphics.width / 2 + sprite.y = -64 + sprite.z = (i * 32) + sprite.tone=@tone + sprite.color=@color + if i==@bitmaps.length-1 && !@flash_data.nil? + sprite.blend_type=1 + sprite.z=1 + sprite.opacity=FlashOpacity[(Graphics.frame_count/2) % 6] + end + return sprite + end + + def drawStrip(j) + minY=(j*StripSize)/32 + maxY=(j*StripSize+StripSize*2)/32 + minY=0 if minY<0 + minY=@map_data.ysize-1 if minY>@map_data.ysize-1 + maxY=0 if maxY<0 + maxY=@map_data.ysize-1 if maxY>@map_data.ysize-1 + for y in minY..maxY + if !@drawnstrips[y] + for x in 0...@map_data.xsize + draw_position(x, y) + end + @drawnstrips[y]=true + end + end + for i in 0...@sprites.length # For each priority + sprite=@sprites[i][j] + if !sprite || (sprite!=true && sprite.disposed?) + havecontent=false + for y in minY..maxY + havecontent=havecontent||@contentstrips[i][y] + end + sprite=(havecontent) ? true : nil + @sprites[i][j]=sprite + end + end + end + + def draw_position(x, y) + for layer in 0...@map_data.zsize + pos = @map_data[x, y, layer] + priopos=@priorities[pos] + priopos=0 if !priopos + prio=(20 + @helper.bltTile(@bitmaps[prio],x*32,y*32,pos,0) + end + if !@flash_data.nil? + lastlayer=@bitmaps.length-1 + id=@flash_data[x,y,0] + r=(id>>8)&15 + g=(id>>4)&15 + b=(id)&15 + @contentstrips[lastlayer][y]=true + color=Color.new(r*16,g*16,b*16) + @bitmaps[lastlayer].fill_rect(x*32,y*32,32,32,color) + end + end +end + + + +class Sprite_Character + alias perspectivetilemap_initialize initialize + attr_accessor :character + + def initialize(viewport, character = nil) + @character = character + perspectivetilemap_initialize(viewport,character) + end + + alias update_or :update + + def update + update_or + if $PokemonSystem.tilemap==2 + self.zoom_y=ScreenPosHelper.pbScreenZoomY(@character) + self.zoom_x=ScreenPosHelper.pbScreenZoomX(@character) + self.x=ScreenPosHelper.pbScreenX(@character) + self.y=ScreenPosHelper.pbScreenY(@character) + self.z=ScreenPosHelper.pbScreenZ(@character,@ch) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/005_Map renderer/003_Tilemap_Original.rb b/Data/Scripts/005_Map renderer/003_Tilemap_Original.rb new file mode 100644 index 000000000..47cdc2c3c --- /dev/null +++ b/Data/Scripts/005_Map renderer/003_Tilemap_Original.rb @@ -0,0 +1,118 @@ +#=============================================================================== +# +#=============================================================================== +class SynchronizedTilemapAutotilesInternal + def initialize(oldat) + @atdisposables = [[],[],[],[],[],[],[]] + @atframes = [[],[],[],[],[],[],[]] + @atframe = [-1,-1,-1,-1,-1,-1,-1] + @autotiles = [] + @oldat = oldat + end + + def dispose + for i in 0...7 + for bitmap in @atdisposables[i] + bitmap.dispose + end + @atdisposables[i].clear + @atframes[i].clear + end + end + + def [](i) + return @autotiles[i] + end + + def []=(i,value) + for frame in @atdisposables[i] + frame.dispose + end + @atframe[i] = -1 + @atframes[i].clear + @atdisposables[i].clear + if value && !value.disposed? + if value.height==32 + frames = value.width/32 + for j in 0...frames + @atdisposables[i][j] = Bitmap.new(32,32) + @atdisposables[i][j].blt(0,0,value,Rect.new(j*32,0,32,32)) + @atframes[i][j] = @atdisposables[i][j] + end + elsif value.height==128 + frames = value.width/96 + for j in 0...frames + @atdisposables[i][j] = Bitmap.new(96,128) + @atdisposables[i][j].blt(0,0,value,Rect.new(j*96,0,96,128)) + @atframes[i][j] = @atdisposables[i][j] + end + else + @atframes[i][0] = value + end + else + @atframes[i][0] = value + end + @autotiles[i] = value + sync + end + + def sync + frameused = [] + for i in 0...7 + frames = [1,@atframes[i].length].max + frame = (Graphics.frame_count/15)%frames + if frames>1 && @atframe[i]!=frame + @oldat[i] = @atframes[i][frame] + @atframe[i] = frame + end + end + end +end + + + +class SynchronizedTilemapAutotiles + def initialize(autotiles) + @autotiles = autotiles + end + + def [](i) + return @autotiles[i] + end + + def []=(i,value) + @autotiles[i] = value + end +end + + + +class SynchronizedTilemap < Tilemap + # This class derives from Tilemap just to synchronize + # the tilemap animation. + attr_accessor :numupdates + + def initialize(viewport=nil) + super(viewport) + @updating = true + @autotiles = SynchronizedTilemapAutotilesInternal.new(self.autotiles) + @autos = SynchronizedTilemapAutotiles.new(@autotiles) + @updating = false + end + + def dispose + @autotiles.dispose + super + end + + def autotiles + return @autos if !@updating + super + end + + def update + return if disposed? + @autotiles.sync + super + end +end \ No newline at end of file diff --git a/Data/Scripts/005_Map renderer/004_TilemapLoader.rb b/Data/Scripts/005_Map renderer/004_TilemapLoader.rb new file mode 100644 index 000000000..14d0a4720 --- /dev/null +++ b/Data/Scripts/005_Map renderer/004_TilemapLoader.rb @@ -0,0 +1,70 @@ +class TilemapLoader + def initialize(viewport) + @viewport = viewport + @tilemap = nil + @color = Color.new(0,0,0,0) + @tone = Tone.new(0,0,0,0) + updateClass + end + + def updateClass + case $PokemonSystem.tilemap + when 1 # Custom (recommended) + setClass(CustomTilemap) + when 2 # Perspective + setClass(Draw_Tilemap) + else # Original (SynchronizedTilemap) or custom (CustomTilemap) + if Tilemap.method_defined?(:passages) + setClass(CustomTilemap) + else + setClass(($ResizeFactor==1.0) ? SynchronizedTilemap : CustomTilemap) + end + end + end + + def setClass(cls) + newtilemap = cls.new(@viewport) + if @tilemap + newtilemap.tileset = @tilemap.tileset + newtilemap.map_data = @tilemap.map_data + newtilemap.flash_data = @tilemap.flash_data + newtilemap.priorities = @tilemap.priorities + newtilemap.terrain_tags = @tilemap.terrain_tags + newtilemap.visible = @tilemap.visible + newtilemap.ox = @tilemap.ox + newtilemap.oy = @tilemap.oy + for i in 0...7 + newtilemap.autotiles[i] = @tilemap.autotiles[i] + end + @tilemap.dispose + @tilemap = newtilemap + newtilemap.update if cls!=SynchronizedTilemap + else + @tilemap = newtilemap + end + end + + def dispose; @tilemap.dispose; end + def disposed?; @tilemap && @tilemap.disposed?; end + def update; @tilemap.update; end + def viewport; @tilemap.viewport; end + def autotiles; @tilemap.autotiles; end + def tileset; @tilemap.tileset; end + def tileset=(v); @tilemap.tileset = v; end + def map_data; @tilemap.map_data; end + def map_data=(v); @tilemap.map_data = v; end + def flash_data; @tilemap.flash_data; end + def flash_data=(v); @tilemap.flash_data = v; end + def priorities; @tilemap.priorities; end + def priorities=(v); @tilemap.priorities = v; end + def terrain_tags; (@tilemap.terrain_tags rescue nil); end + def terrain_tags=(v); (@tilemap.terrain_tags = v rescue nil); end + def visible; @tilemap.visible; end + def visible=(v); @tilemap.visible = v; end + def tone; (@tilemap.tone rescue @tone); end + def tone=(value); (@tilemap.tone = value rescue nil); end + def ox; @tilemap.ox; end + def ox=(v); @tilemap.ox = v; end + def oy; @tilemap.oy; end + def oy=(v); @tilemap.oy = v; end +end \ No newline at end of file diff --git a/Data/Scripts/005_Map renderer/005_TileDrawingHelper.rb b/Data/Scripts/005_Map renderer/005_TileDrawingHelper.rb new file mode 100644 index 000000000..3ebf87843 --- /dev/null +++ b/Data/Scripts/005_Map renderer/005_TileDrawingHelper.rb @@ -0,0 +1,136 @@ +class TileDrawingHelper + attr_accessor :tileset + attr_accessor :autotiles + + Autotiles = [ + [ [27, 28, 33, 34], [ 5, 28, 33, 34], [27, 6, 33, 34], [ 5, 6, 33, 34], + [27, 28, 33, 12], [ 5, 28, 33, 12], [27, 6, 33, 12], [ 5, 6, 33, 12] ], + [ [27, 28, 11, 34], [ 5, 28, 11, 34], [27, 6, 11, 34], [ 5, 6, 11, 34], + [27, 28, 11, 12], [ 5, 28, 11, 12], [27, 6, 11, 12], [ 5, 6, 11, 12] ], + [ [25, 26, 31, 32], [25, 6, 31, 32], [25, 26, 31, 12], [25, 6, 31, 12], + [15, 16, 21, 22], [15, 16, 21, 12], [15, 16, 11, 22], [15, 16, 11, 12] ], + [ [29, 30, 35, 36], [29, 30, 11, 36], [ 5, 30, 35, 36], [ 5, 30, 11, 36], + [39, 40, 45, 46], [ 5, 40, 45, 46], [39, 6, 45, 46], [ 5, 6, 45, 46] ], + [ [25, 30, 31, 36], [15, 16, 45, 46], [13, 14, 19, 20], [13, 14, 19, 12], + [17, 18, 23, 24], [17, 18, 11, 24], [41, 42, 47, 48], [ 5, 42, 47, 48] ], + [ [37, 38, 43, 44], [37, 6, 43, 44], [13, 18, 19, 24], [13, 14, 43, 44], + [37, 42, 43, 48], [17, 18, 47, 48], [13, 18, 43, 48], [ 1, 2, 7, 8] ] + ] + + # converts neighbors returned from tableNeighbors to tile indexes + NeighborsToTiles = [ + 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, + 42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16, + 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, + 42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16, + 45, 39, 45, 39, 33, 31, 33, 29, 45, 39, 45, 39, 33, 31, 33, 29, + 37, 27, 37, 27, 23, 15, 23, 13, 37, 27, 37, 27, 22, 11, 22, 9, + 45, 39, 45, 39, 33, 31, 33, 29, 45, 39, 45, 39, 33, 31, 33, 29, + 36, 26, 36, 26, 21, 7, 21, 5, 36, 26, 36, 26, 20, 3, 20, 1, + 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, + 42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16, + 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, + 42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16, + 45, 38, 45, 38, 33, 30, 33, 28, 45, 38, 45, 38, 33, 30, 33, 28, + 37, 25, 37, 25, 23, 14, 23, 12, 37, 25, 37, 25, 22, 10, 22, 8, + 45, 38, 45, 38, 33, 30, 33, 28, 45, 38, 45, 38, 33, 30, 33, 28, + 36, 24, 36, 24, 21, 6, 21, 4, 36, 24, 36, 24, 20, 2, 20, 0 + ] + + def self.tableNeighbors(data,x,y) + return 0 if x < 0 || x >= data.xsize + return 0 if y < 0 || y >= data.ysize + t = data[x,y] + xp1 = [x + 1, data.xsize - 1].min + yp1 = [y + 1, data.ysize - 1].min + xm1 = [x - 1, 0].max + ym1 = [y - 1, 0].max + i = 0 + i |= 0x01 if data[ x, ym1] == t # N + i |= 0x02 if data[xp1, ym1] == t # NE + i |= 0x04 if data[xp1, y] == t # E + i |= 0x08 if data[xp1, yp1] == t # SE + i |= 0x10 if data[ x, yp1] == t # S + i |= 0x20 if data[xm1, yp1] == t # SW + i |= 0x40 if data[xm1, y] == t # W + i |= 0x80 if data[xm1, ym1] == t # NW + return i + end + + def self.fromTileset(tileset) + bmtileset=pbGetTileset(tileset.tileset_name) + bmautotiles=[] + for i in 0...7 + bmautotiles.push(pbGetAutotile(tileset.autotile_names[i])) + end + return self.new(bmtileset,bmautotiles) + end + + def initialize(tileset,autotiles) + @tileset = tileset + @autotiles = autotiles + end + + def dispose + @tileset.dispose if @tileset + @tileset = nil + for i in 0...@autotiles.length + @autotiles[i].dispose + @autotiles[i] = nil + end + end + + def bltSmallAutotile(bitmap,x,y,cxTile,cyTile,id,frame) + return if id >= 384 || frame < 0 || !@autotiles + autotile = @autotiles[id / 48 - 1] + return if !autotile || autotile.disposed? + cxTile = [cxTile / 2, 1].max + cyTile = [cyTile / 2, 1].max + if autotile.height == 32 + anim = frame * 32 + src_rect = Rect.new(anim, 0, 32, 32) + bitmap.stretch_blt(Rect.new(x, y, cxTile * 2, cyTile * 2), autotile, src_rect) + else + anim = frame * 96 + id %= 48 + tiles = TileDrawingHelper::Autotiles[id >> 3][id & 7] + src = Rect.new(0, 0, 0, 0) + for i in 0...4 + tile_position = tiles[i] - 1 + src.set(tile_position % 6 * 16 + anim, tile_position / 6 * 16, 16, 16) + bitmap.stretch_blt(Rect.new(i % 2 * cxTile + x, i / 2 * cyTile + y, cxTile, cyTile), + autotile, src) + end + end + end + + def bltSmallRegularTile(bitmap,x,y,cxTile,cyTile,id) + return if id < 384 || !@tileset || @tileset.disposed? + rect = Rect.new((id - 384) % 8 * 32, (id - 384) / 8 * 32, 32, 32) + bitmap.stretch_blt(Rect.new(x, y, cxTile, cyTile), @tileset, rect) + end + + def bltSmallTile(bitmap,x,y,cxTile,cyTile,id,frame=0) + if id >= 384 + bltSmallRegularTile(bitmap, x, y, cxTile, cyTile, id) + elsif id > 0 + bltSmallAutotile(bitmap, x, y, cxTile, cyTile, id, frame) + end + end + + def bltAutotile(bitmap,x,y,id,frame) + bltSmallAutotile(bitmap, x, y, 32, 32, id, frame) + end + + def bltRegularTile(bitmap,x,y,id) + bltSmallRegularTile(bitmap, x, y, 32, 32, id) + end + + def bltTile(bitmap,x,y,id,frame=0) + if id >= 384 + bltRegularTile(bitmap, x, y, id) + elsif id > 0 + bltAutotile(bitmap, x, y, id, frame) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/001_Interpreter.rb b/Data/Scripts/006_Events and files/001_Interpreter.rb new file mode 100644 index 000000000..7f87747ca --- /dev/null +++ b/Data/Scripts/006_Events and files/001_Interpreter.rb @@ -0,0 +1,1469 @@ +#=============================================================================== +# ** Interpreter +#------------------------------------------------------------------------------- +# This interpreter runs event commands. This class is used within the +# Game_System class and the Game_Event class. +#=============================================================================== +class Interpreter + #----------------------------------------------------------------------------- + # * Object Initialization + # depth : nest depth + # main : main flag + #----------------------------------------------------------------------------- + def initialize(depth = 0, main = false) + @depth = depth + @main = main + # Depth goes up to level 100 + if depth > 100 + print("Common event call has exceeded maximum limit.") + exit + end + # Clear inner situation of interpreter + clear + end + + def clear + @map_id = 0 # map ID when starting up + @event_id = 0 # event ID + @message_waiting = false # waiting for message to end + @move_route_waiting = false # waiting for move completion + @button_input_variable_id = 0 # button input variable ID + @wait_count = 0 # wait count + @child_interpreter = nil # child interpreter + @branch = {} # branch data + end + #----------------------------------------------------------------------------- + # * Event Setup + # list : list of event commands + # event_id : event ID + #----------------------------------------------------------------------------- + def setup(list, event_id, map_id=nil) + # Clear inner situation of interpreter + clear + # Remember map ID + @map_id = map_id ? map_id : $game_map.map_id + # Remember event ID + @event_id = event_id + # Remember list of event commands + @list = list + # Initialize index + @index = 0 + # Clear branch data hash + @branch.clear + end + + def running? + return @list != nil + end + + def setup_starting_event + $game_map.refresh if $game_map.need_refresh + # If common event call is reserved + if $game_temp.common_event_id > 0 + # Set up event + setup($data_common_events[$game_temp.common_event_id].list, 0) + # Release reservation + $game_temp.common_event_id = 0 + return + end + # Loop (map events) + for event in $game_map.events.values + # If running event is found + next if !event.starting + # If not auto run + if event.trigger < 3 + # Lock + event.lock + # Clear starting flag + event.clear_starting + end + # Set up event + setup(event.list, event.id, event.map.map_id) #### CHANGED + return + end + # Loop (common events) + for common_event in $data_common_events.compact + # If trigger is auto run, and condition switch is ON + if common_event.trigger == 1 and + $game_switches[common_event.switch_id] == true + # Set up event + setup(common_event.list, 0) + return + end + end + end + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + # Initialize loop count + @loop_count = 0 + # Loop + loop do + @loop_count += 1 + if @loop_count > 100 + # Call Graphics.update for freeze prevention + Graphics.update + @loop_count = 0 + end + # If map is different than event startup time + if $game_map.map_id != @map_id && + (!$MapFactory || !$MapFactory.areConnected?($game_map.map_id,@map_id)) + @event_id = 0 + end + if @child_interpreter != nil + @child_interpreter.update + unless @child_interpreter.running? + @child_interpreter = nil + end + return if @child_interpreter != nil + end + return if @message_waiting + if @move_route_waiting + return if $game_player.move_route_forcing + for event in $game_map.events.values + return if event.move_route_forcing + end + @move_route_waiting = false + end + # If waiting for button input + if @button_input_variable_id > 0 + input_button + return + end + if @wait_count > 0 + @wait_count -= 1 + return + end + # If an action forcing battler exists + return if $game_temp.forcing_battler != nil + # If a call flag is set for each type of screen + if $game_temp.battle_calling or + $game_temp.shop_calling or + $game_temp.name_calling or + $game_temp.menu_calling or + $game_temp.save_calling or + $game_temp.gameover + return + end + # If list of event commands is empty + if @list == nil + setup_starting_event if @main + return if @list == nil + end + # If return value is false when trying to execute event command + return if execute_command == false + # Advance index + @index += 1 + end + end + #----------------------------------------------------------------------------- + # * Button Input + #----------------------------------------------------------------------------- + def input_button + # Determine pressed button + n = 0 + for i in 1..18 + n = i if Input.trigger?(i) + end + # If button was pressed + if n > 0 + # Change value of variables + $game_variables[@button_input_variable_id] = n + $game_map.need_refresh = true + # End button input + @button_input_variable_id = 0 + end + end + #----------------------------------------------------------------------------- + # * Setup Choices + #----------------------------------------------------------------------------- + def setup_choices(parameters) + # Set choice item count to choice_max + $game_temp.choice_max = parameters[0].size + # Set choice to message_text + for text in parameters[0] + $game_temp.message_text += text + "\n" + end + # Set cancel processing + $game_temp.choice_cancel_type = parameters[1] + # Set callback + current_indent = @list[@index].indent + $game_temp.choice_proc = Proc.new { |n| @branch[current_indent] = n } + end + + def command_dummy + return true + end + + def pbExecuteScript(script) + begin + result = eval(script) + return result + rescue Exception + e = $! + raise if e.is_a?(SystemExit) || "#{e.class}"=="Reset" + event = get_character(0) + s = "Backtrace:\r\n" + message = pbGetExceptionMessage(e) + if e.is_a?(SyntaxError) + script.each_line { |line| + line.gsub!(/\s+$/,"") + if line[/\:\:\s*$/] + message += "\r\n***Line '#{line}' can't begin with '::'. Try putting\r\n" + message += "the next word on the same line, e.g. 'PBSpecies:"+":MEW'" + end + if line[/^\s*\(/] + message += "\r\n***Line '#{line}' shouldn't begin with '('. Try\r\n" + message += "putting the '(' at the end of the previous line instead,\r\n" + message += "or using 'extendtext.exe'." + end + } + else + for bt in e.backtrace[0,10] + s += bt+"\r\n" + end + s.gsub!(/Section(\d+)/) { $RGSS_SCRIPTS[$1.to_i][1] } + end + message = "Exception: #{e.class}\r\nMessage: "+message+"\r\n" + message += "\r\n***Full script:\r\n#{script}\r\n" + if event && $game_map + mapname = ($game_map.name rescue nil) || "???" + err = "Script error within event #{event.id} (coords #{event.x},#{event.y}), " + err += "map #{$game_map.map_id} (#{mapname}):\r\n#{message}\r\n#{s}" + if e.is_a?(Hangup) + $EVENTHANGUPMSG = err; raise + end + raise err + elsif $game_map + mapname = ($game_map.name rescue nil) || "???" + err = "Script error within map #{$game_map.map_id} " + err += "(#{mapname}):\r\n#{message}\r\n#{s}" + if e.is_a?(Hangup) + $EVENTHANGUPMSG = err; raise + end + raise err + else + err = "Script error in interpreter:\r\n#{message}\r\n#{s}" + if e.is_a?(Hangup) + $EVENTHANGUPMSG = err; raise + end + raise err + end + return false + end + end + #----------------------------------------------------------------------------- + # * Event Command Execution + #----------------------------------------------------------------------------- + def execute_command + # If last to arrive for list of event commands + if @index >= @list.size - 1 + # End event + command_end + # Continue + return true + end + # Make event command parameters available for reference via @parameters + @parameters = @list[@index].parameters + # Branch by command code + case @list[@index].code + when 101; return command_101 # Show Text + when 102; return command_102 # Show Choices + when 402; return command_402 # When [**] + when 403; return command_403 # When Cancel + when 103; return command_103 # Input Number + when 104; return command_104 # Change Text Options [not in VX] + when 105; return command_105 # Button Input Processing [not in VX] + when 106; return command_106 # Wait [in VX: 230] + when 111; return command_111 # Conditional Branch + when 411; return command_411 # Else + when 112; return command_112 # Loop + when 413; return command_413 # Repeat Above + when 113; return command_113 # Break Loop + when 115; return command_115 # Exit Event Processing + when 116; return command_116 # Erase Event [in VX: 214] + when 117; return command_117 # Call Common Event + when 118; return command_118 # Label + when 119; return command_119 # Jump to Label + when 121; return command_121 # Control Switches + when 122; return command_122 # Control Variables + when 123; return command_123 # Control Self Switch + when 124; return command_124 # Control Timer + when 125; return command_125 # Change Gold + when 126; return command_126 # Change Items + when 127; return command_127 # Change Weapons + when 128; return command_128 # Change Armor + when 129; return command_129 # Change Party Member + when 131; return command_131 # Change Windowskin [not in VX] + when 132; return command_132 # Change Battle BGM + when 133; return command_133 # Change Battle End ME + when 134; return command_134 # Change Save Access + when 135; return command_135 # Change Menu Access + when 136; return command_136 # Change Encounter + when 201; return command_201 # Transfer Player + when 202; return command_202 # Set Event Location + when 203; return command_203 # Scroll Map + when 204; return command_204 # Change Map Settings + when 205; return command_205 # Change Fog Color Tone [in VX: Set Move Route] + when 206; return command_206 # Change Fog Opacity [in VX: Get on/off Vehicle] + when 207; return command_207 # Show Animation [in VX: 212] + when 208; return command_208 # Change Transparent Flag [in VX: 211] + when 209; return command_209 # Set Move Route [in VX: 205] + when 210; return command_210 # Wait for Move's Completion + when 221; return command_221 # Prepare for Transition [Not in VX, now called Fadeout Screen] + when 222; return command_222 # Execute Transition [Not in VX, now called Fadein Screen] + when 223; return command_223 # Change Screen Color Tone + when 224; return command_224 # Screen Flash + when 225; return command_225 # Screen Shake + when 231; return command_231 # Show Picture + when 232; return command_232 # Move Picture + when 233; return command_233 # Rotate Picture + when 234; return command_234 # Change Picture Color Tone + when 235; return command_235 # Erase Picture + when 236; return command_236 # Set Weather Effects + when 241; return command_241 # Play BGM + when 242; return command_242 # Fade Out BGM + when 245; return command_245 # Play BGS + when 246; return command_246 # Fade Out BGS + when 247; return command_247 # Memorize BGM/BGS [not in VX] + when 248; return command_248 # Restore BGM/BGS [not in VX] + when 249; return command_249 # Play ME + when 250; return command_250 # Play SE + when 251; return command_251 # Stop SE + when 301; return command_301 # Battle Processing + when 601; return command_601 # If Win + when 602; return command_602 # If Escape + when 603; return command_603 # If Lose + when 302; return command_302 # Shop Processing + when 303; return command_303 # Name Input Processing + when 311; return command_311 # Change HP + when 312; return command_312 # Change SP + when 313; return command_313 # Change State + when 314; return command_314 # Recover All + when 315; return command_315 # Change EXP + when 316; return command_316 # Change Level + when 317; return command_317 # Change Parameters + when 318; return command_318 # Change Skills + when 319; return command_319 # Change Equipment + when 320; return command_320 # Change Actor Name + when 321; return command_321 # Change Actor Class + when 322; return command_322 # Change Actor Graphic + when 331; return command_331 # Change Enemy HP + when 332; return command_332 # Change Enemy SP + when 333; return command_333 # Change Enemy State + when 334; return command_334 # Enemy Recover All + when 335; return command_335 # Enemy Appearance + when 336; return command_336 # Enemy Transform + when 337; return command_337 # Show Battle Animation + when 338; return command_338 # Deal Damage + when 339; return command_339 # Force Action + when 340; return command_340 # Abort Battle + when 351; return command_351 # Call Menu Screen + when 352; return command_352 # Call Save Screen + when 353; return command_353 # Game Over + when 354; return command_354 # Return to Title Screen + when 355; return command_355 # Script + else; return true # Other + end + end + + def command_dummy + return true + end + #----------------------------------------------------------------------------- + # * End Event + #----------------------------------------------------------------------------- + def command_end + # Clear list of event commands + @list = nil + # If main map event and event ID are valid + if @main and @event_id > 0 && $game_map.events[@event_id] + # Unlock event + $game_map.events[@event_id].unlock + end + end + #----------------------------------------------------------------------------- + # * Command Skip + #----------------------------------------------------------------------------- + def command_skip + # Get indent + indent = @list[@index].indent + # Loop + loop do + # If next event command is at the same level as indent + if @list[@index+1].indent == indent + # Continue + return true + end + # Advance index + @index += 1 + end + end + #----------------------------------------------------------------------------- + # * Get Character + # parameter : parameter + #----------------------------------------------------------------------------- + def get_character(parameter) + # Branch by parameter + case parameter + when -1 # player + return $game_player + when 0 # this event + events = $game_map.events + return events == nil ? nil : events[@event_id] + else # specific event + events = $game_map.events + return events == nil ? nil : events[parameter] + end + end + #----------------------------------------------------------------------------- + # * Calculate Operated Value + # operation : operation + # operand_type : operand type (0: invariable 1: variable) + # operand : operand (number or variable ID) + #----------------------------------------------------------------------------- + def operate_value(operation, operand_type, operand) + # Get operand + if operand_type == 0 + value = operand + else + value = $game_variables[operand] + end + # Reverse sign of integer if operation is [decrease] + if operation == 1 + value = -value + end + # Return value + return value + end + #----------------------------------------------------------------------------- + # * Show Text + #----------------------------------------------------------------------------- + def command_101 + # If other text has been set to message_text + if $game_temp.message_text != nil + # End + return false + end + # Set message end waiting flag and callback + @message_waiting = true + $game_temp.message_proc = Proc.new { @message_waiting = false } + # Set message text on first line + $game_temp.message_text = @list[@index].parameters[0] + "\n" + line_count = 1 + # Loop + loop do + # If next event command text is on the second line or after + if @list[@index+1].code == 401 + # Add the second line or after to message_text + $game_temp.message_text += @list[@index+1].parameters[0] + "\n" + line_count += 1 + # If event command is not on the second line or after + else + # If next event command is show choices + if @list[@index+1].code == 102 + # If choices fit on screen + if @list[@index+1].parameters[0].size <= 4 - line_count + # Advance index + @index += 1 + # Choices setup + $game_temp.choice_start = line_count + setup_choices(@list[@index].parameters) + end + # If next event command is input number + elsif @list[@index+1].code == 103 + # If number input window fits on screen + if line_count < 4 + # Advance index + @index += 1 + # Number input setup + $game_temp.num_input_start = line_count + $game_temp.num_input_variable_id = @list[@index].parameters[0] + $game_temp.num_input_digits_max = @list[@index].parameters[1] + end + end + # Continue + return true + end + # Advance index + @index += 1 + end + end + #----------------------------------------------------------------------------- + # * Show Choices + #----------------------------------------------------------------------------- + def command_102 + # If text has been set to message_text + if $game_temp.message_text != nil + # End + return false + end + # Set message end waiting flag and callback + @message_waiting = true + $game_temp.message_proc = Proc.new { @message_waiting = false } + # Choices setup + $game_temp.message_text = "" + $game_temp.choice_start = 0 + setup_choices(@parameters) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * When [**] + #----------------------------------------------------------------------------- + def command_402 + # If fitting choices are selected + if @branch[@list[@index].indent] == @parameters[0] + # Delete branch data + @branch.delete(@list[@index].indent) + # Continue + return true + end + # If it doesn't meet the condition: command skip + return command_skip + end + #----------------------------------------------------------------------------- + # * When Cancel + #----------------------------------------------------------------------------- + def command_403 + # If choices are cancelled + if @branch[@list[@index].indent] == 4 + # Delete branch data + @branch.delete(@list[@index].indent) + # Continue + return true + end + # If it doen't meet the condition: command skip + return command_skip + end + #----------------------------------------------------------------------------- + # * Input Number + #----------------------------------------------------------------------------- + def command_103 + # If text has been set to message_text + if $game_temp.message_text != nil + # End + return false + end + # Set message end waiting flag and callback + @message_waiting = true + $game_temp.message_proc = Proc.new { @message_waiting = false } + # Number input setup + $game_temp.message_text = "" + $game_temp.num_input_start = 0 + $game_temp.num_input_variable_id = @parameters[0] + $game_temp.num_input_digits_max = @parameters[1] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Text Options + #----------------------------------------------------------------------------- + def command_104 + # If message is showing + if $game_temp.message_window_showing + # End + return false + end + # Change each option + $game_system.message_position = @parameters[0] + $game_system.message_frame = @parameters[1] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Button Input Processing + #----------------------------------------------------------------------------- + def command_105 + # Set variable ID for button input + @button_input_variable_id = @parameters[0] + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Wait + #----------------------------------------------------------------------------- + def command_106 + # Set wait count + @wait_count = @parameters[0] * Graphics.frame_rate/20 + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Conditional Branch + #----------------------------------------------------------------------------- + def command_111 + # Initialize local variable: result + result = false + case @parameters[0] + when 0 # switch + result = false + switchname=$data_system.switches[@parameters[1]] + if switchname && switchname[/^s\:/] + result = (eval($~.post_match) == (@parameters[2] == 0)) + else + result = ($game_switches[@parameters[1]] == (@parameters[2] == 0)) + end + when 1 # variable + value1 = $game_variables[@parameters[1]] + if @parameters[2] == 0 + value2 = @parameters[3] + else + value2 = $game_variables[@parameters[3]] + end + case @parameters[4] + when 0 # value1 is equal to value2 + result = (value1 == value2) + when 1 # value1 is greater than or equal to value2 + result = (value1 >= value2) + when 2 # value1 is less than or equal to value2 + result = (value1 <= value2) + when 3 # value1 is greater than value2 + result = (value1 > value2) + when 4 # value1 is less than value2 + result = (value1 < value2) + when 5 # value1 is not equal to value2 + result = (value1 != value2) + end + when 2 # self switch + if @event_id > 0 + key = [$game_map.map_id, @event_id, @parameters[1]] + if @parameters[2] == 0 + result = ($game_self_switches[key] == true) + else + result = ($game_self_switches[key] != true) + end + end + when 3 # timer + if $game_system.timer_working + sec = $game_system.timer / Graphics.frame_rate + if @parameters[2] == 0 + result = (sec >= @parameters[1]) + else + result = (sec <= @parameters[1]) + end + end + when 4, 5 # actor, enemy + when 6 # character + character = get_character(@parameters[1]) + if character != nil + result = (character.direction == @parameters[2]) + end + when 7 + if @parameters[2] == 0 + result = ($Trainer.money >= @parameters[1]) + else + result = ($Trainer.money <= @parameters[1]) + end + when 8, 9, 10 # item, weapon, armor + when 11 # button + result = (Input.press?(@parameters[1])) + when 12 # script + result = pbExecuteScript(@parameters[1]) + end + # Store determinant results in hash + @branch[@list[@index].indent] = result + # If determinant results are true + if @branch[@list[@index].indent] == true + # Delete branch data + @branch.delete(@list[@index].indent) + # Continue + return true + end + # If it doesn't meet the conditions: command skip + return command_skip + end + #----------------------------------------------------------------------------- + # * Else + #----------------------------------------------------------------------------- + def command_411 + # If determinant results are false + if @branch[@list[@index].indent] == false + # Delete branch data + @branch.delete(@list[@index].indent) + # Continue + return true + end + # If it doesn't meet the conditions: command skip + return command_skip + end + #----------------------------------------------------------------------------- + # * Loop + #----------------------------------------------------------------------------- + def command_112 + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Repeat Above + #----------------------------------------------------------------------------- + def command_413 + # Get indent + indent = @list[@index].indent + # Loop + loop do + # Return index + @index -= 1 + # If this event command is the same level as indent + if @list[@index].indent == indent + # Continue + return true + end + end + end + #----------------------------------------------------------------------------- + # * Break Loop + #----------------------------------------------------------------------------- + def command_113 + # Get indent + indent = @list[@index].indent + # Copy index to temporary variables + temp_index = @index + # Loop + loop do + # Advance index + temp_index += 1 + # If a fitting loop was not found + if temp_index >= @list.size-1 + # Continue + return true + end + # If this event command is [repeat above] and indent is shallow + if @list[temp_index].code == 413 and @list[temp_index].indent < indent + # Update index + @index = temp_index + # Continue + return true + end + end + end + #----------------------------------------------------------------------------- + # * Exit Event Processing + #----------------------------------------------------------------------------- + def command_115 + # End event + command_end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Erase Event + #----------------------------------------------------------------------------- + def command_116 + # If event ID is valid + if @event_id > 0 + # Erase event + $game_map.events[@event_id].erase if $game_map.events[@event_id] + $PokemonMap.addErasedEvent(@event_id) if $PokemonMap + end + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Call Common Event + #----------------------------------------------------------------------------- + def command_117 + # Get common event + common_event = $data_common_events[@parameters[0]] + # If common event is valid + if common_event != nil + # Make child interpreter + @child_interpreter = Interpreter.new(@depth + 1) + @child_interpreter.setup(common_event.list, @event_id) + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Label + #----------------------------------------------------------------------------- + def command_118 + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Jump to Label + #----------------------------------------------------------------------------- + def command_119 + # Get label name + label_name = @parameters[0] + # Initialize temporary variables + temp_index = 0 + # Loop + loop do + # If a fitting label was not found + if temp_index >= @list.size-1 + # Continue + return true + end + # If this event command is a designated label name + if @list[temp_index].code == 118 and + @list[temp_index].parameters[0] == label_name + # Update index + @index = temp_index + # Continue + return true + end + # Advance index + temp_index += 1 + end + end + #----------------------------------------------------------------------------- + # * Control Switches + #----------------------------------------------------------------------------- + def command_121 + shouldRefresh = false + # Loop for group control + for i in @parameters[0] .. @parameters[1] + next if $game_switches[i] == (@parameters[2] == 0) + # Change switch + $game_switches[i] = (@parameters[2] == 0) + shouldRefresh = true + end + # Refresh map + $game_map.need_refresh = true if shouldRefresh + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Control Variables + #----------------------------------------------------------------------------- + def command_122 + # Initialize value + value = 0 + # Branch with operand + case @parameters[3] + when 0 # invariable (fixed value) + value = @parameters[4] + when 1 # variable + value = $game_variables[@parameters[4]] + when 2 # random number + value = @parameters[4] + rand(@parameters[5] - @parameters[4] + 1) + when 3, 4, 5 # item, actor, enemy + when 6 # character + character = get_character(@parameters[4]) + if character != nil + case @parameters[5] + when 0; value = character.x # x-coordinate + when 1; value = character.y # y-coordinate + when 2; value = character.direction # direction + when 3; value = character.screen_x # screen x-coordinate + when 4; value = character.screen_y # screen y-coordinate + when 5; value = character.terrain_tag # terrain tag + end + end + when 7 # other + case @parameters[4] + when 0; value = $game_map.map_id # map ID + when 1, 3 # number of party members, steps + when 2; value = $Trainer.money # gold + when 4; value = Graphics.frame_count / Graphics.frame_rate # play time + when 5; value = $game_system.timer / Graphics.frame_rate # timer + when 6; value = $game_system.save_count # save count + end + end + shouldRefresh = false + # Loop for group control + for i in @parameters[0] .. @parameters[1] + # Branch with control + case @parameters[2] + when 0 # substitute + next if $game_variables[i] == value + $game_variables[i] = value + when 1 # add + next if $game_variables[i] >= 99999999 + $game_variables[i] += value + when 2 # subtract + next if $game_variables[i] <= -99999999 + $game_variables[i] -= value + when 3 # multiply + next if value == 1 + $game_variables[i] *= value + when 4 # divide + next if value == 1 || value == 0 + $game_variables[i] /= value + when 5 # remainder + next if value == 1 || value == 0 + $game_variables[i] %= value + end + # Maximum limit check + $game_variables[i] = 99999999 if $game_variables[i] > 99999999 + # Minimum limit check + $game_variables[i] = -99999999 if $game_variables[i] < -99999999 + # Refresh map + $game_map.need_refresh = true + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Control Self Switch + #----------------------------------------------------------------------------- + def command_123 + # If event ID is valid + if @event_id > 0 + # Make a self switch key + key = [$game_map.map_id, @event_id, @parameters[0]] + newValue = (@parameters[1] == 0) + if $game_self_switches[key] != newValue + # Change self switches + $game_self_switches[key] = newValue + # Refresh map + $game_map.need_refresh = true + end + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Control Timer + #----------------------------------------------------------------------------- + def command_124 + # If started + if @parameters[0] == 0 + $game_system.timer = @parameters[1] * Graphics.frame_rate + $game_system.timer_working = true + end + # If stopped + $game_system.timer_working = false if @parameters[0] == 1 + # Continue + return true + end + + def command_125; command_dummy; end # Change Gold + def command_126; command_dummy; end # Change Items + def command_127; command_dummy; end # Change Weapons + def command_128; command_dummy; end # Change Armor + def command_129; command_dummy; end # Change Party Member + #----------------------------------------------------------------------------- + # * Change Windowskin + #----------------------------------------------------------------------------- + def command_131 + # Change windowskin file name + for i in 0...$SpeechFrames.length + if $SpeechFrames[i]==@parameters[0] + $PokemonSystem.textskin=i + MessageConfig.pbSetSpeechFrame("Graphics/Windowskins/"+$SpeechFrames[i]) + return true + end + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Battle BGM + #----------------------------------------------------------------------------- + def command_132 + # Change battle BGM + $game_system.battle_bgm = @parameters[0] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Battle End ME + #----------------------------------------------------------------------------- + def command_133 + # Change battle end ME + $game_system.battle_end_me = @parameters[0] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Save Access + #----------------------------------------------------------------------------- + def command_134 + # Change save access flag + $game_system.save_disabled = (@parameters[0] == 0) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Menu Access + #----------------------------------------------------------------------------- + def command_135 + # Change menu access flag + $game_system.menu_disabled = (@parameters[0] == 0) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Encounter + #----------------------------------------------------------------------------- + def command_136 + # Change encounter flag + $game_system.encounter_disabled = (@parameters[0] == 0) + # Make encounter count + $game_player.make_encounter_count + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Transfer Player + #----------------------------------------------------------------------------- + def command_201 + # If in battle + if $game_temp.in_battle + # Continue + return true + end + # If transferring player, showing message, or processing transition + if $game_temp.player_transferring or + $game_temp.message_window_showing or + $game_temp.transition_processing + # End + return false + end + # Set transferring player flag + $game_temp.player_transferring = true + # If appointment method is [direct appointment] + if @parameters[0] == 0 + # Set player move destination + $game_temp.player_new_map_id = @parameters[1] + $game_temp.player_new_x = @parameters[2] + $game_temp.player_new_y = @parameters[3] + $game_temp.player_new_direction = @parameters[4] + # If appointment method is [appoint with variables] + else + # Set player move destination + $game_temp.player_new_map_id = $game_variables[@parameters[1]] + $game_temp.player_new_x = $game_variables[@parameters[2]] + $game_temp.player_new_y = $game_variables[@parameters[3]] + $game_temp.player_new_direction = @parameters[4] + end + # Advance index + @index += 1 + # If fade is set + if @parameters[5] == 0 + # Prepare for transition + Graphics.freeze + # Set transition processing flag + $game_temp.transition_processing = true + $game_temp.transition_name = "" + end + # End + return false + end + #----------------------------------------------------------------------------- + # * Set Event Location + #----------------------------------------------------------------------------- + def command_202 + # If in battle + if $game_temp.in_battle + # Continue + return true + end + # Get character + character = get_character(@parameters[0]) + # If no character exists + if character == nil + # Continue + return true + end + # If appointment method is [direct appointment] + if @parameters[1] == 0 + # Set character position + character.moveto(@parameters[2], @parameters[3]) + # If appointment method is [appoint with variables] + elsif @parameters[1] == 1 + # Set character position + character.moveto($game_variables[@parameters[2]], + $game_variables[@parameters[3]]) + # If appointment method is [exchange with another event] + else + old_x = character.x + old_y = character.y + character2 = get_character(@parameters[2]) + if character2 != nil + character.moveto(character2.x, character2.y) + character2.moveto(old_x, old_y) + end + end + # Set character direction + case @parameters[4] + when 8 # up + character.turn_up + when 6 # right + character.turn_right + when 2 # down + character.turn_down + when 4 # left + character.turn_left + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Scroll Map + #----------------------------------------------------------------------------- + def command_203 + # If in battle + if $game_temp.in_battle + # Continue + return true + end + # If already scrolling + if $game_map.scrolling? + # End + return false + end + # Start scroll + $game_map.start_scroll(@parameters[0], @parameters[1], @parameters[2]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Map Settings + #----------------------------------------------------------------------------- + def command_204 + case @parameters[0] + when 0 # panorama + $game_map.panorama_name = @parameters[1] + $game_map.panorama_hue = @parameters[2] + when 1 # fog + $game_map.fog_name = @parameters[1] + $game_map.fog_hue = @parameters[2] + $game_map.fog_opacity = @parameters[3] + $game_map.fog_blend_type = @parameters[4] + $game_map.fog_zoom = @parameters[5] + $game_map.fog_sx = @parameters[6] + $game_map.fog_sy = @parameters[7] + when 2 # battleback + $game_map.battleback_name = @parameters[1] + $game_temp.battleback_name = @parameters[1] + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Fog Color Tone + #----------------------------------------------------------------------------- + def command_205 + # Start color tone change + $game_map.start_fog_tone_change(@parameters[0], @parameters[1] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Fog Opacity + #----------------------------------------------------------------------------- + def command_206 + # Start opacity level change + $game_map.start_fog_opacity_change(@parameters[0], @parameters[1] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Show Animation + #----------------------------------------------------------------------------- + def command_207 + # Get character + character = get_character(@parameters[0]) + # If no character exists + if character == nil + # Continue + return true + end + # Set animation ID + character.animation_id = @parameters[1] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Transparent Flag + #----------------------------------------------------------------------------- + def command_208 + # Change player transparent flag + $game_player.transparent = (@parameters[0] == 0) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Set Move Route + #----------------------------------------------------------------------------- + def command_209 + # Get character + character = get_character(@parameters[0]) + # If no character exists + if character == nil + # Continue + return true + end + # Force move route + character.force_move_route(@parameters[1]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Wait for Move's Completion + #----------------------------------------------------------------------------- + def command_210 + # If not in battle + unless $game_temp.in_battle + # Set move route completion waiting flag + @move_route_waiting = true + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Prepare for Transition + #----------------------------------------------------------------------------- + def command_221 + # If showing message window + if $game_temp.message_window_showing + # End + return false + end + # Prepare for transition + Graphics.freeze + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Execute Transition + #----------------------------------------------------------------------------- + def command_222 + # If transition processing flag is already set + if $game_temp.transition_processing + # End + return false + end + # Set transition processing flag + $game_temp.transition_processing = true + $game_temp.transition_name = @parameters[0] + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Change Screen Color Tone + #----------------------------------------------------------------------------- + def command_223 + # Start changing color tone + $game_screen.start_tone_change(@parameters[0], @parameters[1] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Screen Flash + #----------------------------------------------------------------------------- + def command_224 + # Start flash + $game_screen.start_flash(@parameters[0], @parameters[1] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Screen Shake + #----------------------------------------------------------------------------- + def command_225 + # Start shake + $game_screen.start_shake(@parameters[0], @parameters[1], + @parameters[2] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Show Picture + #----------------------------------------------------------------------------- + def command_231 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # If appointment method is [direct appointment] + if @parameters[3] == 0 + x = @parameters[4] + y = @parameters[5] + # If appointment method is [appoint with variables] + else + x = $game_variables[@parameters[4]] + y = $game_variables[@parameters[5]] + end + # Show picture + $game_screen.pictures[number].show(@parameters[1], @parameters[2], + x, y, @parameters[6], @parameters[7], @parameters[8], @parameters[9]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Move Picture + #----------------------------------------------------------------------------- + def command_232 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # If appointment method is [direct appointment] + if @parameters[3] == 0 + x = @parameters[4] + y = @parameters[5] + # If appointment method is [appoint with variables] + else + x = $game_variables[@parameters[4]] + y = $game_variables[@parameters[5]] + end + # Move picture + $game_screen.pictures[number].move(@parameters[1] * Graphics.frame_rate / 20, + @parameters[2], x, y, + @parameters[6], @parameters[7], @parameters[8], @parameters[9]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Rotate Picture + #----------------------------------------------------------------------------- + def command_233 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # Set rotation speed + $game_screen.pictures[number].rotate(@parameters[1]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Picture Color Tone + #----------------------------------------------------------------------------- + def command_234 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # Start changing color tone + $game_screen.pictures[number].start_tone_change(@parameters[1], + @parameters[2] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Erase Picture + #----------------------------------------------------------------------------- + def command_235 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # Erase picture + $game_screen.pictures[number].erase + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Set Weather Effects + #----------------------------------------------------------------------------- + def command_236 + # Set Weather Effects + $game_screen.weather(@parameters[0], @parameters[1], @parameters[2]) + # Continue + return true + end + + def command_247 + # Memorize BGM/BGS + $game_system.bgm_memorize + $game_system.bgs_memorize + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Restore BGM/BGS + #----------------------------------------------------------------------------- + def command_248 + # Restore BGM/BGS + $game_system.bgm_restore + $game_system.bgs_restore + # Continue + return true + end + + def command_if(value) + if @branch[@list[@index].indent] == value + @branch.delete(@list[@index].indent) + return true + end + return command_skip + end + + def command_301; command_dummy; end # Battle Processing + def command_601; command_if(0); end # If Win + def command_602; command_if(1); end # If Escape + def command_603; command_if(2); end # If Lose + def command_302; command_dummy; end # Shop Processing + def command_303; command_dummy; end # Name Processing + def command_311; command_dummy; end # Change HP + def command_312; command_dummy; end # Change SP + def command_313; command_dummy; end # Change State + def command_314; command_dummy; end # Recover All + def command_315; command_dummy; end # Change EXP + def command_316; command_dummy; end # Change Level + def command_317; command_dummy; end # Change Parameters + def command_318; command_dummy; end # Change Skills + def command_319; command_dummy; end # Change Equipment + def command_320; command_dummy; end # Change Actor Name + def command_321; command_dummy; end # Change Actor Class + def command_322; command_dummy; end # Change Actor Graphic + def command_331; command_dummy; end # Change Enemy HP + def command_332; command_dummy; end # Change Enemy SP + def command_333; command_dummy; end # Change Enemy State + def command_334; command_dummy; end # Enemy Recover All + def command_335; command_dummy; end # Enemy Appearance + def command_336; command_dummy; end # Enemy Transform + def command_337; command_dummy; end # Show Battle Animation + def command_338; command_dummy; end # Deal Damage + def command_339; command_dummy; end # Force Action + def command_340; command_dummy; end # Abort Battle + #----------------------------------------------------------------------------- + # * Call Menu Screen + #----------------------------------------------------------------------------- + def command_351 + # Set menu calling flag + $game_temp.menu_calling = true + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Call Save Screen + #----------------------------------------------------------------------------- + def command_352 + # Set save calling flag + $game_temp.save_calling = true + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Game Over + #----------------------------------------------------------------------------- + def command_353 + # Set game over flag + $game_temp.gameover = true + # End + return false + end + #----------------------------------------------------------------------------- + # * Return to Title Screen + #----------------------------------------------------------------------------- + def command_354 + # Set return to title screen flag + $game_temp.to_title = true + # End + return false + end + #----------------------------------------------------------------------------- + # * Script + #----------------------------------------------------------------------------- + def command_355 + script = @list[@index].parameters[0] + "\n" + loop do + if @list[@index+1].code == 655 || @list[@index+1].code == 355 + script += @list[@index+1].parameters[0] + "\n" + else + break + end + @index += 1 + end + result = pbExecuteScript(script) + return true + end +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/002_EventHandlers.rb b/Data/Scripts/006_Events and files/002_EventHandlers.rb new file mode 100644 index 000000000..101fc4c12 --- /dev/null +++ b/Data/Scripts/006_Events and files/002_EventHandlers.rb @@ -0,0 +1,172 @@ +# Defines an event that procedures can subscribe to. +class Event + def initialize + @callbacks = [] + end + + # Sets an event handler for this event and removes all other event handlers. + def set(method) + @callbacks.clear + @callbacks.push(method) + end + + # Removes an event handler procedure from the event. + def -(method) + for i in 0...@callbacks.length + next if @callbacks[i]!=method + @callbacks.delete_at(i) + break + end + return self + end + + # Adds an event handler procedure from the event. + def +(method) + for i in 0...@callbacks.length + return self if @callbacks[i]==method + end + @callbacks.push(method) + return self + end + + # Clears the event of event handlers. + def clear + @callbacks.clear + end + + # Triggers the event and calls all its event handlers. Normally called only + # by the code where the event occurred. + # The first argument is the sender of the event, the second argument contains + # the event's parameters. If three or more arguments are given, this method + # supports the following callbacks: + # proc{ |sender,params| } where params is an array of the other parameters, and + # proc{ |sender,arg0,arg1,...| } + def trigger(*arg) + arglist = arg[1,arg.length] + for callback in @callbacks + if callback.arity>2 && arg.length==callback.arity + # Retrofitted for callbacks that take three or more arguments + callback.call(*arg) + else + callback.call(arg[0],arglist) + end + end + end + + # Triggers the event and calls all its event handlers. Normally called only + # by the code where the event occurred. The first argument is the sender of + # the event, the other arguments are the event's parameters. + def trigger2(*arg) + for callback in @callbacks + callback.call(*arg) + end + end +end + + + +class HandlerHash + def initialize(mod) + @mod = mod + @hash = {} + @addIfs = [] + @symbolCache = {} + end + + def fromSymbol(sym) + return sym unless sym.is_a?(Symbol) || sym.is_a?(String) + mod = Object.const_get(@mod) rescue nil + return nil if !mod + return mod.const_get(sym.to_sym) rescue nil + end + + def toSymbol(sym) + return sym.to_sym if sym.is_a?(Symbol) || sym.is_a?(String) + ret = @symbolCache[sym] + return ret if ret + mod = Object.const_get(@mod) rescue nil + return nil if !mod + for key in mod.constants + next if mod.const_get(key)!=sym + ret = key.to_sym + @symbolCache[sym] = ret + break + end + return ret + end + + def addIf(condProc,handler) + @addIfs.push([condProc,handler]) + end + + def add(sym,handler) # 'sym' can be an ID or symbol + id = fromSymbol(sym) + @hash[id] = handler if id + symbol = toSymbol(sym) + @hash[symbol] = handler if symbol + end + + def copy(src,*dests) + handler = self[src] + if handler + for dest in dests + self.add(dest,handler) + end + end + end + + def [](sym) # 'sym' can be an ID or symbol + id = fromSymbol(sym) + ret = nil + ret = @hash[id] if id && @hash[id] # Real ID from the item + symbol = toSymbol(sym) + ret = @hash[symbol] if symbol && @hash[symbol] # Symbol or string + unless ret + for addif in @addIfs + return addif[1] if addif[0].call(id) + end + end + return ret + end + + def trigger(sym,*args) + handler = self[sym] + return (handler) ? handler.call(fromSymbol(sym),*args) : nil + end + + def clear + @hash.clear + end +end + + + +class SpeciesHandlerHash < HandlerHash + def initialize + super(:PBSpecies) + end +end + + + +class AbilityHandlerHash < HandlerHash + def initialize + super(:PBAbilities) + end +end + + + +class ItemHandlerHash < HandlerHash + def initialize + super(:PBItems) + end +end + + + +class MoveHandlerHash < HandlerHash + def initialize + super(:PBMoves) + end +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/003_File_Mixins.rb b/Data/Scripts/006_Events and files/003_File_Mixins.rb new file mode 100644 index 000000000..05c4cf5c2 --- /dev/null +++ b/Data/Scripts/006_Events and files/003_File_Mixins.rb @@ -0,0 +1,163 @@ +module FileInputMixin + def fgetb + x=0 + ret=0 + each_byte do |i| + ret=i || 0 + break + end + return ret + end + + def fgetw + x=0 + ret=0 + each_byte do |i| + break if !i + ret|=(i<>3 + return 0 if index>=offset + self.pos=index*8 + return fgetdw + end + + def getLength(index) + self.binmode + self.pos=0 + offset=fgetdw>>3 + return 0 if index>=offset + self.pos=index*8+4 + return fgetdw + end + + def readName(index) + self.binmode + self.pos=0 + offset=fgetdw>>3 + return "" if index>=offset + self.pos=index<<3 + offset=fgetdw + length=fgetdw + return "" if length==0 + self.pos=offset + return read(length) + end +end + + + +module FileOutputMixin + def fputb(b) + b=b&0xFF + write(b.chr) + end + + def fputw(w) + 2.times do + b=w&0xFF + write(b.chr) + w>>=8 + end + end + + def fputdw(w) + 4.times do + b=w&0xFF + write(b.chr) + w>>=8 + end + end +end + + + +class File < IO +=begin + unless defined?(debugopen) + class << self + alias debugopen open + end + end + + def open(f,m="r") + debugopen("debug.txt","ab") { |file| file.write([f,m,Time.now.to_f].inspect+"\r\n") } + if block_given? + debugopen(f,m) { |file| yield file } + else + return debugopen(f,m) + end + end +=end + include FileInputMixin + include FileOutputMixin +end + + + +class StringInput + include FileInputMixin + + def pos=(value) + seek(value) + end + + def each_byte + while !eof? + yield getc + end + end + + def binmode + end +end + + + +class StringOutput + include FileOutputMixin +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/004_Intl_Messages.rb b/Data/Scripts/006_Events and files/004_Intl_Messages.rb new file mode 100644 index 000000000..ee1ddcab2 --- /dev/null +++ b/Data/Scripts/006_Events and files/004_Intl_Messages.rb @@ -0,0 +1,776 @@ +def pbAddScriptTexts(items,script) + script.scan(/(?:_I)\s*\(\s*\"((?:[^\\\"]*\\\"?)*[^\"]*)\"/) { |s| + string=s[0] + string.gsub!(/\\\"/,"\"") + string.gsub!(/\\\\/,"\\") + items.push(string) + } +end + +def pbAddRgssScriptTexts(items,script) + script.scan(/(?:_INTL|_ISPRINTF)\s*\(\s*\"((?:[^\\\"]*\\\"?)*[^\"]*)\"/) { |s| + string=s[0] + string.gsub!(/\\r/,"\r") + string.gsub!(/\\n/,"\n") + string.gsub!(/\\1/,"\1") + string.gsub!(/\\\"/,"\"") + string.gsub!(/\\\\/,"\\") + items.push(string) + } +end + +def pbSetTextMessages + Graphics.update + begin + t = Time.now.to_i + texts=[] + for script in $RGSS_SCRIPTS + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + scr=Zlib::Inflate.inflate(script[2]) + pbAddRgssScriptTexts(texts,scr) + end + # Must add messages because this code is used by both game system and Editor + MessageTypes.addMessagesAsHash(MessageTypes::ScriptTexts,texts) + commonevents=pbLoadRxData("Data/CommonEvents") + items=[] + choices=[] + for event in commonevents.compact + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + begin + neednewline=false + lastitem="" + for j in 0...event.list.size + list = event.list[j] + if neednewline && list.code!=401 + if lastitem!="" + lastitem.gsub!(/([^\.\!\?])\s\s+/) { |m| $1+" " } + items.push(lastitem) + lastitem="" + end + neednewline=false + end + if list.code == 101 + lastitem+="#{list.parameters[0]}" if !$RPGVX + neednewline=true + elsif list.code == 102 + for k in 0...list.parameters[0].length + choices.push(list.parameters[0][k]) + end + neednewline=false + elsif list.code == 401 + lastitem+=" " if lastitem!="" + lastitem+="#{list.parameters[0]}" + neednewline=true + elsif list.code == 355 || list.code == 655 + pbAddScriptTexts(items,list.parameters[0]) + elsif list.code == 111 && list.parameters[0]==12 + pbAddScriptTexts(items,list.parameters[1]) + elsif list.code == 209 + route=list.parameters[1] + for k in 0...route.list.size + if route.list[k].code == 45 + pbAddScriptTexts(items,route.list[k].parameters[0]) + end + end + end + end + if neednewline + if lastitem!="" + items.push(lastitem) + lastitem="" + end + end + end + end + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + items|=[] + choices|=[] + items.concat(choices) + MessageTypes.setMapMessagesAsHash(0,items) + mapinfos = pbLoadRxData("Data/MapInfos") + mapnames=[] + for id in mapinfos.keys + mapnames[id]=mapinfos[id].name + end + MessageTypes.setMessages(MessageTypes::MapNames,mapnames) + for id in mapinfos.keys + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + filename=sprintf("Data/Map%03d.%s",id,$RPGVX ? "rvdata" : "rxdata") + next if !pbRgssExists?(filename) + map = load_data(filename) + items=[] + choices=[] + for event in map.events.values + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + begin + for i in 0...event.pages.size + neednewline=false + lastitem="" + for j in 0...event.pages[i].list.size + list = event.pages[i].list[j] + if neednewline && list.code!=401 + if lastitem!="" + lastitem.gsub!(/([^\.\!\?])\s\s+/) { |m| $1+" " } + items.push(lastitem) + lastitem="" + end + neednewline=false + end + if list.code == 101 + lastitem+="#{list.parameters[0]}" if !$RPGVX + neednewline=true + elsif list.code == 102 + for k in 0...list.parameters[0].length + choices.push(list.parameters[0][k]) + end + neednewline=false + elsif list.code == 401 + lastitem+=" " if lastitem!="" + lastitem+="#{list.parameters[0]}" + neednewline=true + elsif list.code == 355 || list.code==655 + pbAddScriptTexts(items,list.parameters[0]) + elsif list.code == 111 && list.parameters[0]==12 + pbAddScriptTexts(items,list.parameters[1]) + elsif list.code==209 + route=list.parameters[1] + for k in 0...route.list.size + if route.list[k].code==45 + pbAddScriptTexts(items,route.list[k].parameters[0]) + end + end + end + end + if neednewline + if lastitem!="" + items.push(lastitem) + lastitem="" + end + end + end + end + end + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + items|=[] + choices|=[] + items.concat(choices) + MessageTypes.setMapMessagesAsHash(id,items) + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + end + rescue Hangup + end + Graphics.update +end + +def pbEachIntlSection(file) + lineno=1 + re=/^\s*\[\s*([^\]]+)\s*\]\s*$/ + havesection=false + sectionname=nil + lastsection=[] + file.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line=line[3,line.length-3] + end + if !line[/^\#/] && !line[/^\s*$/] + if line[re] + if havesection + yield lastsection,sectionname + end + lastsection.clear + sectionname=$~[1] + havesection=true + else + if sectionname==nil + raise _INTL("Expected a section at the beginning of the file (line {1})",lineno) + end + lastsection.push(line.gsub(/\s+$/,"")) + end + end + lineno+=1 + if lineno%500==0 + Graphics.update + end + } + if havesection + yield lastsection,sectionname + end +end + +def pbGetText(infile) + begin + file=File.open(infile,"rb") + rescue + raise _INTL("Can't find {1}",infile) + end + intldat=[] + begin + pbEachIntlSection(file) { |section,name| + next if section.length==0 + index=name + if !name[/^([Mm][Aa][Pp])?(\d+)$/] + raise _INTL("Invalid section name {1}",name) + end + ismap=$~[1] && $~[1]!="" + id=$~[2].to_i + itemlength=0 + if section[0][/^\d+$/] + intlhash=[] + itemlength=3 + if ismap + raise _INTL("Section {1} can't be an ordered list (section was recognized as an ordered list because its first line is a number)",name) + end + if section.length%3!=0 + raise _INTL("Section {1}'s line count is not divisible by 3 (section was recognized as an ordered list because its first line is a number)",name) + end + else + intlhash=OrderedHash.new + itemlength=2 + if section.length%2!=0 + raise _INTL("Section {1} has an odd number of entries (section was recognized as a hash because its first line is not a number)",name) + end + end + i=0;loop do break unless i0 + str+=@keys[i].inspect+"=>"+self[@keys[i]].inspect + end + str+="}" + return str + end + + alias :to_s :inspect + + def []=(key,value) + oldvalue=self[key] + if !oldvalue && value + @keys.push(key) + elsif !value + @keys|=[] + @keys-=[key] + end + return super(key,value) + end + + def self._load(string) + ret=self.new + keysvalues=Marshal.load(string) + keys=keysvalues[0] + values=keysvalues[1] + for i in 0...keys.length + ret[keys[i]]=values[i] + end + return ret + end + + def _dump(depth=100) + values=[] + for key in @keys + values.push(self[key]) + end + return Marshal.dump([@keys,values]) + end +end + + + +class Messages + def initialize(filename=nil,delayLoad=false) + @messages=nil + @filename=filename + if @filename && !delayLoad + loadMessageFile(@filename) + end + end + + def delayedLoad + if @filename && !@messages + loadMessageFile(@filename) + @filename=nil + end + end + + def self.stringToKey(str) + if str && str[/[\r\n\t\1]|^\s+|\s+$|\s{2,}/] + key=str.clone + key.gsub!(/^\s+/,"") + key.gsub!(/\s+$/,"") + key.gsub!(/\s{2,}/," ") + return key + end + return str + end + + def self.normalizeValue(value) + if value[/[\r\n\t\x01]|^[\[\]]/] + ret=value.clone + ret.gsub!(/\r/,"<>") + ret.gsub!(/\n/,"<>") + ret.gsub!(/\t/,"<>") + ret.gsub!(/\[/,"<<[>>") + ret.gsub!(/\]/,"<<]>>") + ret.gsub!(/\x01/,"<<1>>") + return ret + end + return value + end + + def self.denormalizeValue(value) + if value[/<<[rnt1\[\]]>>/] + ret=value.clone + ret.gsub!(/<<1>>/,"\1") + ret.gsub!(/<>/,"\r") + ret.gsub!(/<>/,"\n") + ret.gsub!(/<<\[>>/,"[") + ret.gsub!(/<<\]>>/,"]") + ret.gsub!(/<>/,"\t") + return ret + end + return value + end + + def self.writeObject(f,msgs,secname,origMessages=nil) + return if !msgs + if msgs.is_a?(Array) + f.write("[#{secname}]\r\n") + for j in 0...msgs.length + next if msgs[j]==nil || msgs[j]=="" + value=Messages.normalizeValue(msgs[j]) + origValue="" + if origMessages + origValue=Messages.normalizeValue(origMessages.get(secname,j)) + else + origValue=Messages.normalizeValue(MessageTypes.get(secname,j)) + end + f.write("#{j}\r\n") + f.write(origValue+"\r\n") + f.write(value+"\r\n") + end + elsif msgs.is_a?(OrderedHash) + f.write("[#{secname}]\r\n") + keys=msgs.keys + for key in keys + next if msgs[key]==nil || msgs[key]=="" + value=Messages.normalizeValue(msgs[key]) + valkey=Messages.normalizeValue(key) + # key is already serialized + f.write(valkey+"\r\n") + f.write(value+"\r\n") + end + end + end + + def messages + return @messages || [] + end + + def extract(outfile) +# return if !@messages + origMessages=Messages.new("Data/messages.dat") + File.open(outfile,"wb") { |f| + f.write(0xef.chr) + f.write(0xbb.chr) + f.write(0xbf.chr) + f.write("# To localize this text for a particular language, please\r\n") + f.write("# translate every second line of this file.\r\n") + if origMessages.messages[0] + for i in 0...origMessages.messages[0].length + msgs=origMessages.messages[0][i] + Messages.writeObject(f,msgs,"Map#{i}",origMessages) + end + end + for i in 1...origMessages.messages.length + msgs=origMessages.messages[i] + Messages.writeObject(f,msgs,i,origMessages) + end + } + end + + def setMessages(type,array) + @messages=[] if !@messages + arr=[] + for i in 0...array.length + arr[i]=(array[i]) ? array[i] : "" + end + @messages[type]=arr + end + + def addMessages(type,array) + @messages=[] if !@messages + arr=(@messages[type]) ? @messages[type] : [] + for i in 0...array.length + arr[i]=(array[i]) ? array[i] : (arr[i]) ? arr[i] : "" + end + @messages[type]=arr + end + + def self.createHash(type,array) + arr=OrderedHash.new + for i in 0...array.length + if array[i] + key=Messages.stringToKey(array[i]) + arr[key]=array[i] + end + end + return arr + end + + def self.addToHash(type,array,hash) + if !hash + hash=OrderedHash.new + end + for i in 0...array.length + if array[i] + key=Messages.stringToKey(array[i]) + hash[key]=array[i] + end + end + return hash + end + + def setMapMessagesAsHash(type,array) + @messages=[] if !@messages + @messages[0]=[] if !@messages[0] + @messages[0][type]=Messages.createHash(type,array) + end + + def addMapMessagesAsHash(type,array) + @messages=[] if !@messages + @messages[0]=[] if !@messages[0] + @messages[0][type]=Messages.addToHash(type,array,@messages[0][type]) + end + + def setMessagesAsHash(type,array) + @messages=[] if !@messages + @messages[type]=Messages.createHash(type,array) + end + + def addMessagesAsHash(type,array) + @messages=[] if !@messages + @messages[type]=Messages.addToHash(type,array,@messages[type]) + end + + def saveMessages(filename=nil) + filename="Data/messages.dat" if !filename + File.open(filename,"wb") { |f| Marshal.dump(@messages,f) } + end + + def loadMessageFile(filename) + begin + pbRgssOpen(filename,"rb") { |f| @messages=Marshal.load(f) } + if !@messages.is_a?(Array) + @messages=nil + raise "Corrupted data" + end + return @messages + rescue + @messages=nil + return nil + end + end + + def set(type,id,value) + delayedLoad + return if !@messages + return if !@messages[type] + @messages[type][id]=value + end + + def getCount(type) + delayedLoad + return 0 if !@messages + return 0 if !@messages[type] + return @messages[type].length + end + + def get(type,id) + delayedLoad + return "" if !@messages + return "" if !@messages[type] + return "" if !@messages[type][id] + return @messages[type][id] + end + + def getFromHash(type,key) + delayedLoad + return key if !@messages || !@messages[type] || !key + id=Messages.stringToKey(key) + return key if !@messages[type][id] + return @messages[type][id] + end + + def getFromMapHash(type,key) + delayedLoad + return key if !@messages + return key if !@messages[0] + return key if !@messages[0][type] && !@messages[0][0] + id=Messages.stringToKey(key) + if @messages[0][type] && @messages[0][type][id] + return @messages[0][type][id] + elsif @messages[0][0] && @messages[0][0][id] + return @messages[0][0][id] + end + return key + end +end + + + +module MessageTypes + # Value 0 is used for common event and map event text + Species = 1 + Kinds = 2 + Entries = 3 + FormNames = 4 + Moves = 5 + MoveDescriptions = 6 + Items = 7 + ItemPlurals = 8 + ItemDescriptions = 9 + Abilities = 10 + AbilityDescs = 11 + Types = 12 + TrainerTypes = 13 + TrainerNames = 14 + BeginSpeech = 15 + EndSpeechWin = 16 + EndSpeechLose = 17 + RegionNames = 18 + PlaceNames = 19 + PlaceDescriptions = 20 + MapNames = 21 + PhoneMessages = 22 + TrainerLoseText = 23 + ScriptTexts = 24 + @@messages = Messages.new + @@messagesFallback = Messages.new("Data/messages.dat",true) + + def self.stringToKey(str) + return Messages.stringToKey(str) + end + + def self.normalizeValue(value) + return Messages.normalizeValue(value) + end + + def self.denormalizeValue(value) + Messages.denormalizeValue(value) + end + + def self.writeObject(f,msgs,secname) + Messages.denormalizeValue(str) + end + + def self.extract(outfile) + @@messages.extract(outfile) + end + + def self.setMessages(type,array) + @@messages.setMessages(type,array) + end + + def self.addMessages(type,array) + @@messages.addMessages(type,array) + end + + def self.createHash(type,array) + Messages.createHash(type,array) + end + + def self.addMapMessagesAsHash(type,array) + @@messages.addMapMessagesAsHash(type,array) + end + + def self.setMapMessagesAsHash(type,array) + @@messages.setMapMessagesAsHash(type,array) + end + + def self.addMessagesAsHash(type,array) + @@messages.addMessagesAsHash(type,array) + end + + def self.setMessagesAsHash(type,array) + @@messages.setMessagesAsHash(type,array) + end + + def self.saveMessages(filename=nil) + @@messages.saveMessages(filename) + end + + def self.loadMessageFile(filename) + @@messages.loadMessageFile(filename) + end + + def self.get(type,id) + ret=@@messages.get(type,id) + if ret=="" + ret=@@messagesFallback.get(type,id) + end + return ret + end + + def self.getCount(type) + c1=@@messages.getCount(type) + c2=@@messagesFallback.getCount(type) + return c1>c2 ? c1 : c2 + end + + def self.getOriginal(type,id) + return @@messagesFallback.get(type,id) + end + + def self.getFromHash(type,key) + @@messages.getFromHash(type,key) + end + + def self.getFromMapHash(type,key) + @@messages.getFromMapHash(type,key) + end +end + + + +def pbLoadMessages(file) + return MessageTypes.loadMessageFile(file) +end + +def pbGetMessageCount(type) + return MessageTypes.getCount(type) +end + +def pbGetMessage(type,id) + return MessageTypes.get(type,id) +end + +def pbGetMessageFromHash(type,id) + return MessageTypes.getFromHash(type,id) +end + +# Replaces first argument with a localized version and formats the other +# parameters by replacing {1}, {2}, etc. with those placeholders. +def _INTL(*arg) + begin + string=MessageTypes.getFromHash(MessageTypes::ScriptTexts,arg[0]) + rescue + string=arg[0] + end + string=string.clone + for i in 1...arg.length + string.gsub!(/\{#{i}\}/,"#{arg[i]}") + end + return string +end + +# Replaces first argument with a localized version and formats the other +# parameters by replacing {1}, {2}, etc. with those placeholders. +# This version acts more like sprintf, supports e.g. {1:d} or {2:s} +def _ISPRINTF(*arg) + begin + string=MessageTypes.getFromHash(MessageTypes::ScriptTexts,arg[0]) + rescue + string=arg[0] + end + string=string.clone + for i in 1...arg.length + string.gsub!(/\{#{i}\:([^\}]+?)\}/) { |m| + next sprintf("%"+$1,arg[i]) + } + end + return string +end + +def _I(str) + return _MAPINTL($game_map.map_id,str) +end + +def _MAPINTL(mapid,*arg) + string=MessageTypes.getFromMapHash(mapid,arg[0]) + string=string.clone + for i in 1...arg.length + string.gsub!(/\{#{i}\}/,"#{arg[i]}") + end + return string +end + +def _MAPISPRINTF(mapid,*arg) + string=MessageTypes.getFromMapHash(mapid,arg[0]) + string=string.clone + for i in 1...arg.length + string.gsub!(/\{#{i}\:([^\}]+?)\}/) { |m| + next sprintf("%"+$1,arg[i]) + } + end + return string +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/005_PBDebug.rb b/Data/Scripts/006_Events and files/005_PBDebug.rb new file mode 100644 index 000000000..988c1ec07 --- /dev/null +++ b/Data/Scripts/006_Events and files/005_PBDebug.rb @@ -0,0 +1,40 @@ +module PBDebug + @@log = [] + + def self.logonerr + begin + yield + rescue + PBDebug.log("") + PBDebug.log("**Exception: #{$!.message}") + PBDebug.log("#{$!.backtrace.inspect}") + PBDebug.log("") +# if $INTERNAL + pbPrintException($!) +# end + PBDebug.flush + end + end + + def self.flush + if $DEBUG && $INTERNAL && @@log.length>0 + File.open("Data/debuglog.txt", "a+b") { |f| f.write("#{@@log}") } + end + @@log.clear + end + + def self.log(msg) + if $DEBUG && $INTERNAL + @@log.push("#{msg}\r\n") +# if @@log.length>1024 + PBDebug.flush +# end + end + end + + def self.dump(msg) + if $DEBUG && $INTERNAL + File.open("Data/dumplog.txt", "a+b") { |f| f.write("#{msg}\r\n") } + end + end +end \ No newline at end of file diff --git a/Data/Scripts/007_Audio/001_Audio.rb b/Data/Scripts/007_Audio/001_Audio.rb new file mode 100644 index 000000000..165da35f2 --- /dev/null +++ b/Data/Scripts/007_Audio/001_Audio.rb @@ -0,0 +1,376 @@ +class Thread + def Thread.exclusive + _old = Thread.critical + begin + Thread.critical = true + return yield + ensure + Thread.critical = _old + end + end +end + + + +def getPlayMusic + return MiniRegistry.get(MiniRegistry::HKEY_CURRENT_USER, + "SOFTWARE\\Enterbrain\\RGSS","PlayMusic",true) +end + +def getPlaySound + return MiniRegistry.get(MiniRegistry::HKEY_CURRENT_USER, + "SOFTWARE\\Enterbrain\\RGSS","PlaySound",true) +end + + + +class AudioContext + attr_reader :context + + def initialize + init = Win32API.new("audio.dll", "AudioContextInitialize", '', 'l') + @context=init.call() + end + + def dispose + if @context!=0 + init = Win32API.new("audio.dll", "AudioContextFree", 'l', '') + init.call(context) + @context=0 + end + end +end + + + +##################################### +# Needed because RGSS doesn't call at_exit procs on exit +# Exit is not called when game is reset (using F12) +$AtExitProcs=[] if !$AtExitProcs + +def exit(code=0) + for p in $AtExitProcs + p.call + end + raise SystemExit.new(code) +end + +def at_exit(&block) + $AtExitProcs.push(Proc.new(&block)) +end + +##################################### +# Works around a problem with FileTest.exist +# if directory contains accent marks +def safeExists?(f) + ret=false + File.open(f,"rb") { ret=true } rescue nil + return ret +end + + + +module AudioState + w32_LL = Win32API.new("kernel32.dll", "LoadLibrary", 'p', 'l') # :nodoc: + w32_FL = Win32API.new("kernel32.dll", "FreeLibrary", 'p', 'l')# :nodoc: + + if safeExists?("audio.dll") + @handle = w32_LL.call("audio.dll") + at_exit { w32_FL.call(@handle) } + AudioContextIsActive = Win32API.new("audio.dll","AudioContextIsActive","l","l")# :nodoc: + AudioContextPlay = Win32API.new("audio.dll","AudioContextPlay","lpllll","")# :nodoc: + AudioContextStop = Win32API.new("audio.dll","AudioContextStop","l","")# :nodoc: + AudioContextFadeOut = Win32API.new("audio.dll","AudioContextFadeOut","ll","")# :nodoc: + AudioContextGetPosition = Win32API.new("audio.dll","AudioContextGetPosition","l","l")# :nodoc: + AudioContextFadeIn = Win32API.new("audio.dll","AudioContextFadeIn","ll","")# :nodoc: + AudioContextSetVolume = Win32API.new("audio.dll","AudioContextSetVolume","ll","")# :nodoc: + AudioContextSEPlay = Win32API.new("audio.dll","AudioContextSEPlay","lplll","")# :nodoc: + if !@MEContext + @MEContext=AudioContext.new + at_exit { @MEContext.dispose } + end + if !@BGMContext + @BGMContext=AudioContext.new + at_exit { @BGMContext.dispose } + end + if !@BGSContext + @BGSContext=AudioContext.new + at_exit { @BGSContext.dispose } + end + if !@SEContext + @SEContext=AudioContext.new + at_exit { @SEContext.dispose } + end + else + AudioContextIsActive = nil # :nodoc: + AudioContextPlay = nil # :nodoc: + AudioContextStop = nil # :nodoc: + AudioContextFadeOut = nil # :nodoc: + AudioContextGetPosition = nil # :nodoc: + AudioContextFadeIn = nil # :nodoc: + AudioContextSetVolume = nil # :nodoc: + AudioContextSEPlay = nil # :nodoc: + end + + @channel = nil + @bgm = nil + @name = "" + @pitch = 100 + @bgmVolume = 100.0 + @meVolume = 100.0 + @bgsVolume = 100.0 + @seVolume = 100.0 + + def self.setWaitingBGM(bgm,volume,pitch,position) + @waitingBGM=[bgm,volume,pitch,position] + end + + def self.bgmActive? + return !@BGMContext ? false : (AudioContextIsActive.call(@BGMContext.context)!=0) + end + + def self.meActive? + return !@MEContext ? false : (AudioContextIsActive.call(@MEContext.context)!=0) + end + + def self.waitingBGM; @waitingBGM; end + def self.context; @BGMContext ? @BGMContext.context : nil; end + def self.meContext; @MEContext ? @MEContext.context : nil; end + def self.bgsContext; @BGSContext ? @BGSContext.context : nil; end + def self.seContext; @SEContext ? @SEContext.context : nil; end + def self.system; @system; end + def self.bgm; @bgm; end + def self.name; @name; end + def self.pitch; @pitch; end + def self.volume; @volume; end + + def self.waitingBGM=(value); + Thread.exclusive { @waitingBGM=value; } + end + + def self.volume=(value); @volume=value; end + def self.bgm=(value); @bgm=value; end + def self.name=(value); @name=value; end + def self.pitch=(value); @pitch=value; end +end + + + +def Audio_bgm_playing? + AudioState.channel!=nil +end + +def Audio_bgm_name + AudioState.name +end + +def Audio_bgm_pitch + AudioState.pitch +end + +def Audio_bgm_play(name, volume, pitch, position = 0) + volume=0 if !getPlayMusic() + begin + filename = canonicalize(RTP.getAudioPath(name)) + if AudioState.meActive? + AudioState.setWaitingBGM(filename,volume,pitch,position) + return + end + AudioState::AudioContextPlay.call(AudioState.context,filename,volume,pitch,position,1) + AudioState.name=filename + AudioState.volume=volume + AudioState.pitch=pitch + rescue Hangup + rescue + p $!.message,$!.backtrace + end +end + +def Audio_bgm_fadein(ms) + AudioState::AudioContextFadeIn.call(AudioState.context,ms.to_i) +end + +def Audio_bgm_fade(ms) + AudioState::AudioContextFadeOut.call(AudioState.context,ms.to_i) +end + +def Audio_bgm_stop() + begin + AudioState::AudioContextStop.call(AudioState.context) + AudioState.waitingBGM=nil + AudioState.name = "" + rescue + p $!.message,$!.backtrace + end +end + +def Audio_bgm_get_position + return AudioState::AudioContextGetPosition.call(AudioState.context) +end + +def Audio_bgm_get_volume + return 0 if !AudioState.bgmActive? + return AudioState.volume +end + +def Audio_bgm_set_volume(volume) + return if !AudioState.bgmActive? + AudioState.volume = volume * 1.0 + AudioState::AudioContextSetVolume.call(AudioState.context,volume.to_i) +end + +def Audio_me_play(name, volume, pitch, position = 0) + volume=0 if !getPlayMusic() + begin + filename = canonicalize(RTP.getAudioPath(name)) + if AudioState.bgmActive? + bgmPosition=Audio_bgm_get_position + AudioState.setWaitingBGM( + AudioState.name, + AudioState.volume, + AudioState.pitch, + bgmPosition + ) + AudioState::AudioContextStop.call(AudioState.context) + end + AudioState::AudioContextPlay.call(AudioState.meContext,filename, + volume,pitch,position,0) + rescue + p $!.message,$!.backtrace + end +end + +def Audio_me_fade(ms) + AudioState::AudioContextFadeOut.call(AudioState.meContext,ms) +end + +def Audio_me_stop() + AudioState::AudioContextStop.call(AudioState.meContext) +end + +def Audio_bgs_play(name, volume, pitch, position = 0) + volume=0 if !getPlaySound() + begin + filename = canonicalize(RTP.getAudioPath(name)) + AudioState::AudioContextPlay.call(AudioState.bgsContext,filename, + volume,pitch,position,0) + rescue + p $!.message,$!.backtrace + end +end + +def Audio_bgs_fade(ms) + AudioState::AudioContextFadeOut.call(AudioState.bgsContext,ms) +end + +def Audio_bgs_stop() + AudioState::AudioContextStop.call(AudioState.bgsContext) +end + +def Audio_se_play(name, volume, pitch, position = 0) + volume=0 if !getPlaySound() + begin + filename = canonicalize(RTP.getAudioPath(name)) + AudioState::AudioContextSEPlay.call(AudioState.seContext,filename, + volume,pitch,position) + rescue + p $!.message,$!.backtrace + end +end + +def Audio_se_stop() + AudioState::AudioContextStop.call(AudioState.seContext) +end + + + +#################################################### +if safeExists?("audio.dll") + module Graphics + if !defined?(audiomodule_update) + class << self + alias audiomodule_update update + end + end + + def self.update + Audio.update + audiomodule_update + end + end + + + + module Audio + @@musicstate = nil + @@soundstate = nil + + def self.update + return if Graphics.frame_count%10!=0 + if AudioState.waitingBGM && !AudioState.meActive? + waitbgm=AudioState.waitingBGM + AudioState.waitingBGM=nil + bgm_play(waitbgm[0],waitbgm[1],waitbgm[2],waitbgm[3]) + end + end + + def self.bgm_play(name,volume=80,pitch=100,position=nil) + begin + if position==nil || position==0 + Audio_bgm_play(name,volume,pitch,0) + else + Audio_bgm_play(name,volume,pitch,position) + Audio_bgm_fadein(500) + end + rescue Hangup + bgm_play(name,volume,pitch,position) + end + end + + def self.bgm_fade(ms) + Audio_bgm_fade(ms) + end + + def self.bgm_stop + Audio_bgm_stop() + end + + def self.bgm_position + return Audio_bgm_get_position + end + + def self.me_play(name,volume=80,pitch=100) + Audio_me_play(name,volume,pitch,0) + end + + def self.me_fade(ms) + Audio_me_fade(ms) + end + + def self.me_stop + Audio_me_stop() + end + + def self.bgs_play(name,volume=80,pitch=100) + Audio_bgs_play(name,volume,pitch,0) + end + + def self.bgs_fade(ms) + Audio_bgs_fade(ms) + end + + def self.bgs_stop + Audio_bgs_stop() + end + +=begin + def self.se_play(name,volume=80,pitch=100) + Audio_se_play(name,volume,pitch,0) + end + + def self.se_stop + Audio_se_stop() + end +=end + end +end # safeExists?("audio.dll") diff --git a/Data/Scripts/007_Audio/002_AudioPlay.rb b/Data/Scripts/007_Audio/002_AudioPlay.rb new file mode 100644 index 000000000..b7b3e28b9 --- /dev/null +++ b/Data/Scripts/007_Audio/002_AudioPlay.rb @@ -0,0 +1,292 @@ +def pbStringToAudioFile(str) + if str[/^(.*)\:\s*(\d+)\s*\:\s*(\d+)\s*$/] # Of the format "XXX: ###: ###" + file = $1 + volume = $2.to_i + pitch = $3.to_i + return RPG::AudioFile.new(file,volume,pitch) + elsif str[/^(.*)\:\s*(\d+)\s*$/] # Of the format "XXX: ###" + file = $1 + volume = $2.to_i + return RPG::AudioFile.new(file,volume,100) + else + return RPG::AudioFile.new(str,100,100) + end +end + +# Converts an object to an audio file. +# str -- Either a string showing the filename or an RPG::AudioFile object. +# Possible formats for _str_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbResolveAudioFile(str,volume=nil,pitch=nil) + if str.is_a?(String) + str = pbStringToAudioFile(str) + str.volume = volume || 100 + str.pitch = pitch || 100 + end + if str.is_a?(RPG::AudioFile) + if volume || pitch + return RPG::AudioFile.new(str.name,volume || str.volume || 100 , + pitch || str.pitch || 100) + else + return str + end + end + return str +end + +################################################################################ + +# Plays a BGM file. +# param -- Either a string showing the filename +# (relative to Audio/BGM/) or an RPG::AudioFile object. +# Possible formats for _param_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbBGMPlay(param,volume=nil,pitch=nil) + return if !param + param=pbResolveAudioFile(param,volume,pitch) + if param.name && param.name!="" + if $game_system && $game_system.respond_to?("bgm_play") + $game_system.bgm_play(param) + return + elsif (RPG.const_defined?(:BGM) rescue false) + b=RPG::BGM.new(param.name,param.volume,param.pitch) + if b && b.respond_to?("play") + b.play + return + end + end + Audio.bgm_play(canonicalize("Audio/BGM/"+param.name),param.volume,param.pitch) + end +end + +# Fades out or stops BGM playback. 'x' is the time in seconds to fade out. +def pbBGMFade(x=0.0); pbBGMStop(x);end + +# Fades out or stops BGM playback. 'x' is the time in seconds to fade out. +def pbBGMStop(timeInSeconds=0.0) + if $game_system && timeInSeconds>0.0 && $game_system.respond_to?("bgm_fade") + $game_system.bgm_fade(timeInSeconds) + return + elsif $game_system && $game_system.respond_to?("bgm_stop") + $game_system.bgm_stop + return + elsif (RPG.const_defined?(:BGM) rescue false) + begin + (timeInSeconds>0.0) ? RPG::BGM.fade((timeInSeconds*1000).floor) : RPG::BGM.stop + return + rescue + end + end + (timeInSeconds>0.0) ? Audio.bgm_fade((timeInSeconds*1000).floor) : Audio.bgm_stop +end + +################################################################################ + +# Plays an ME file. +# param -- Either a string showing the filename +# (relative to Audio/ME/) or an RPG::AudioFile object. +# Possible formats for _param_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbMEPlay(param,volume=nil,pitch=nil) + return if !param + param=pbResolveAudioFile(param,volume,pitch) + if param.name && param.name!="" + if $game_system && $game_system.respond_to?("me_play") + $game_system.me_play(param) + return + elsif (RPG.const_defined?(:ME) rescue false) + b=RPG::ME.new(param.name,param.volume,param.pitch) + if b && b.respond_to?("play") + b.play; return + end + end + Audio.me_play(canonicalize("Audio/ME/"+param.name),param.volume,param.pitch) + end +end + +# Fades out or stops ME playback. 'x' is the time in seconds to fade out. +def pbMEFade(x=0.0); pbMEStop(x);end + +# Fades out or stops ME playback. 'x' is the time in seconds to fade out. +def pbMEStop(timeInSeconds=0.0) + if $game_system && timeInSeconds>0.0 && $game_system.respond_to?("me_fade") + $game_system.me_fade(timeInSeconds) + return + elsif $game_system && $game_system.respond_to?("me_stop") + $game_system.me_stop(nil) + return + elsif (RPG.const_defined?(:ME) rescue false) + begin + (timeInSeconds>0.0) ? RPG::ME.fade((timeInSeconds*1000).floor) : RPG::ME.stop + return + rescue + end + end + (timeInSeconds>0.0) ? Audio.me_fade((timeInSeconds*1000).floor) : Audio.me_stop +end + +################################################################################ + +# Plays a BGS file. +# param -- Either a string showing the filename +# (relative to Audio/BGS/) or an RPG::AudioFile object. +# Possible formats for _param_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbBGSPlay(param,volume=nil,pitch=nil) + return if !param + param=pbResolveAudioFile(param,volume,pitch) + if param.name && param.name!="" + if $game_system && $game_system.respond_to?("bgs_play") + $game_system.bgs_play(param) + return + elsif (RPG.const_defined?(:BGS) rescue false) + b=RPG::BGS.new(param.name,param.volume,param.pitch) + if b && b.respond_to?("play") + b.play; return + end + end + Audio.bgs_play(canonicalize("Audio/BGS/"+param.name),param.volume,param.pitch) + end +end + +# Fades out or stops BGS playback. 'x' is the time in seconds to fade out. +def pbBGSFade(x=0.0); pbBGSStop(x);end + +# Fades out or stops BGS playback. 'x' is the time in seconds to fade out. +def pbBGSStop(timeInSeconds=0.0) + if $game_system && timeInSeconds>0.0 && $game_system.respond_to?("bgs_fade") + $game_system.bgs_fade(timeInSeconds) + return + elsif $game_system && $game_system.respond_to?("bgs_play") + $game_system.bgs_play(nil) + return + elsif (RPG.const_defined?(:BGS) rescue false) + begin + (timeInSeconds>0.0) ? RPG::BGS.fade((timeInSeconds*1000).floor) : RPG::BGS.stop + return + rescue + end + end + (timeInSeconds>0.0) ? Audio.bgs_fade((timeInSeconds*1000).floor) : Audio.bgs_stop +end + +################################################################################ + +# Plays an SE file. +# param -- Either a string showing the filename +# (relative to Audio/SE/) or an RPG::AudioFile object. +# Possible formats for _param_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbSEPlay(param,volume=nil,pitch=nil) + return if !param + param = pbResolveAudioFile(param,volume,pitch) + if param.name && param.name!="" + if $game_system && $game_system.respond_to?("se_play") + $game_system.se_play(param) + return + end + if (RPG.const_defined?(:SE) rescue false) + b = RPG::SE.new(param.name,param.volume,param.pitch) + if b && b.respond_to?("play") + b.play + return + end + end + Audio.se_play(canonicalize("Audio/SE/"+param.name),param.volume,param.pitch) + end +end + +# Stops SE playback. +def pbSEFade(x=0.0); pbSEStop(x);end + +# Stops SE playback. +def pbSEStop(timeInSeconds=0.0) + if $game_system + $game_system.se_stop + elsif (RPG.const_defined?(:SE) rescue false) + RPG::SE.stop rescue nil + else + Audio.se_stop + end +end + +################################################################################ + +# Plays a sound effect that plays when the player moves the cursor. +def pbPlayCursorSE + if $data_system && $data_system.respond_to?("cursor_se") && + $data_system.cursor_se && $data_system.cursor_se.name!="" + pbSEPlay($data_system.cursor_se) + elsif $data_system && $data_system.respond_to?("sounds") && + $data_system.sounds && $data_system.sounds[0] && $data_system.sounds[0].name!="" + pbSEPlay($data_system.sounds[0]) + elsif FileTest.audio_exist?("Audio/SE/GUI sel cursor") + pbSEPlay("GUI sel cursor",80) + end +end + +# Plays a sound effect that plays when a decision is confirmed or a choice is made. +def pbPlayDecisionSE + if $data_system && $data_system.respond_to?("decision_se") && + $data_system.decision_se && $data_system.decision_se.name!="" + pbSEPlay($data_system.decision_se) + elsif $data_system && $data_system.respond_to?("sounds") && + $data_system.sounds && $data_system.sounds[1] && $data_system.sounds[1].name!="" + pbSEPlay($data_system.sounds[1]) + elsif FileTest.audio_exist?("Audio/SE/GUI sel decision") + pbSEPlay("GUI sel decision",80) + end +end + +# Plays a sound effect that plays when a choice is canceled. +def pbPlayCancelSE + if $data_system && $data_system.respond_to?("cancel_se") && + $data_system.cancel_se && $data_system.cancel_se.name!="" + pbSEPlay($data_system.cancel_se) + elsif $data_system && $data_system.respond_to?("sounds") && + $data_system.sounds && $data_system.sounds[2] && $data_system.sounds[2].name!="" + pbSEPlay($data_system.sounds[2]) + elsif FileTest.audio_exist?("Audio/SE/GUI sel cancel") + pbSEPlay("GUI sel cancel",80) + end +end + +# Plays a buzzer sound effect. +def pbPlayBuzzerSE + if $data_system && $data_system.respond_to?("buzzer_se") && + $data_system.buzzer_se && $data_system.buzzer_se.name!="" + pbSEPlay($data_system.buzzer_se) + elsif $data_system && $data_system.respond_to?("sounds") && + $data_system.sounds && $data_system.sounds[3] && $data_system.sounds[3].name!="" + pbSEPlay($data_system.sounds[3]) + elsif FileTest.audio_exist?("Audio/SE/GUI sel buzzer") + pbSEPlay("GUI sel buzzer",80) + end +end + +# Plays a sound effect that plays when the player moves the cursor. +def pbPlayCloseMenuSE + if FileTest.audio_exist?("Audio/SE/GUI menu close") + pbSEPlay("GUI menu close",80) + end +end \ No newline at end of file diff --git a/Data/Scripts/007_Audio/003_AudioUtilities.rb b/Data/Scripts/007_Audio/003_AudioUtilities.rb new file mode 100644 index 000000000..8b1eea711 --- /dev/null +++ b/Data/Scripts/007_Audio/003_AudioUtilities.rb @@ -0,0 +1,1350 @@ +=begin +This script contains various utility functions and classes for dealing +with audio. This is a stand-alone script. + +Audio.square(durationInMs,freq,volume,timbre,async) - Generates a square wave. +Audio.beep(durationInMs,freq,volume,timbre,async) - Alias for Audio.square +Audio.sine(durationInMs,freq,volume,timbre,async) - Generates a sine wave. +Audio.triangle(durationInMs,freq,volume,timbre,async) - Generates a triangle wave. +Audio.saw(durationInMs,freq,volume,async) - Generates a saw wave. +Audio.noise(durationInMs,volume,async) - Generates white noise. +Audio.playTone(toneFile,async) - Plays a tone in the Apple iPod alarm tone format. +Parameters: + durationInMs - duration of the sound in milliseconds. + The module Audio::NoteLength contains useful durations for tones. + If 0 or nil, the frequency is determined using the maximum duration + of the given sound envelopes. + freq - the frequency of the sound in Hz. The higher the frequency, + the higher the pitch. If 0, no sound will be generated. + The module Audio::Note contains useful frequencies for tones. + freq can also be a SoundEnvelope or an array of two element arrays, + as follows: + freq[0] - time in ms to apply the specified frequency + freq[1] - frequency to apply. In between, values will be interpolated + volume - volume of the sound, from 0 through 100 + volume can also be a SoundEnvelope. + async - specifies whether the function will return immediately + without waiting for the sound to finish (stands for asynchronous) + timbre - specifies the timbre of the tone; from 0.0 through 1.0 + timbre can also be a SoundEnvelope or an array of two element arrays, + as follows: + volume[0] - time in ms to apply the specified timbre + volume[1] - timbre to apply. In between, values will be interpolated + +WaveData - A class for holding audio data in memory. This class +is easy to serialize into the save file. + intensity() - Calculates the intensity, or loudness of the data + Returns a value from 0 through 127. + time() - Length of the data in seconds. + play() - Plays the wave data + +getPlayTime(filename) - Gets the length of an audio file in seconds. + Supports WAV, MP3, and OGG files. +getWaveData(filename) - Creates wave data from the given WAV file path. + Returns a WaveData object or an integer: 1=not found; 2=invalid format; + 3=format not supported; 4=no sound in the data (the last error is helpful + for diagnosing whether anything was recorded, since a recording device + can record even if no microphone is attached.) + +beginRecord() - Starts recording. Returns 0 if successful. +getRecorderSample() - Gets a single sample from the microphone. + The beginRecord function must have been called beforehand. +stopRecord() - Stops recording without saving the recording to a file. +endRecord(file) - Stops recording and saves the recording to a file. +=end + +if !defined?(safeExists?) + def safeExists?(f) + ret=false + File.open(f,"rb") { ret=true } rescue nil + return ret + end +end + +def pbSaveSoundData(samples, freq, filename) + samples="" if !samples + data=[ + 0x46464952,samples.length+0x2C, + 0x45564157,0x20746d66,0x10, + 0x01,0x01, # PCM,mono + freq,freq, + 1,8, # 8-bit + 0x61746164,samples.length + ].pack("VVVVVvvVVvvVV") + f=File.open(filename,"wb") + if f + f.write(data) + f.write(samples) + f.close + end +end + +# plays 8 bit mono sound data (default: 11025 Hz) +def pbPlaySoundData(samples,volume,async=false,sampleFreq=11025) + return if !samples || samples.length==0 || sampleFreq==0 + waveOutOpen = Win32API.new("winmm.dll","waveOutOpen","plplll","l") + waveOutPrepareHeader = Win32API.new("winmm.dll","waveOutPrepareHeader","lpl","l") + waveOutWrite = Win32API.new("winmm.dll","waveOutWrite","lpl","l") + waveOutSetVolume = Win32API.new("winmm.dll","waveOutSetVolume","ll","l") + waveOutClose = Win32API.new("winmm.dll","waveOutClose","l","l") + waveOutGetNumDevs = Win32API.new("winmm.dll","waveOutGetNumDevs","","l") + getStringAddress = proc { |obj| + next 0 if !obj + buffer=" "*4 + rtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') + stringPointer=(obj.__id__*2)+12 + rtlMoveMemory_pi.call(buffer,stringPointer,4) + next buffer.unpack("L")[0] + } + saveToTemp = proc { |samples,freq| + chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + ret = nil + for i in 1...1000 + name="" + 8.times { name += chars[rand(chars.length),1] } + name = ENV["TEMP"]+"\\"+name+"_tmp.wav" + next if safeExists?(name) + pbSaveSoundData(samples,freq,name) + ret = name + break + end + return ret + } + playThenDelete = proc { |path,volume,length,async| + next if !path || !safeExists?(path) + thread=Thread.new{ + Thread.stop + _path=Thread.current[:path] + _length=Thread.current[:length] + sleep(_length) + File.delete(_path) rescue nil + } + thread[:path]=path + thread[:length]=length + Audio.se_play(path,volume) + thread.run + sleep(length) + } + waveHdr=[getStringAddress.call(samples),samples.length,0,0,0,0,0,0].pack("V*") + # 8 bit mono sound data + waveFormat=[0x01,0x01,sampleFreq,sampleFreq,1,8,0].pack("vvVVvvv") + duration=samples.length + waveOutHandle=" "*4 + code=waveOutOpen.call(waveOutHandle,-1,waveFormat,0,0,0) + if code!=0 + timeLength=samples.length.to_f/sampleFreq + path=saveToTemp.call(samples,sampleFreq) + playThenDelete.call(path,volume,timeLength,async) + return + end + waveOutHandle=waveOutHandle.unpack("L")[0] + volume=(volume*65535/100) + volume=(volume<<16)|volume + waveOutSetVolume.call(waveOutHandle,volume) + code=waveOutPrepareHeader.call(waveOutHandle,waveHdr,waveHdr.length) + if code!=0 + waveOutClose.call(waveOutHandle) + return + end + thread=Thread.new{ + Thread.stop + waveOut=Thread.current[:waveOut] + waveHdr=Thread.current[:waveHeader] + waveData=Thread.current[:waveData] + waveOutUnprepareHeader=Win32API.new("winmm.dll","waveOutUnprepareHeader","lpl","l") + waveOutClose=Win32API.new("winmm.dll","waveOutClose","l","l") + loop do + sleep(1) + hdr=waveHdr.unpack("V*") + flags=hdr[4] + if (flags&1)==1 + # All done + waveOutUnprepareHeader.call(waveOut,waveHdr,waveHdr.length) + waveOutClose.call(waveOut) + break + end + end + } + thread[:waveOut]=waveOutHandle + thread[:waveHeader]=waveHdr + thread[:waveData]=@samples + if waveOutWrite.call(waveOutHandle,waveHdr,waveHdr.length)!=0 + waveOutClose.call(waveOutHandle) + return + end + thread.run + sleep(@duration/1000.0) if !async + return +end + + + +class NoteEnvelope + attr_accessor :fall + attr_accessor :max + attr_reader :envelope + + def initialize(fall=1200,maxPoint=30) + @fall=fall # time until fall to zero + @maxPoint=maxPoint # maximum point + @envelope=SoundEnvelope.new + end + + def falling(duration) + return self if duration<=0 + @envelope.changeDiscrete(0,@maxPoint) + if duration>=@fall + @envelope.change(fall,0) + @envelope.change(duration-fall,0) + else + @envelope.change(duration,@maxPoint*duration/@fall) + end + @envelope.changeDiscrete(0,0) + return self + end + + def sweeping(duration,sweepDuration) + return self if duration<=0 + return steady(duration) if sweepDuration<=0 + @envelope.changeDiscrete(0,@maxPoint) + falling=true + while duration>0 + dur=duration>sweepDuration ? sweepDuration : duration + if falling + self.falling(dur) + else + sd=sweepDuration + if sd>@fall + d=[(sweepDuration-@fall),dur].min + @envelope.change(d,0) + dur-=d + sd-=d + end + if d==sd + @envelope.change(dur,@maxPoint) + else + @envelope.change(dur,@maxPoint*(@fall-(sd-dur))/@fall) + end + end + falling=!falling + duration-=sweepDuration + end + @envelope.changeDiscrete(0,0) + return self + end + + def rest(duration) + if duration>0 + @envelope.changeDiscrete(0,0) + @envelope.changeDiscrete(duration,0) + end + return self + end + + def steady(duration) + if duration>0 + @envelope.changeDiscrete(0,@maxPoint) + @envelope.changeDiscrete(duration,@maxPoint) + @envelope.changeDiscrete(0,0) + end + return self + end +end + + + +# A class for holding audio data in memory. This class +# is easy to serialize into the save file. +class WaveData + def initialize(samplesPerSec,samples) + @freq=samplesPerSec + @samples=samples.is_a?(String) ? samples.clone : samples.pack("C*") + end + + def setSamples(samples) + @samples=samples.is_a?(String) ? samples.clone : samples + end + + def self._load(string) + data=Marshal.load(string) + ret=self.new(data[0],[]) + ret.setSamples(Zlib::Inflate.inflate(data[1])) + return ret + end + + def _dump(depth=100) + return Marshal.dump([@freq,Zlib::Deflate.deflate(@samples)]) + end + + def intensity + distance=@samples.length/2000 + i=distance/2 + count=0 + volume=0 + while i<@samples.length + vol=(@samples[i]-128).abs + vol=127 if vol>127 + if vol>=16 + volume+=vol + count+=1 + end + i+=distance + end + return 0 if count==0 + return volume/count # from 0 through 127 + end + + def time + return @freq==0 ? 0.0 : (@samples.length)*1.0/@freq + end + + def play + # Play sound data asynchronously + pbPlaySoundData(@samples,100,true,@freq) + end + + def save(filename) + pbSaveSoundData(@samples,@freq,filename) + end +end + + + +# A class for specifying volume, frequency, and timbre envelopes. +class SoundEnvelope; include Enumerable + def initialize(env=nil) + @e=[]; + set(env) if env + end + + def self.fromToneFile(file) + envelope=self.new + File.open(file,"rb") { |f| + f.gets if !f.eof? + while !f.eof? + ln=f.gets + if ln[ /^(\d+)\s+(\d+)/ ] + envelope.addValueChange($1.to_i,$2.to_i) + end + end + } + return envelope + end + + def length; @e.length; end + def [](x); @e[x]; end + def each; @e.each { |x| yield x }; end + def clear; @e.clear; end + + def changeAbsolute(pos,volume) + return self if pos<0 + velength=@e.length + if velength>0 + @e.push([pos,volume]) + stableSort() + else + @e.push([pos,volume]) + end + return self + end + + def self.initial(value) + return self.new.change(0,value) + end + + def self.smoothVolume(duration,volume) + env=self.new + return env if duration<8 + env.change(0,0) + env.change(5,volume) + env.changeAbsolute(duration-10,volume) + env.changeAbsolute(duration-5,0) + env.changeAbsolute(duration,0) + return env + end + + # Creates a volume envelope using the given attack, decay, and + # release times and the given sustain level. + # duration - duration of the sound + # attack - attack time (in ms), or time between the start of the sound + # and the time where the sound reaches its maximum volume. + # If this value is less than 0, the sound will decay from silence to + # the sustain volume instead (see below). + # decay - decay time (in ms), or time after the attack phase until + # the time where the sound reaches its sustain volume + # sustain - sustain volume, or normal volume of sound (0-100) + # release - release time (in ms), or amount of time to fade out the + # sound when it reaches its end. The sound's duration includes its + # release time. + def self.attackDecayRelease(duration,attack,decay,sustain,release) + env=self.new + if attack<=0 + env.change(0,attack==0 ? 100 : 0) + else + env.change(attack,100) + end + env.change(decay,sustain) + env.changeAbsolute(duration-release,sustain) + if release>20 + env.changeAbsolute(duration-release-4,0) + end + env.changeAbsolute(duration,0) + return env + end + + def self.blink(value,onDuration,offDuration,totalDuration) + return self.new.addValueChanges( + value,onDuration,0,offDuration).repeat(totalDuration) + end + + def change(delta,volume) + return self if delta<0 + velength=@e.length + if velength>0 + @e.push([@e[velength-1][0]+delta,volume]) + else + @e.push([delta,volume]) + end + return self + end + + def duration + return @e.length==0 ? 0 : @e[@e.length-1][0] + end + + def value + return @e.length==0 ? 0 : @e[@e.length-1][1] + end + + def addValueChange(value,duration) + changeDiscrete(0,value) + changeDiscrete(duration,value) + return self + end + + def sweep(value1,value2,duration,sweepDuration=1000/16) + val=true + while duration>0 + dur=durationdesiredDuration + deltaNew=(newDuration-self.duration) + deltaDesired=(desiredDuration-self.duration) + newValue=deltaNew==0 ? self.value : self.value+(item[1]-self.value)*deltaDesired/deltaNew + @e.push([desiredDuration,newValue]) + break + else + @e.push([newDuration,item[1]]) + end + i+=1 + if i>=oldLength + i=0; currentDuration+=oldDuration + end + end + return self + end + + # Changes the volume, frequency, etc. abruptly without blending. + def changeDiscrete(delta,volume) + return self if delta<0 + velength=@e.length + if velength>0 + oldValue=@e[velength-1][1] + oldDelta=@e[velength-1][0] + newDelta=oldDelta+delta + newValue=oldValue + if newDelta!=oldDelta || newValue!=oldValue + @e.push([newDelta,newValue]) + oldDelta=newDelta + oldValue=newValue + end + newValue=volume + if newDelta!=oldDelta || newValue!=oldValue + @e.push([newDelta,newValue]) + end + else + @e.push([delta,volume]) + end + return self + end + + def set(value) + if value.is_a?(SoundEnvelope) || value.is_a?(Array) + @e.clear + for v in value; @e.push(v); end + end + return self + end + + private + + def stableSort + pm=1;while pm<@e.length + pl=pm; while pl>0 && @e[pl-1][0]>@e[pl][0] + tmp=@e[pl]; @e[pl]=@e[pl-1]; @e[pl-1]=tmp + pl-=1; end + pm+=1;end + end +end + + + +# internal class +class SoundEnvelopeIterator# :nodoc: + def initialize(env) + @env=env + @envIndex=0 + end + + def getValue(frame) + value=0 + if @envIndex==@env.length + value=@env[@envIndex-1][1] + elsif @envIndex==0 + value=@env[@envIndex][1] + else + lastPos=@env[@envIndex-1][0] + thisPos=@env[@envIndex][0] + if thisPos!=lastPos + lastVolume=@env[@envIndex-1][1] + thisVolume=@env[@envIndex][1] + value=(thisVolume-lastVolume)*(frame-lastPos)/(thisPos-lastPos)+lastVolume + else + value=@env[@envIndex][1] + end + end + while @envIndex+1<=@env.length && @env[@envIndex][0]==frame.to_i + @envIndex+=1 + end + return value + end +end + + + +# internal class +class WaveForm# :nodoc: + SAMPLEFREQ=11025 + + def initialize(proc,freq,duration,timbre=0.5) + @duration=duration # in ms + @volumeEnvelope=SoundEnvelope.new + @freqEnvelope=SoundEnvelope.new + @timbreEnvelope=SoundEnvelope.new + @proc=proc + @freq=freq + @timbre=timbre + if @freq.is_a?(Array) || @freq.is_a?(SoundEnvelope) + @freqEnvelope.set(@freq) + @freq=@freq.length>0 ? @freq[0][1] : 800 + end + if @timbre.is_a?(Array) || @timbre.is_a?(SoundEnvelope) + @timbreEnvelope.set(@timbre) + @timbre=@timbre.length>0 ? @timbre[0][1] : 0.5 + end + end + + def setFrequencyEnvelope(env) + if env.is_a?(Numeric) + @freqEnvelope.clear + @freqEnvelope.addValueChange(env,@duration) + else + @freqEnvelope.set(env) + end + end + + def setVolumeEnvelope(env) + if env.is_a?(Numeric) + @volumeEnvelope.clear + @volumeEnvelope.addValueChange(env,@duration) + else + @volumeEnvelope.set(env) + end + end + + def lcm(x,y) + return y if x==0; return x if y==0 + return x if x==y + if x>y; incr=x + while x%y!=0; x+=incr; end + return x + else; incr=y + while y%x!=0; y+=incr; end + return y + end + end + + def start + @i=0 + @volumeIterator=SoundEnvelopeIterator.new(@volumeEnvelope) + @freqIterator=SoundEnvelopeIterator.new(@freqEnvelope) + @timbreIterator=SoundEnvelopeIterator.new(@timbreEnvelope) + @sampleCount=@duration*SAMPLEFREQ/1000 + @samples=" "*@sampleCount + @exactSamplesPerPass=@freq==0 ? 1.0 : [SAMPLEFREQ.to_f/@freq,1].max + @step=1.0/@exactSamplesPerPass + @exactCounter=0 + end + + def self.frac(x) + return x-x.floor + end + + def nextSample + vol=100 + fframe=@i*1000.0/SAMPLEFREQ + if @volumeEnvelope.length>0 # Update volume + vol=@volumeIterator.getValue(fframe) + end + if @proc + updateBuffer=false + if @freqEnvelope.length>0 # Update frequency + freq=@freqIterator.getValue(fframe) + if freq!=@freq # update sample buffer + @freq=freq + @exactSamplesPerPass=@freq==0 ? 1.0 : [SAMPLEFREQ.to_f/@freq,1].max + @step=1.0/@exactSamplesPerPass + end + end + if @timbreEnvelope.length>0 # Update timbre + @timbre=@timbreIterator.getValue(fframe) + end + if @freq==0 || vol==0 + @samples[@i]=0x80 + else + sample=@proc.call(@exactCounter,@timbre) + @samples[@i]=0x80+(vol*sample).round + end + else # noise + v=vol.abs.to_i*2 + @samples[@i]=0x80+(rand(v).to_i-(v/2)) + end + @i+=1 + @exactCounter+=@step + while @exactCounter>1.0; @exactCounter-=1.0; end + end + + def mixSamples(other) + for i in 0...other.sampleCount + newSamp=((@samples[i]-0x80)+(other.samples[i]-0x80))/2+0x80 + @samples[i]=newSamp + end + end + + def play(volume=100, async=false) + pbPlaySoundData(@samples,volume,async,SAMPLEFREQ) + end + + def generateSound + start + while @i<@sampleCount + nextSample + end + end + + def toWaveData + return WaveData.new(SAMPLEFREQ,@samples) + end + + def save(filename) + pbSaveSoundData(@samples,SAMPLEFREQ,filename) + end + + attr_accessor :samples,:sampleCount +end + + + +module Audio + module Note + REST = 0 + GbelowC = 196 + A = 220 + Asharp = 233 + B = 247 + C = 262 + Csharp = 277 + D = 294 + Dsharp = 311 + E = 330 + F = 349 + Fsharp = 370 + G = 392 + Gsharp = 415 + end + + module NoteLength + WHOLE = 1600 + HALF = WHOLE/2 + QUARTER = HALF/2 + EIGHTH = QUARTER/2 + SIXTEENTH = EIGHTH/2 + end + + def self.noise(durationInMs=200, volume=100, async=false) + return if durationInMs<=0 + waveForm=WaveForm.new(nil,0,durationInMs) + if volume.is_a?(Array) || volume.is_a?(SoundEnvelope) + waveForm.setVolumeEnvelope(volume) + volume=100 + end + waveForm.generateSound + waveForm.play(volume,async) + end + + # internal method + def self.envelopeDuration(env) + return 0 if !env + if env.is_a?(SoundEnvelope) || env.is_a?(Array) + return SoundEnvelope.new(env).duration + end + return 0 + end + + def self.oscillateDouble(durationInMs, freq1, freq2, volume1, volume2, + timbre1, timbre2, proc1, proc2, async=false) + return if durationInMs<0 + freq1Zero=(freq1.is_a?(Numeric) && freq1<=0) || + (freq1.is_a?(Array) && freq1.length==0) + freq2Zero=(freq2.is_a?(Numeric) && freq2<=0) || + (freq2.is_a?(Array) && freq2.length==0) + if freq1Zero && freq2Zero + Thread.sleep(durationInMs/1000.0) if !async + return + end + if durationInMs==0 || durationInMs==nil + durationInMs=[ + envelopeDuration(freq1), + envelopeDuration(freq2), + envelopeDuration(volume1), + envelopeDuration(volume2), + envelopeDuration(timbre1), + envelopeDuration(timbre2) + ].max + return if durationInMs<=0 + end + waveForm1=WaveForm.new(proc1,freq1,durationInMs,timbre1) + waveForm2=WaveForm.new(proc2,freq2,durationInMs,timbre2) + waveForm1.setVolumeEnvelope(volume1) + waveForm2.setVolumeEnvelope(volume2) + waveForm1.generateSound + waveForm2.generateSound + waveForm1.mixSamples(waveForm2) + waveForm1.play(100,async) + end + + def self.oscillate(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false, &block) + return if durationInMs<0 + if (freq.is_a?(Numeric) && freq<=0) || (freq.is_a?(Array) && freq.length==0) + Thread.sleep(durationInMs/1000.0) if !async + return + end + if durationInMs==0 || durationInMs==nil + durationInMs=[ + envelopeDuration(freq), + envelopeDuration(volume), + envelopeDuration(timbre)].max + return if durationInMs<=0 + end + waveForm=WaveForm.new(block,freq,durationInMs,timbre) + if volume.is_a?(Array) || volume.is_a?(SoundEnvelope) + waveForm.setVolumeEnvelope(volume) + volume=100 + end + waveForm.generateSound + waveForm.play(volume,async) + end + + def self.frac(x) + return x-x.floor + end + + TWOPI = Math::PI*2 + @@sineProc2 = proc { |z,timbre| + x = (z1 + for i in 0...tone.length + dtmf(tone[i,1],durationInMs,volume,false) + end + end + t1=0 + t1=1209 if "14ghi7pqrs*".include?(tone) + t1=1336 if "2abc5jkl8tuv0".include?(tone) + t1=1477 if "3def6mno9wxyz#".include?(tone) + t1=1633 if "ABCD".include?(tone) + t2=0 + t2=697 if "12abc3defA".include?(tone) + t2=770 if "4ghi5jkl6mnoB".include?(tone) + t2=852 if "7pqrs8tuv9wxyzC".include?(tone) + t2=941 if "*0#D".include?(tone) + return if t1==0 || t2==0 + f1=Math::PI*2.0*t1/WaveForm::SAMPLEFREQ + f2=Math::PI*2.0*t2/WaveForm::SAMPLEFREQ + doubleSine(durationInMs,t1,t2,volume,volume,0.5,0.5,async) + end + + def self.beep(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false) + square(durationInMs,freq,volume,timbre,async) + end + + @@triangleProc2 = proc { |z,timbre| + next (z>4 + next if t==0 || t==15 + freqs=[44100,22050,11025,48000] + bitrates=[32,40,48,56,64,80,96,112,128,160,192,224,256,320] + bitrate=bitrates[t] + t=(rstr[1]>>2)&3 + freq=freqs[t] + t=(rstr[1]>>1)&1 + filesize=FileTest.size(filename) + frameLength=((144000*bitrate)/freq)+t + numFrames=filesize/(frameLength+4) + time=(numFrames*1152.0/freq) + break + end + end + } + return time +end + +# Creates wave data from the given WAV file path +def getWaveData(filename) + time=-1 + fgetdw=proc { |file| + (file.eof? ? 0 : (file.read(4).unpack("V")[0] || 0)) + } + fgetw=proc { |file| + (file.eof? ? 0 : (file.read(2).unpack("v")[0] || 0)) + } + return 1 if !safeExists?(filename) # Not found + File.open(filename,"rb") { |file| + file.pos=0 + fdw=fgetdw.call(file) + if fdw==0x46464952 # "RIFF" + filesize=fgetdw.call(file) + wave=fgetdw.call(file) + if wave!=0x45564157 # "WAVE" + return 2 + end + fmt=fgetdw.call(file) + if fmt!=0x20746d66 # "fmt " + return 2 + end + fmtsize=fgetdw.call(file) + format=fgetw.call(file) + if format!=1 + return 3 # unsupported + end + channels=fgetw.call(file) # channels (1 or 2) + if channels!=1 + return 3 # unsupported + end + rate=fgetdw.call(file) # samples per second + bytessec=fgetdw.call(file) # avg bytes per second + if bytessec==0 + return 2 + end + bytessample=fgetw.call(file) # bytes per sample + bitssample=fgetw.call(file) # bits per sample (8, 16, etc.) + if bitssample!=8 && bitssample!=16 + return 3 # unsupported + end + data=fgetdw.call(file) + if data!=0x61746164 # "data" + return 2 + end + datasize=fgetdw.call(file) + data=file.read(datasize) + samples=nil + if bitssample==8 + samples=data.unpack("C*") + start=0 + for i in 0...samples.length + s=samples[i] + if s<0x70 || s>=0x90 + start=i + break + end + end + finish=start + i=samples.length-1 + while i>=start + s=samples[i] + if s<0x70 || s>=0x90 + finish=i+1 + break + end + i-=1 + end + if finish==start + return 4 # Nothing was recorded + end + start=0 + finish=samples.length + wave=WaveData.new(rate,samples[start,finish-start]) + return wave + elsif bitssample==16 + samples=data.unpack("v*") + start=0 + for i in 0...samples.length + s=samples[i] + if s>0x1000 && s<0xF000 + start=i + break + end + end + finish=start + i=samples.length-1 + while i>=start + s=samples[i] + if s<0x1000 && s<0xF000 + finish=i+1 + break + end + i-=1 + end + if finish==start + return 4 # Nothing was recorded + end + start=0 + # Convert to 8-bit samples + for i in start...finish + samples[i]=((samples[i]-0x8000)>>8)&0xFF + end + finish=samples.length + return WaveData.new(rate,samples[start,finish-start]) + end + end + } + return 2 +end + +############################### + +begin + MciSendString = Win32API.new('winmm','mciSendString','%w(p,p,l,l)','l') + MciErrorString = Win32API.new('winmm','mciGetErrorString','%w(l,p,l)','l') +rescue + MciSendString = nil + MciErrorString = nil +end + +# Starts recording. Returns 0 if successful. +def beginRecord + return 256+72 if !MciSendString + MciSendString.call("open new type waveaudio alias RECORDER buffer 4",0,0,0) + MciSendString.call("set RECORDER channels 1",0,0,0) + retval=MciSendString.call("record RECORDER",0,0,0) + if retval!=0 + MciSendString.call("close RECORDER",0,0,0) + end + return retval +end + + +# Gets a single sample from the microphone. +# The beginRecord or beginRecordUI function must have been called beforehand. +def getRecorderSample + return 0x8000 if !MciSendString + buffer="\0"*256 + ret=0 + MciSendString.call("stop RECORDER",0,0,0) + MciSendString.call("status RECORDER bitspersample",buffer,256,0) + bitspersample=buffer.to_i + MciSendString.call("status RECORDER level",buffer,256,0) + MciSendString.call("record RECORDER",0,0,0) + if bitspersample==8 + ret=buffer.to_i<<8 # max 128 + else + ret=buffer.to_i # max 0x8000 + end + return ret +end + +def stopRecord() + return if !MciSendString + MciSendString.call("stop RECORDER",0,0,0) + MciSendString.call("close RECORDER",0,0,0) +end + +def endRecord(file) + return if !MciSendString + MciSendString.call("stop RECORDER",0,0,0) + if file && file!="" + MciSendString.call("save RECORDER #{file}",0,0,0) + end + MciSendString.call("close RECORDER",0,0,0) +end + +#Audio.sine(140,SoundEnvelope.initial(6400).change(140,11400),50) \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/001_FileTests.rb b/Data/Scripts/008_Objects and windows/001_FileTests.rb new file mode 100644 index 000000000..864e2d34c --- /dev/null +++ b/Data/Scripts/008_Objects and windows/001_FileTests.rb @@ -0,0 +1,657 @@ +#=============================================================================== +# Checking for files and directories +#=============================================================================== +# Works around a problem with FileTest.directory if directory contains accent marks +def safeIsDirectory?(f) + ret = false + Dir.chdir(f) { ret = true } rescue nil + return ret +end + +# Works around a problem with FileTest.exist if path contains accent marks +def safeExists?(f) + return FileTest.exist?(f) if f[/\A[\x20-\x7E]*\z/] + ret = false + begin + File.open(f,"rb") { ret = true } + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES + ret = false + end + return ret +end + +# Similar to "Dir.glob", but designed to work around a problem with accessing +# files if a path contains accent marks. +# "dir" is the directory path, "wildcard" is the filename pattern to match. +def safeGlob(dir,wildcard) + ret = [] + afterChdir = false + begin + Dir.chdir(dir) { + afterChdir = true + Dir.glob(wildcard) { |f| ret.push(dir+"/"+f) } + } + rescue Errno::ENOENT + raise if afterChdir + end + if block_given? + ret.each { |f| yield(f) } + end + return (block_given?) ? nil : ret +end + +# Finds the real path for an image file. This includes paths in encrypted +# archives. Returns nil if the path can't be found. +def pbResolveBitmap(x) + return nil if !x + noext = x.gsub(/\.(bmp|png|gif|jpg|jpeg)$/,"") + filename = nil +# RTP.eachPathFor(x) { |path| +# filename = pbTryString(path) if !filename +# filename = pbTryString(path+".gif") if !filename +# } + RTP.eachPathFor(noext) { |path| + filename = pbTryString(path+".png") if !filename + filename = pbTryString(path+".gif") if !filename +# filename = pbTryString(path+".jpg") if !filename +# filename = pbTryString(path+".jpeg") if !filename +# filename = pbTryString(path+".bmp") if !filename + } + return filename +end + +# Finds the real path for an image file. This includes paths in encrypted +# archives. Returns _x_ if the path can't be found. +def pbBitmapName(x) + ret = pbResolveBitmap(x) + return (ret) ? ret : x +end + +def getUnicodeString(addr) + return "" if addr==0 + rtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') + ret = "" + data = "xx" + index = (addr.is_a?(String)) ? 0 : addr + loop do + if addr.is_a?(String) + data = addr[index,2] + else + rtlMoveMemory_pi.call(data, index, 2) + end + codepoint = data.unpack("v")[0] + break if codepoint==0 + index += 2 + if codepoint<=0x7F + ret += codepoint.chr + elsif codepoint<=0x7FF + ret += (0xC0|((codepoint>>6)&0x1F)).chr + ret += (0x80|(codepoint &0x3F)).chr + elsif codepoint<=0xFFFF + ret += (0xE0|((codepoint>>12)&0x0F)).chr + ret += (0x80|((codepoint>>6)&0x3F)).chr + ret += (0x80|(codepoint &0x3F)).chr + elsif codepoint<=0x10FFFF + ret += (0xF0|((codepoint>>18)&0x07)).chr + ret += (0x80|((codepoint>>12)&0x3F)).chr + ret += (0x80|((codepoint>>6)&0x3F)).chr + ret += (0x80|(codepoint &0x3F)).chr + end + end + return ret +end + +def getUnicodeStringFromAnsi(addr) + return "" if addr==0 + rtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') + ret = "" + data = "x" + index = (addr.is_a?(String)) ? 0 : addr + loop do + if addr.is_a?(String) + data = addr[index,1] + else + rtlMoveMemory_pi.call(data, index, 1) + end + index += 1 + codepoint = data.unpack("C")[0] + break if codepoint==0 || !codepoint + break if codepoint==0 + if codepoint<=0x7F + ret += codepoint.chr + else + ret += (0xC0|((codepoint>>6)&0x1F)).chr + ret += (0x80|(codepoint &0x3F)).chr + end + end + return ret +end + +def getKnownFolder(guid) + packedGuid = guid.pack("VvvC*") + shGetKnownFolderPath = Win32API.new("shell32.dll","SHGetKnownFolderPath","pllp","i") rescue nil + coTaskMemFree = Win32API.new("ole32.dll","CoTaskMemFree","i","") rescue nil + return "" if !(shGetKnownFolderPath && coTaskMemFree) + path = "\0"*4 + ret = shGetKnownFolderPath.call(packedGuid,0,0,path) + path = path.unpack("V")[0] + ret = getUnicodeString(path) + coTaskMemFree.call(path) + return ret +end + + + +module RTP + @rtpPaths = nil + + def self.exists?(filename,extensions=[]) + return false if !filename || filename=="" + eachPathFor(filename) { |path| + return true if safeExists?(path) + for ext in extensions + return true if safeExists?(path+ext) + end + } + return false + end + + def self.getImagePath(filename) + return self.getPath(filename,["",".png",".gif"]) # ".jpg",".bmp",".jpeg" + end + + def self.getAudioPath(filename) + return self.getPath(filename,["",".mp3",".wav",".wma",".mid",".ogg",".midi"]) + end + + def self.getPath(filename,extensions=[]) + return filename if !filename || filename=="" + eachPathFor(filename) { |path| + return path if safeExists?(path) + for ext in extensions + file = path+ext + return file if safeExists?(file) + end + } + return filename + end + + # Gets the absolute RGSS paths for the given file name + def self.eachPathFor(filename) + return if !filename + if filename[/^[A-Za-z]\:[\/\\]/] || filename[/^[\/\\]/] + # filename is already absolute + yield filename + else + # relative path + RTP.eachPath { |path| + if path=="./" + yield filename + else + yield path+filename + end + } + end + end + + # Gets all RGSS search paths + def self.eachPath + # XXX: Use "." instead of Dir.pwd because of problems retrieving files if + # the current directory contains an accent mark + yield ".".gsub(/[\/\\]/,"/").gsub(/[\/\\]$/,"")+"/" + if !@rtpPaths + tmp = Sprite.new + isRgss2 = tmp.respond_to?("wave_amp") + tmp.dispose + @rtpPaths = [] + if isRgss2 + rtp = getGameIniValue("Game","RTP") + if rtp!="" + rtp = MiniRegistry.get(MiniRegistry::HKEY_LOCAL_MACHINE, + "SOFTWARE\\Enterbrain\\RGSS2\\RTP",rtp,nil) + if rtp && safeIsDirectory?(rtp) + @rtpPaths.push(rtp.sub(/[\/\\]$/,"")+"/") + end + end + else + %w( RTP1 RTP2 RTP3 ).each { |v| + rtp = getGameIniValue("Game",v) + if rtp!="" + rtp = MiniRegistry.get(MiniRegistry::HKEY_LOCAL_MACHINE, + "SOFTWARE\\Enterbrain\\RGSS\\RTP",rtp,nil) + if rtp && safeIsDirectory?(rtp) + @rtpPaths.push(rtp.sub(/[\/\\]$/,"")+"/") + end + end + } + end + end + @rtpPaths.each { |x| yield x } + end + + private + + @@folder = nil + + def self.getGameIniValue(section,key) + val = "\0"*256 + gps = Win32API.new('kernel32', 'GetPrivateProfileString',%w(p p p p l p), 'l') + gps.call(section, key, "", val, 256, ".\\Game.ini") + val.delete!("\0") + return val + end + + def self.isDirWritable(dir) + return false if !dir || dir=="" + loop do + name = dir.gsub(/[\/\\]$/,"")+"/writetest" + for i in 0...12 + name += sprintf("%02X",rand(256)) + end + name += ".tmp" + if !safeExists?(name) + retval = false + begin + File.open(name,"wb") { retval = true } + rescue Errno::EINVAL, Errno::EACCES, Errno::ENOENT + ensure + File.delete(name) rescue nil + end + return retval + end + end + end + + def self.ensureGameDir(dir) + title = RTP.getGameIniValue("Game","Title") + title = "RGSS Game" if title=="" + title = title.gsub(/[^\w ]/,"_") + newdir = dir.gsub(/[\/\\]$/,"")+"/" + # Convert to UTF-8 because of ANSI function + newdir += getUnicodeStringFromAnsi(title) + Dir.mkdir(newdir) rescue nil + ret = safeIsDirectory?(newdir) ? newdir : dir + return ret + end + + def self.getSaveFileName(fileName) + return getSaveFolder().gsub(/[\/\\]$/,"")+"/"+fileName + end + + def self.getSaveFolder + if !@@folder + # XXX: Use "." instead of Dir.pwd because of problems retrieving files if + # the current directory contains an accent mark + pwd = "." + # Get the known folder path for saved games + savedGames = getKnownFolder([ + 0x4c5c32ff,0xbb9d,0x43b0,0xb5,0xb4,0x2d,0x72,0xe5,0x4e,0xaa,0xa4]) + if savedGames && savedGames!="" && isDirWritable(savedGames) + pwd = ensureGameDir(savedGames) + end + if isDirWritable(pwd) + @@folder = pwd + else + appdata = ENV["LOCALAPPDATA"] + if isDirWritable(appdata) + appdata = ensureGameDir(appdata) + else + appdata = ENV["APPDATA"] + if isDirWritable(appdata) + appdata = ensureGameDir(appdata) + elsif isDirWritable(pwd) + appdata = pwd + else + appdata = "." + end + end + @@folder = appdata + end + end + return @@folder + end +end + + + +module FileTest + Image_ext = ['.bmp', '.png', '.jpg', '.jpeg', '.gif'] + Audio_ext = ['.mp3', '.mid', '.midi', '.ogg', '.wav', '.wma'] + + def self.audio_exist?(filename) + return RTP.exists?(filename,Audio_ext) + end + + def self.image_exist?(filename) + return RTP.exists?(filename,Image_ext) + end +end + + + +# Used to determine whether a data file exists (rather than a graphics or +# audio file). Doesn't check RTP, but does check encrypted archives. +def pbRgssExists?(filename) + filename = canonicalize(filename) + if safeExists?("./Game.rgssad") || safeExists?("./Game.rgss2a") + return pbGetFileChar(filename)!=nil + else + return safeExists?(filename) + end +end + +# Opens an IO, even if the file is in an encrypted archive. +# Doesn't check RTP for the file. +def pbRgssOpen(file,mode=nil) + #File.open("debug.txt","ab") { |fw| fw.write([file,mode,Time.now.to_f].inspect+"\r\n") } + if !safeExists?("./Game.rgssad") && !safeExists?("./Game.rgss2a") + if block_given? + File.open(file,mode) { |f| yield f } + return nil + else + return File.open(file,mode) + end + end + file = canonicalize(file) + Marshal.neverload = true + begin + str = load_data(file) + ensure + Marshal.neverload = false + end + if block_given? + StringInput.open(str) { |f| yield f } + return nil + else + return StringInput.open(str) + end +end + +# Gets at least the first byte of a file. Doesn't check RTP, but does check +# encrypted archives. +def pbGetFileChar(file) + file = canonicalize(file) + if !safeExists?("./Game.rgssad") && !safeExists?("./Game.rgss2a") + return nil if !safeExists?(file) + begin + File.open(file,"rb") { |f| return f.read(1) } # read one byte + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES + return nil + end + end + Marshal.neverload = true + str = nil + begin + str = load_data(file) + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, RGSSError + str = nil + ensure + Marshal.neverload = false + end + return str +end + +def pbTryString(x) + ret = pbGetFileChar(x) + return (ret!=nil && ret!="") ? x : nil +end + +# Gets the contents of a file. Doesn't check RTP, but does check +# encrypted archives. +def pbGetFileString(file) + file = canonicalize(file) + if !(safeExists?("./Game.rgssad") || safeExists?("./Game.rgss2a")) + return nil if !safeExists?(file) + begin + File.open(file,"rb") { |f| return f.read } # read all data + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES + return nil + end + end + Marshal.neverload = true + str = nil + begin + str = load_data(file) + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, RGSSError + str = nil + ensure + Marshal.neverload = false + end + return str +end + + + +#=============================================================================== +# +#=============================================================================== +module MiniRegistry + HKEY_CLASSES_ROOT = 0x80000000 + HKEY_CURRENT_USER = 0x80000001 + HKEY_LOCAL_MACHINE = 0x80000002 + HKEY_USERS = 0x80000003 + FormatMessageA = Win32API.new("kernel32","FormatMessageA","LPLLPLP","L") + RegOpenKeyExA = Win32API.new("advapi32","RegOpenKeyExA","LPLLP","L") + RegCloseKey = Win32API.new("advapi32","RegCloseKey","L","L") + RegQueryValueExA = Win32API.new("advapi32","RegQueryValueExA","LPLPPP","L") + + def self.open(hkey,subkey,bit64=false) + key = 0.chr*4 + flag = bit64 ? 0x20119 : 0x20019 + rg = RegOpenKeyExA.call(hkey, subkey, 0, flag, key) + return nil if rg!=0 + key = key.unpack("V")[0] + if block_given? + begin + yield(key) + ensure + check(RegCloseKey.call(key)) + end + else + return key + end + end + + def self.close(hkey); check(RegCloseKey.call(hkey)) if hkey; end + + def self.get(hkey,subkey,name,defaultValue=nil,bit64=false) + self.open(hkey,subkey,bit64) { |key| + return self.read(key,name) rescue defaultValue + } + return defaultValue + end + + def self.read(hkey,name) + hkey = 0 if !hkey + type = 0.chr*4 + size = 0.chr*4 + check(RegQueryValueExA.call(hkey,name,0,type,0,size)) + data = " "*size.unpack("V")[0] + check(RegQueryValueExA.call(hkey,name,0,type,data,size)) + type = type.unpack("V")[0] + data = data[0,size.unpack("V")[0]] + case type + when 1; return data.chop # REG_SZ + when 2; return data.gsub(/%([^%]+)%/) { ENV[$1] || $& } # REG_EXPAND_SZ + when 3; return data # REG_BINARY + when 4; return data.unpack("V")[0] # REG_DWORD + when 5; return data.unpack("V")[0] # REG_DWORD_BIG_ENDIAN + when 11; qw = data.unpack("VV"); return (data[1]<<32|data[0]) # REG_QWORD + else; raise "Type #{type} not supported." + end + end + + private + + def self.check(code) + if code!=0 + msg = "\0"*1024 + len = FormatMessageA.call(0x1200, 0, code, 0, msg, 1024, 0) + raise msg[0, len].tr("\r", '').chomp + end + end +end + + + +class StringInput + include Enumerable + + class << self + def new( str ) + if block_given? + begin + f = super + yield f + ensure + f.close if f + end + else + super + end + end + alias open new + end + + def initialize( str ) + @string = str + @pos = 0 + @closed = false + @lineno = 0 + end + + attr_reader :lineno,:string + + def inspect + return "#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@string[0,30].inspect}>" + end + + def close + raise IOError, 'closed stream' if @closed + @pos = nil + @closed = true + end + + def closed?; @closed; end + + def pos + raise IOError, 'closed stream' if @closed + [@pos, @string.size].min + end + + alias tell pos + + def rewind; seek(0); end + + def pos=(value); seek(value); end + + def seek(offset, whence=IO::SEEK_SET) + raise IOError, 'closed stream' if @closed + case whence + when IO::SEEK_SET; @pos = offset + when IO::SEEK_CUR; @pos += offset + when IO::SEEK_END; @pos = @string.size - offset + else + raise ArgumentError, "unknown seek flag: #{whence}" + end + @pos = 0 if @pos < 0 + @pos = [@pos, @string.size + 1].min + offset + end + + def eof? + raise IOError, 'closed stream' if @closed + @pos > @string.size + end + + def each( &block ) + raise IOError, 'closed stream' if @closed + begin + @string.each(&block) + ensure + @pos = 0 + end + end + + def gets + raise IOError, 'closed stream' if @closed + if idx = @string.index(?\n, @pos) + idx += 1 # "\n".size + line = @string[ @pos ... idx ] + @pos = idx + @pos += 1 if @pos == @string.size + else + line = @string[ @pos .. -1 ] + @pos = @string.size + 1 + end + @lineno += 1 + line + end + + def getc + raise IOError, 'closed stream' if @closed + ch = @string[@pos] + @pos += 1 + @pos += 1 if @pos == @string.size + ch + end + + def read( len = nil ) + raise IOError, 'closed stream' if @closed + if !len + return nil if eof? + rest = @string[@pos ... @string.size] + @pos = @string.size + 1 + return rest + end + str = @string[@pos, len] + @pos += len + @pos += 1 if @pos == @string.size + str + end + + def read_all; read(); end + + alias sysread read +end + + + +module ::Marshal + class << self + if !@oldloadAliased + alias oldload load + @oldloadAliased = true + end + + @@neverload = false + + def neverload + return @@neverload + end + + def neverload=(value) + @@neverload = value + end + + def load(port,*arg) + if @@neverload + if port.is_a?(IO) + return port.read + end + return port + end + oldpos = port.pos if port.is_a?(IO) + begin + oldload(port,*arg) + rescue + p [$!.class,$!.message,$!.backtrace] + if port.is_a?(IO) + port.pos = oldpos + return port.read + end + return port + end + end + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/002_BitmapCache.rb b/Data/Scripts/008_Objects and windows/002_BitmapCache.rb new file mode 100644 index 000000000..e44135d17 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/002_BitmapCache.rb @@ -0,0 +1,497 @@ +class Hangup < Exception; end + + + +def strsplit(str,re) + ret=[] + tstr=str + while re=~tstr + ret[ret.length]=$~.pre_match + tstr=$~.post_match + end + ret[ret.length]=tstr if ret.length + return ret +end + +def canonicalize(c) + csplit = strsplit(c,/[\/\\]/) + pos = -1 + ret = [] + retstr = "" + for x in csplit + if x=="." + elsif x==".." + if pos>=0 + ret.delete_at(pos) + pos -= 1 + end + else + ret.push(x) + pos += 1 + end + end + for i in 0...ret.length + retstr += "/" if i>0 + retstr += ret[i] + end + return retstr +end + + + +##################################################################### +class WeakRef + @@id_map = {} + @@id_rev_map = {} + @@final = lambda { |id| + __old_status = Thread.critical + Thread.critical = true + begin + rids = @@id_map[id] + if rids + for rid in rids + @@id_rev_map.delete(rid) + end + @@id_map.delete(id) + end + rid = @@id_rev_map[id] + if rid + @@id_rev_map.delete(id) + @@id_map[rid].delete(id) + @@id_map.delete(rid) if @@id_map[rid].empty? + end + ensure + Thread.critical = __old_status + end + } + + # Create a new WeakRef from +orig+. + def initialize(orig) + __setobj__(orig) + end + + def __getobj__ + unless @@id_rev_map[self.__id__] == @__id + return nil + end + begin + ObjectSpace._id2ref(@__id) + rescue RangeError + return nil + end + end + + def __setobj__(obj) + @__id = obj.__id__ + __old_status = Thread.critical + begin + Thread.critical = true + unless @@id_rev_map.key?(self) + ObjectSpace.define_finalizer obj, @@final + ObjectSpace.define_finalizer self, @@final + end + @@id_map[@__id] = [] unless @@id_map[@__id] + ensure + Thread.critical = __old_status + end + @@id_map[@__id].push self.__id__ + @@id_rev_map[self.__id__] = @__id + end + + # Returns true if the referenced object still exists, and false if it has + # been garbage collected. + def weakref_alive? + @@id_rev_map[self.__id__] == @__id + end +end + + + +class WeakHashtable + include Enumerable + + def initialize + @hash={} + end + + def clear + @hash.clear + end + + def delete(value) + @hash.delete(value) + end + + def include?(value) + @hash.include?(value) + end + + def each + @hash.each { |i| yield i } + end + + def keys + @hash.keys + end + + def values + @hash.values + end + + def [](key) + o=@hash[key] + return o if !o + if o.weakref_alive? + o=o.__getobj__ + else + @hash.delete(key) + o=nil + end + return o + end + + def []=(key,o) + if o!=nil + o=WeakRef.new(o) + end + @hash[key]=o + end +end + + + +# Cache from RPG Maker VX library +module Cache + def self.system(x,hue=0) + BitmapCache.load_bitmap("Graphics/System/"+x,hue, true) + end + + def self.character(x,hue=0) + BitmapCache.load_bitmap("Graphics/Characters/"+x,hue, true) + end + + def self.picture(x,hue=0) + BitmapCache.load_bitmap("Graphics/Pictures/"+x,hue, true) + end + + def self.animation(x,hue=0) + BitmapCache.load_bitmap("Graphics/Animations/"+x,hue, true) + end + + def self.battler(x,hue=0) + BitmapCache.load_bitmap("Graphics/Battlers/"+x,hue, true) + end + + def self.face(x,hue=0) + BitmapCache.load_bitmap("Graphics/Faces/"+x,hue, true) + end + + def self.parallax(x,hue=0) + BitmapCache.load_bitmap("Graphics/Parallaxes/"+x,hue, true) + end + + def self.clear + BitmapCache.clear() + end + + def self.load_bitmap(dir,name,hue=0) + BitmapCache.load_bitmap(dir+name,hue, true) + end +end + + + +# RPG::Cache from RPG Maker XP library +module RPG + module Cache + def self.load_bitmap(folder_name, filename, hue = 0) + BitmapCache.load_bitmap(folder_name+filename.to_s,hue, true) + end + + def self.animation(filename, hue) + self.load_bitmap("Graphics/Animations/", filename, hue) + end + + def self.autotile(filename) + self.load_bitmap("Graphics/Autotiles/", filename) + end + + def self.battleback(filename) + self.load_bitmap("Graphics/Battlebacks/", filename) + end + + def self.battler(filename, hue) + self.load_bitmap("Graphics/Battlers/", filename, hue) + end + + def self.character(filename, hue) + self.load_bitmap("Graphics/Characters/", filename, hue) + end + + def self.fog(filename, hue) + self.load_bitmap("Graphics/Fogs/", filename, hue) + end + + def self.gameover(filename) + self.load_bitmap("Graphics/Gameovers/", filename) + end + + def self.icon(filename) + self.load_bitmap("Graphics/Icons/", filename) + end + + def self.panorama(filename, hue) + self.load_bitmap("Graphics/Panoramas/", filename, hue) + end + + def self.picture(filename) + self.load_bitmap("Graphics/Pictures/", filename) + end + + def self.tileset(filename) + self.load_bitmap("Graphics/Tilesets/", filename) + end + + def self.title(filename) + self.load_bitmap("Graphics/Titles/", filename) + end + + def self.windowskin(filename) + self.load_bitmap("Graphics/Windowskins/", filename) + end + + def self.tile(filename, tile_id, hue) + BitmapCache.tile(filename,tile_id,hue) + end + + def self.clear + BitmapCache.clear() + end + end +end + + + +# A safer version of RPG::Cache, this module loads bitmaps that keep an internal +# reference count. Each call to dispose decrements the reference count and the +# bitmap is freed when the reference count reaches 0. +class Thread + def Thread.exclusive + _old = Thread.critical + begin + Thread.critical = true + return yield + ensure + Thread.critical = _old + end + end +end + + + +class BitmapWrapper < Bitmap + @@disposedBitmaps={} + @@keys={} +=begin + @@final = lambda { |id| + Thread.exclusive { + if @@disposedBitmaps[id]!=true + File.open("debug.txt","ab") { |f| + f.write("Bitmap finalized without being disposed: #{@@keys[id]}\r\n") + } + end + @@disposedBitmaps[id]=nil + } + } +=end + attr_reader :refcount + + def dispose + return if self.disposed? + @refcount-=1 + if @refcount==0 + super + #Thread.exclusive { @@disposedBitmaps[__id__]=true } + end + end + + def initialize(*arg) + super + @refcount=1 + #Thread.exclusive { @@keys[__id__]=arg.inspect+caller(1).inspect } + #ObjectSpace.define_finalizer(self,@@final) + end + + def resetRef # internal + @refcount=1 + end + + def copy + bm=self.clone + bm.resetRef + return bm + end + + def addRef + @refcount+=1 + end +end + + + +module BitmapCache + @cache = WeakHashtable.new + + def self.fromCache(i) + return nil if !@cache.include?(i) + obj=@cache[i] + return nil if obj && obj.disposed? + return obj + end + + def self.setKey(key,obj) + @cache[key]=obj + end + + def self.debug + File.open("bitmapcache2.txt","wb") { |f| + for i in @cache.keys + k = fromCache(i) + if !k + f.write("#{i} (nil)\r\n") + elsif k.disposed? + f.write("#{i} (disposed)\r\n") + else + f.write("#{i} (#{k.refcount}, #{k.width}x#{k.height})\r\n") + end + end + } + end + + def self.load_bitmap(path, hue = 0, failsafe = false) + cached = true + path = canonicalize(path) + objPath = fromCache(path) + if !objPath + @cleancounter = ((@cleancounter || 0) + 1)%10 + if @cleancounter == 0 + for i in @cache.keys + @cache.delete(i) if !fromCache(i) + end + end + begin + bm = BitmapWrapper.new(path) + rescue Hangup + begin + bm = BitmapWrapper.new(path) + rescue + raise _INTL("Failed to load the bitmap located at: {1}",path) if !failsafe + bm = BitmapWrapper.new(32,32) + end + rescue + raise _INTL("Failed to load the bitmap located at: {1}",path) if !failsafe + bm = BitmapWrapper.new(32,32) + end + objPath = bm + @cache[path] = objPath + cached=false + end + if hue == 0 + objPath.addRef if cached + return objPath + else + key = [path, hue] + objKey = fromCache(key) + if !objKey + bitmap = objPath.copy + bitmap.hue_change(hue) if hue!=0 + objKey = bitmap + @cache[key] = objKey + else + objKey.addRef + end + return objKey + end + end + + def self.animation(filename, hue) + self.load_bitmap("Graphics/Animations/"+filename, hue) + end + + def self.autotile(filename) + self.load_bitmap("Graphics/Autotiles/"+ filename) + end + + def self.battleback(filename) + self.load_bitmap("Graphics/Battlebacks/"+ filename) + end + + def self.battler(filename, hue) + self.load_bitmap("Graphics/Battlers/"+ filename, hue) + end + + def self.character(filename, hue) + self.load_bitmap("Graphics/Characters/"+ filename, hue) + end + + def self.fog(filename, hue) + self.load_bitmap("Graphics/Fogs/"+ filename, hue) + end + + def self.gameover(filename) + self.load_bitmap("Graphics/Gameovers/"+ filename) + end + + def self.icon(filename) + self.load_bitmap("Graphics/Icons/"+ filename) + end + + def self.panorama(filename, hue) + self.load_bitmap("Graphics/Panoramas/"+ filename, hue) + end + + def self.picture(filename) + self.load_bitmap("Graphics/Pictures/"+ filename) + end + + def self.tileset(filename) + self.load_bitmap("Graphics/Tilesets/"+ filename) + end + + def self.title(filename) + self.load_bitmap("Graphics/Titles/"+ filename) + end + + def self.windowskin(filename) + self.load_bitmap("Graphics/Windowskins/"+ filename) + end + + def self.tileEx(filename, tile_id, hue) + key = [filename, tile_id, hue] + objKey=fromCache(key) + if !objKey + bitmap=BitmapWrapper.new(32, 32) + x = (tile_id - 384) % 8 * 32 + y = (tile_id - 384) / 8 * 32 + rect = Rect.new(x, y, 32, 32) + tileset = yield(filename) + bitmap.blt(0, 0, tileset, rect) + tileset.dispose + bitmap.hue_change(hue) if hue!=0 + objKey=bitmap + @cache[key]=objKey + else + objKey.addRef + end + objKey + end + + def self.tile(filename, tile_id, hue) + return self.tileEx(filename, tile_id,hue) { |f| self.tileset(f) } + end + + def self.clear + @cache = {} + GC.start + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/003_Window.rb b/Data/Scripts/008_Objects and windows/003_Window.rb new file mode 100644 index 000000000..60cc33535 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/003_Window.rb @@ -0,0 +1,608 @@ +class WindowCursorRect < Rect + def initialize(window) + @window=window + @x=0 + @y=0 + @width=0 + @height=0 + end + + attr_reader :x,:y,:width,:height + + def empty + needupdate=@x!=0 || @y!=0 || @width!=0 || @height!=0 + if needupdate + @x=0 + @y=0 + @width=0 + @height=0 + @window.width=@window.width + end + end + + def isEmpty? + return @x==0 && @y==0 && @width==0 && @height==0 + end + + def set(x,y,width,height) + needupdate=@x!=x || @y!=y || @width!=width || @height!=height + if needupdate + @x=x + @y=y + @width=width + @height=height + @window.width=@window.width + end + end + + def height=(value) + @height=value; @window.width=@window.width + end + + def width=(value) + @width=value; @window.width=@window.width + end + + def x=(value) + @x=value; @window.width=@window.width + end + + def y=(value) + @y=value; @window.width=@window.width + end +end + + + +class Window + attr_reader :tone + attr_reader :color + attr_reader :blend_type + attr_reader :contents_blend_type + attr_reader :viewport + attr_reader :contents + attr_reader :ox + attr_reader :oy + attr_reader :x + attr_reader :y + attr_reader :z + attr_reader :width + attr_reader :active + attr_reader :pause + attr_reader :height + attr_reader :opacity + attr_reader :back_opacity + attr_reader :contents_opacity + attr_reader :visible + attr_reader :cursor_rect + attr_reader :openness + attr_reader :stretch + + def windowskin + @_windowskin + end + + def initialize(viewport=nil) + @sprites={} + @spritekeys=[ + "back", + "corner0","side0","scroll0", + "corner1","side1","scroll1", + "corner2","side2","scroll2", + "corner3","side3","scroll3", + "cursor","contents","pause" + ] + @sidebitmaps=[nil,nil,nil,nil] + @cursorbitmap=nil + @bgbitmap=nil + @viewport=viewport + for i in @spritekeys + @sprites[i]=Sprite.new(@viewport) + end + @disposed=false + @tone=Tone.new(0,0,0) + @color=Color.new(0,0,0,0) + @blankcontents=Bitmap.new(1,1) # RGSS2 requires this + @contents=@blankcontents + @_windowskin=nil + @rpgvx=false # Set to true to emulate RPGVX windows + @x=0 + @y=0 + @width=0 + @openness=255 + @height=0 + @ox=0 + @oy=0 + @z=0 + @stretch=true + @visible=true + @active=true + @blend_type=0 + @contents_blend_type=0 + @opacity=255 + @back_opacity=255 + @contents_opacity=255 + @cursor_rect=WindowCursorRect.new(self) + @cursorblink=0 + @cursoropacity=255 + @pause=false + @pauseopacity=255 + @pauseframe=0 + privRefresh(true) + end + + def dispose + if !self.disposed? + for i in @sprites + i[1].dispose if i[1] + @sprites[i[0]]=nil + end + for i in 0...@sidebitmaps.length + @sidebitmaps[i].dispose if @sidebitmaps[i] + @sidebitmaps[i]=nil + end + @blankcontents.dispose + @cursorbitmap.dispose if @cursorbitmap + @backbitmap.dispose if @backbitmap + @sprites.clear + @sidebitmaps.clear + @_windowskin=nil + @_contents=nil + @disposed=true + end + end + + def openness=(value) + @openness=value + @openness=0 if @openness<0 + @openness=255 if @openness>255 + privRefresh + end + + def stretch=(value) + @stretch=value + privRefresh(true) + end + + def visible=(value) + @visible=value + privRefresh + end + + def viewport=(value) + @viewport=value + for i in @spritekeys + @sprites[i].dispose + if @sprites[i].is_a?(Sprite) + @sprites[i]=Sprite.new(@viewport) + else + @sprites[i]=nil + end + end + privRefresh(true) + end + + def z=(value) + @z=value + privRefresh + end + + def disposed? + return @disposed + end + + def contents=(value) + @contents=value + privRefresh + end + + def windowskin=(value) + @_windowskin=value + if value && value.is_a?(Bitmap) && !value.disposed? && value.width==128 + @rpgvx=true + else + @rpgvx=false + end + privRefresh(true) + end + + def ox=(value) + @ox=value + privRefresh + end + + def active=(value) + @active=value + privRefresh(true) + end + + def cursor_rect=(value) + if !value + @cursor_rect.empty + else + @cursor_rect.set(value.x,value.y,value.width,value.height) + end + end + + def oy=(value) + @oy=value + privRefresh + end + + def width=(value) + @width=value + privRefresh(true) + end + + def height=(value) + @height=value + privRefresh(true) + end + + def pause=(value) + @pause=value + @pauseopacity=0 if !value + privRefresh + end + + def x=(value) + @x=value + privRefresh + end + + def y=(value) + @y=value + privRefresh + end + + def opacity=(value) + @opacity=value + @opacity=0 if @opacity<0 + @opacity=255 if @opacity>255 + privRefresh + end + + def back_opacity=(value) + @back_opacity=value + @back_opacity=0 if @back_opacity<0 + @back_opacity=255 if @back_opacity>255 + privRefresh + end + + def contents_opacity=(value) + @contents_opacity=value + @contents_opacity=0 if @contents_opacity<0 + @contents_opacity=255 if @contents_opacity>255 + privRefresh + end + + def tone=(value) + @tone=value + privRefresh + end + + def color=(value) + @color=value + privRefresh + end + + def blend_type=(value) + @blend_type=value + privRefresh + end + + def flash(color,duration) + return if disposed? + for i in @sprites + i[1].flash(color,duration) + end + end + + def update + return if disposed? + mustchange=false + if @active + if @cursorblink==0 + @cursoropacity-=8 + @cursorblink=1 if @cursoropacity<=128 + else + @cursoropacity+=8 + @cursorblink=0 if @cursoropacity>=255 + end + mustchange=true if !@cursor_rect.isEmpty? + else + mustchange=true if @cursoropacity!=128 + @cursoropacity=128 + end + if @pause + @pauseframe=(Graphics.frame_count / 8) % 4 + @pauseopacity=[@pauseopacity+64,255].min + mustchange=true + end + privRefresh if mustchange + for i in @sprites + i[1].update + end + end + + private + + def ensureBitmap(bitmap,dwidth,dheight) + if !bitmap||bitmap.disposed?||bitmap.width 0 + @sprites["scroll1"].visible = @visible && hascontents && @ox > 0 + @sprites["scroll2"].visible = @visible && hascontents && + (@contents.width - @ox) > @width-32 + @sprites["scroll3"].visible = @visible && hascontents && + (@contents.height - @oy) > @height-32 + else + for i in 0...4 + @sprites["corner#{i}"].visible=false + @sprites["side#{i}"].visible=false + @sprites["scroll#{i}"].visible=false + end + @sprites["contents"].visible=@visible && @openness==255 + @sprites["contents"].color=@color + @sprites["contents"].tone=@tone + @sprites["contents"].blend_type=@contents_blend_type + @sprites["contents"].opacity=contopac + @sprites["back"].visible=false + @sprites["pause"].visible=false + @sprites["cursor"].visible=false + end + for i in @sprites + i[1].z=@z + end + if @rpgvx + @sprites["cursor"].z=@z # For Compatibility + @sprites["contents"].z=@z # For Compatibility + @sprites["pause"].z=@z # For Compatibility + else + @sprites["cursor"].z=@z+1 # For Compatibility + @sprites["contents"].z=@z+2 # For Compatibility + @sprites["pause"].z=@z+2 # For Compatibility + end + if @rpgvx + trimX=64 + trimY=0 + backRect=Rect.new(0,0,64,64) + blindsRect=Rect.new(0,64,64,64) + else + trimX=128 + trimY=0 + backRect=Rect.new(0,0,128,128) + blindsRect=nil + end + @sprites["corner0"].src_rect.set(trimX,trimY+0,16,16); + @sprites["corner1"].src_rect.set(trimX+48,trimY+0,16,16); + @sprites["corner2"].src_rect.set(trimX,trimY+48,16,16); + @sprites["corner3"].src_rect.set(trimX+48,trimY+48,16,16); + @sprites["scroll0"].src_rect.set(trimX+24, trimY+16, 16, 8) # up + @sprites["scroll3"].src_rect.set(trimX+24, trimY+40, 16, 8) # down + @sprites["scroll1"].src_rect.set(trimX+16, trimY+24, 8, 16) # left + @sprites["scroll2"].src_rect.set(trimX+40, trimY+24, 8, 16) # right + cursorX=trimX + cursorY=trimY+64 + sideRects=[ + Rect.new(trimX+16,trimY+0,32,16), + Rect.new(trimX,trimY+16,16,32), + Rect.new(trimX+48,trimY+16,16,32), + Rect.new(trimX+16,trimY+48,32,16) + ] + if @width>32 && @height>32 + @sprites["contents"].src_rect.set(@ox,@oy,@width-32,@height-32) + else + @sprites["contents"].src_rect.set(0,0,0,0) + end + pauseRects=[ + trimX+32,trimY+64, + trimX+48,trimY+64, + trimX+32,trimY+80, + trimX+48,trimY+80, + ] + pauseWidth=16 + pauseHeight=16 + @sprites["pause"].src_rect.set( + pauseRects[@pauseframe*2], + pauseRects[@pauseframe*2+1], + pauseWidth,pauseHeight + ) + @sprites["pause"].x=@x+(@width/2)-(pauseWidth/2) + @sprites["pause"].y=@y+@height-16 # 16 refers to skin margin + @sprites["contents"].x=@x+16 + @sprites["contents"].y=@y+16 + @sprites["corner0"].x=@x + @sprites["corner0"].y=@y + @sprites["corner1"].x=@x+@width-16 + @sprites["corner1"].y=@y + @sprites["corner2"].x=@x + @sprites["corner2"].y=@y+@height-16 + @sprites["corner3"].x=@x+@width-16 + @sprites["corner3"].y=@y+@height-16 + @sprites["side0"].x=@x+16 + @sprites["side0"].y=@y + @sprites["side1"].x=@x + @sprites["side1"].y=@y+16 + @sprites["side2"].x=@x+@width-16 + @sprites["side2"].y=@y+16 + @sprites["side3"].x=@x+16 + @sprites["side3"].y=@y+@height-16 + @sprites["scroll0"].x = @x+@width / 2 - 8 + @sprites["scroll0"].y = @y+8 + @sprites["scroll1"].x = @x+8 + @sprites["scroll1"].y = @y+@height / 2 - 8 + @sprites["scroll2"].x = @x+@width - 16 + @sprites["scroll2"].y = @y+@height / 2 - 8 + @sprites["scroll3"].x = @x+@width / 2 - 8 + @sprites["scroll3"].y = @y+@height - 16 + @sprites["back"].x=@x+2 + @sprites["back"].y=@y+2 + @sprites["cursor"].x=@x+16+@cursor_rect.x + @sprites["cursor"].y=@y+16+@cursor_rect.y + if changeBitmap && @_windowskin && !@_windowskin.disposed? + width=@cursor_rect.width + height=@cursor_rect.height + if width > 0 && height > 0 + cursorrects=[ + # sides + Rect.new(cursorX+2, cursorY+0, 28, 2), + Rect.new(cursorX+0, cursorY+2, 2, 28), + Rect.new(cursorX+30, cursorY+2, 2, 28), + Rect.new(cursorX+2, cursorY+30, 28, 2), + # corners + Rect.new(cursorX+0, cursorY+0, 2, 2), + Rect.new(cursorX+30, cursorY+0, 2, 2), + Rect.new(cursorX+0, cursorY+30, 2, 2), + Rect.new(cursorX+30, cursorY+30, 2, 2), + # back + Rect.new(cursorX+2, cursorY+2, 28, 28) + ] + margin=2 + fullmargin=4 + @cursorbitmap = ensureBitmap(@cursorbitmap, width, height) + @cursorbitmap.clear + @sprites["cursor"].bitmap=@cursorbitmap + @sprites["cursor"].src_rect.set(0,0,width,height) + rect = Rect.new(margin,margin, + width - fullmargin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[8]) + @cursorbitmap.blt(0, 0, @_windowskin, cursorrects[4])# top left + @cursorbitmap.blt(width-margin, 0, @_windowskin, cursorrects[5]) # top right + @cursorbitmap.blt(0, height-margin, @_windowskin, cursorrects[6]) # bottom right + @cursorbitmap.blt(width-margin, height-margin, @_windowskin, cursorrects[7]) # bottom left + rect = Rect.new(margin, 0, + width - fullmargin, margin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[0]) + rect = Rect.new(0, margin, + margin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[1]) + rect = Rect.new(width - margin, margin, + margin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[2]) + rect = Rect.new(margin, height-margin, + width - fullmargin, margin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[3]) + else + @sprites["cursor"].visible=false + @sprites["cursor"].src_rect.set(0,0,0,0) + end + for i in 0...4 + dwidth = (i==0 || i==3) ? @width-32 : 16 + dheight = (i==0 || i==3) ? 16 : @height-32 + @sidebitmaps[i]=ensureBitmap(@sidebitmaps[i],dwidth,dheight) + @sprites["side#{i}"].bitmap=@sidebitmaps[i] + @sprites["side#{i}"].src_rect.set(0,0,dwidth,dheight) + @sidebitmaps[i].clear + if sideRects[i].width>0 && sideRects[i].height>0 + @sidebitmaps[i].stretch_blt(@sprites["side#{i}"].src_rect, + @_windowskin,sideRects[i]) + end + end + backwidth=@width-4 + backheight=@height-4 + if backwidth>0 && backheight>0 + @backbitmap=ensureBitmap(@backbitmap,backwidth,backheight) + @sprites["back"].bitmap=@backbitmap + @sprites["back"].src_rect.set(0,0,backwidth,backheight) + @backbitmap.clear + if @stretch + @backbitmap.stretch_blt(@sprites["back"].src_rect,@_windowskin,backRect) + else + tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,backRect) + end + if blindsRect + tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,blindsRect) + end + else + @sprites["back"].visible=false + @sprites["back"].src_rect.set(0,0,0,0) + end + end + if @openness!=255 + opn=@openness/255.0 + for k in @spritekeys + sprite=@sprites[k] + ratio=(@height<=0) ? 0 : (sprite.y-@y)*1.0/@height + sprite.zoom_y=opn + sprite.oy=0 + sprite.y=(@y+(@height/2.0)+(@height*ratio*opn)-(@height/2*opn)).floor + end + else + for k in @spritekeys + sprite=@sprites[k] + sprite.zoom_y=1.0 + end + end + i=0 + # Ensure Z order + for k in @spritekeys + sprite=@sprites[k] + y=sprite.y + sprite.y=i + sprite.oy=(sprite.zoom_y<=0) ? 0 : (i-y)/sprite.zoom_y + end + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/004_SpriteWindow.rb b/Data/Scripts/008_Objects and windows/004_SpriteWindow.rb new file mode 100644 index 000000000..9e5f8f214 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/004_SpriteWindow.rb @@ -0,0 +1,781 @@ +module MessageConfig + FontName = "Power Green" + # in Graphics/Windowskins/ (specify empty string to use the default windowskin) + TextSkinName = "speech hgss 1" + ChoiceSkinName = "choice 1" + WindowOpacity = 255 + TextSpeed = nil # can be positive to wait frames or negative to + # show multiple characters in a single frame + LIGHTTEXTBASE = Color.new(248,248,248) + LIGHTTEXTSHADOW = Color.new(72,80,88) + DARKTEXTBASE = Color.new(80,80,88) + DARKTEXTSHADOW = Color.new(160,160,168) + # 0 = Pause cursor is displayed at end of text + # 1 = Pause cursor is displayed at bottom right + # 2 = Pause cursor is displayed at lower middle side + CURSORMODE = 1 + FontSubstitutes = { + "Power Red and Blue" => "Pokemon RS", + "Power Red and Green" => "Pokemon FireLeaf", + "Power Green" => "Pokemon Emerald", + "Power Green Narrow" => "Pokemon Emerald Narrow", + "Power Green Small" => "Pokemon Emerald Small", + "Power Clear" => "Pokemon DP" + } + @@systemFrame = nil + @@defaultTextSkin = nil + @@systemFont = nil + @@textSpeed = nil + + def self.pbTryFonts(*args) + for a in args + if a && a.is_a?(String) + return a if Font.exist?(a) + a=MessageConfig::FontSubstitutes[a] || a + return a if Font.exist?(a) + elsif a && a.is_a?(Array) + for aa in a + ret=MessageConfig.pbTryFonts(aa) + return ret if ret!="" + end + end + end + return "" + end + + def self.pbDefaultSystemFrame + return "" if !MessageConfig::ChoiceSkinName + return pbResolveBitmap("Graphics/Windowskins/"+MessageConfig::ChoiceSkinName) || "" + end + + def self.pbDefaultSpeechFrame + return "" if !MessageConfig::TextSkinName + return pbResolveBitmap("Graphics/Windowskins/"+MessageConfig::TextSkinName) || "" + end + + def self.pbDefaultSystemFontName + return MessageConfig.pbTryFonts(MessageConfig::FontName,"Arial Narrow","Arial") + end + + def self.pbDefaultTextSpeed + return (TextSpeed) ? TextSpeed : (Graphics.width>400) ? -2 : 1 + end + + def self.pbDefaultWindowskin + skin=load_data("Data/System.rxdata").windowskin_name rescue nil + if skin && skin!="" + skin=pbResolveBitmap("Graphics/Windowskins/"+skin) || "" + end + skin=pbResolveBitmap("Graphics/System/Window") if !skin || skin=="" + skin=pbResolveBitmap("Graphics/Windowskins/001-Blue01") if !skin || skin=="" + return skin || "" + end + + def self.pbGetSystemFrame + if !@@systemFrame + skin=MessageConfig.pbDefaultSystemFrame + skin=MessageConfig.pbDefaultWindowskin if !skin || skin=="" + @@systemFrame=skin || "" + end + return @@systemFrame + end + + def self.pbGetSpeechFrame + if !@@defaultTextSkin + skin=MessageConfig.pbDefaultSpeechFrame + skin=MessageConfig.pbDefaultWindowskin if !skin || skin=="" + @@defaultTextSkin=skin || "" + end + return @@defaultTextSkin + end + + def self.pbGetSystemFontName + @@systemFont=pbDefaultSystemFontName if !@@systemFont + return @@systemFont + end + + def self.pbGetTextSpeed + @@textSpeed=pbDefaultTextSpeed if !@@textSpeed + return @@textSpeed + end + + def self.pbSetSystemFrame(value) + @@systemFrame=pbResolveBitmap(value) || "" + end + + def self.pbSetSpeechFrame(value) + @@defaultTextSkin=pbResolveBitmap(value) || "" + end + + def self.pbSetSystemFontName(value) + @@systemFont=MessageConfig.pbTryFonts(value,"Arial Narrow","Arial") + end + + def self.pbSetTextSpeed(value) + @@textSpeed=value + end +end + + + +#=============================================================================== +# Position a window +#=============================================================================== +def pbBottomRight(window) + window.x=Graphics.width-window.width + window.y=Graphics.height-window.height +end + +def pbBottomLeft(window) + window.x=0 + window.y=Graphics.height-window.height +end + +def pbBottomLeftLines(window,lines,width=nil) + window.x=0 + window.width=width ? width : Graphics.width + window.height=(window.borderY rescue 32)+lines*32 + window.y=Graphics.height-window.height +end + +def pbPositionFaceWindow(facewindow,msgwindow) + return if !facewindow + if msgwindow + if facewindow.height<=msgwindow.height + facewindow.y=msgwindow.y + else + facewindow.y=msgwindow.y+msgwindow.height-facewindow.height + end + facewindow.x=Graphics.width-facewindow.width + msgwindow.x=0 + msgwindow.width=Graphics.width-facewindow.width + else + facewindow.height=Graphics.height if facewindow.height>Graphics.height + facewindow.x=0 + facewindow.y=0 + end +end + +def pbPositionNearMsgWindow(cmdwindow,msgwindow,side) + return if !cmdwindow + if msgwindow + height=[cmdwindow.height,Graphics.height-msgwindow.height].min + if cmdwindow.height!=height + cmdwindow.height=height + end + cmdwindow.y=msgwindow.y-cmdwindow.height + if cmdwindow.y<0 + cmdwindow.y=msgwindow.y+msgwindow.height + if cmdwindow.y+cmdwindow.height>Graphics.height + cmdwindow.y=msgwindow.y-cmdwindow.height + end + end + case side + when :left + cmdwindow.x=msgwindow.x + when :right + cmdwindow.x=msgwindow.x+msgwindow.width-cmdwindow.width + else + cmdwindow.x=msgwindow.x+msgwindow.width-cmdwindow.width + end + else + cmdwindow.height=Graphics.height if cmdwindow.height>Graphics.height + cmdwindow.x=0 + cmdwindow.y=0 + end +end + +# internal function +def pbRepositionMessageWindow(msgwindow, linecount=2) + msgwindow.height=32*linecount+msgwindow.borderY + msgwindow.y=(Graphics.height)-(msgwindow.height) + if $game_system && $game_system.respond_to?("message_position") + case $game_system.message_position + when 0 # up + msgwindow.y=0 + when 1 # middle + msgwindow.y=(Graphics.height/2)-(msgwindow.height/2) + when 2 + msgwindow.y=(Graphics.height)-(msgwindow.height) + end + end + if $game_system && $game_system.respond_to?("message_frame") + if $game_system.message_frame != 0 + msgwindow.opacity = 0 + end + end + if $game_message + case $game_message.background + when 1; msgwindow.opacity=0 # dim + when 2; msgwindow.opacity=0 # transparent + end + end +end + +# internal function +def pbUpdateMsgWindowPos(msgwindow,event,eventChanged=false) + if event + if eventChanged + msgwindow.resizeToFit2(msgwindow.text,Graphics.width*2/3,msgwindow.height) + end + msgwindow.y=event.screen_y-48-msgwindow.height + if msgwindow.y<0 + msgwindow.y=event.screen_y+24 + end + msgwindow.x=event.screen_x-(msgwindow.width/2) + msgwindow.x=0 if msgwindow.x<0 + if msgwindow.x>Graphics.width-msgwindow.width + msgwindow.x=Graphics.width-msgwindow.width + end + else + curwidth=msgwindow.width + if curwidth!=Graphics.width + msgwindow.width=Graphics.width + msgwindow.width=Graphics.width + end + end +end + +#=============================================================================== +# Determine the colour of a background +#=============================================================================== +def isDarkBackground(background,rect=nil) + return true if !background || background.disposed? + rect = background.rect if !rect + return true if rect.width<=0 || rect.height<=0 + xSeg = (rect.width/16) + xLoop = (xSeg==0) ? 1 : 16 + xStart = (xSeg==0) ? rect.x+(rect.width/2) : rect.x+xSeg/2 + ySeg = (rect.height/16) + yLoop = (ySeg==0) ? 1 : 16 + yStart = (ySeg==0) ? rect.y+(rect.height/2) : rect.y+ySeg/2 + count = 0 + y = yStart + r = 0; g = 0; b = 0 + yLoop.times do + x = xStart + xLoop.times do + clr = background.get_pixel(x,y) + if clr.alpha!=0 + r += clr.red + g += clr.green + b += clr.blue + count += 1 + end + x += xSeg + end + y += ySeg + end + return true if count==0 + r /= count + g /= count + b /= count + return (r*0.299+g*0.587+b*0.114)<160 +end + +def isDarkWindowskin(windowskin) + return true if !windowskin || windowskin.disposed? + if windowskin.width==192 && windowskin.height==128 + return isDarkBackground(windowskin,Rect.new(0,0,128,128)) + elsif windowskin.width==128 && windowskin.height==128 + return isDarkBackground(windowskin,Rect.new(0,0,64,64)) + elsif windowskin.width==96 && windowskin.height==48 + return isDarkBackground(windowskin,Rect.new(32,16,16,16)) + else + clr = windowskin.get_pixel(windowskin.width/2, windowskin.height/2) + return (clr.red*0.299+clr.green*0.587+clr.blue*0.114)<160 + end +end + +#=============================================================================== +# Determine which text colours to use based on the darkness of the background +#=============================================================================== +def getSkinColor(windowskin,color,isDarkSkin) + if !windowskin || windowskin.disposed? || + windowskin.width!=128 || windowskin.height!=128 + # Base color, shadow color (these are reversed on dark windowskins) + textcolors = [ + "0070F8","78B8E8", # 1 Blue + "E82010","F8A8B8", # 2 Red + "60B048","B0D090", # 3 Green + "48D8D8","A8E0E0", # 4 Cyan + "D038B8","E8A0E0", # 5 Magenta + "E8D020","F8E888", # 6 Yellow + "A0A0A8","D0D0D8", # 7 Grey + "F0F0F8","C8C8D0", # 8 White + "9040E8","B8A8E0", # 9 Purple + "F89818","F8C898", # 10 Orange + colorToRgb32(MessageConfig::DARKTEXTBASE), + colorToRgb32(MessageConfig::DARKTEXTSHADOW), # 11 Dark default + colorToRgb32(MessageConfig::LIGHTTEXTBASE), + colorToRgb32(MessageConfig::LIGHTTEXTSHADOW) # 12 Light default + ] + if color==0 || color>textcolors.length/2 # No special colour, use default + if isDarkSkin # Dark background, light text + return shadowc3tag(MessageConfig::LIGHTTEXTBASE,MessageConfig::LIGHTTEXTSHADOW) + end + # Light background, dark text + return shadowc3tag(MessageConfig::DARKTEXTBASE,MessageConfig::DARKTEXTSHADOW) + end + # Special colour as listed above + if isDarkSkin && color!=12 # Dark background, light text + return sprintf("",textcolors[2*(color-1)+1],textcolors[2*(color-1)]) + end + # Light background, dark text + return sprintf("",textcolors[2*(color-1)],textcolors[2*(color-1)+1]) + else # VX windowskin + color = 0 if color>=32 + x = 64 + (color % 8) * 8 + y = 96 + (color / 8) * 8 + pixel = windowskin.get_pixel(x, y) + return shadowctagFromColor(pixel) + end +end + +def getDefaultTextColors(windowskin) + if !windowskin || windowskin.disposed? || + windowskin.width!=128 || windowskin.height!=128 + if isDarkWindowskin(windowskin) + return [MessageConfig::LIGHTTEXTBASE,MessageConfig::LIGHTTEXTSHADOW] # White + else + return [MessageConfig::DARKTEXTBASE,MessageConfig::DARKTEXTSHADOW] # Dark gray + end + else # VX windowskin + color = windowskin.get_pixel(64, 96) + shadow = nil + isDark = (color.red+color.green+color.blue)/3 < 128 + if isDark + shadow = Color.new(color.red+64,color.green+64,color.blue+64) + else + shadow = Color.new(color.red-64,color.green-64,color.blue-64) + end + return [color,shadow] + end +end + +#=============================================================================== +# Makes sure a bitmap exists +#=============================================================================== +def pbDoEnsureBitmap(bitmap,dwidth,dheight) + if !bitmap || bitmap.disposed? || bitmap.width0 : false +end + +# pbFadeOutIn(z) { block } +# Fades out the screen before a block is run and fades it back in after the +# block exits. z indicates the z-coordinate of the viewport used for this effect +def pbFadeOutIn(z=99999,nofadeout=false) + col=Color.new(0,0,0,0) + viewport=Viewport.new(0,0,Graphics.width,Graphics.height) + viewport.z=z + numFrames = (Graphics.frame_rate*0.4).floor + alphaDiff = (255.0/numFrames).ceil + for j in 0..numFrames + col.set(0,0,0,j*alphaDiff) + viewport.color=col + Graphics.update + Input.update + end + pbPushFade + begin + yield if block_given? + ensure + pbPopFade + if !nofadeout + for j in 0..numFrames + col.set(0,0,0,(numFrames-j)*alphaDiff) + viewport.color=col + Graphics.update + Input.update + end + end + viewport.dispose + end +end + +def pbFadeOutInWithUpdate(z,sprites,nofadeout=false) + col=Color.new(0,0,0,0) + viewport=Viewport.new(0,0,Graphics.width,Graphics.height) + viewport.z=z + numFrames = (Graphics.frame_rate*0.4).floor + alphaDiff = (255.0/numFrames).ceil + for j in 0..numFrames + col.set(0,0,0,j*alphaDiff) + viewport.color=col + pbUpdateSpriteHash(sprites) + Graphics.update + Input.update + end + pbPushFade + begin + yield if block_given? + ensure + pbPopFade + if !nofadeout + for j in 0..numFrames + col.set(0,0,0,(numFrames-j)*alphaDiff) + viewport.color=col + pbUpdateSpriteHash(sprites) + Graphics.update + Input.update + end + end + viewport.dispose + end +end + +def pbFadeOutAndHide(sprites) + visiblesprites = {} + numFrames = (Graphics.frame_rate*0.4).floor + alphaDiff = (255.0/numFrames).ceil + pbDeactivateWindows(sprites) { + for j in 0..numFrames + pbSetSpritesToColor(sprites,Color.new(0,0,0,j*alphaDiff)) + (block_given?) ? yield : pbUpdateSpriteHash(sprites) + end + } + for i in sprites + next if !i[1] + next if pbDisposed?(i[1]) + visiblesprites[i[0]] = true if i[1].visible + i[1].visible = false + end + return visiblesprites +end + +def pbFadeInAndShow(sprites,visiblesprites=nil) + if visiblesprites + for i in visiblesprites + if i[1] && sprites[i[0]] && !pbDisposed?(sprites[i[0]]) + sprites[i[0]].visible = true + end + end + end + numFrames = (Graphics.frame_rate*0.4).floor + alphaDiff = (255.0/numFrames).ceil + pbDeactivateWindows(sprites) { + for j in 0..numFrames + pbSetSpritesToColor(sprites,Color.new(0,0,0,((numFrames-j)*alphaDiff))) + (block_given?) ? yield : pbUpdateSpriteHash(sprites) + end + } +end + +# Restores which windows are active for the given sprite hash. +# _activeStatuses_ is the result of a previous call to pbActivateWindows +def pbRestoreActivations(sprites,activeStatuses) + return if !sprites || !activeStatuses + for k in activeStatuses.keys + if sprites[k] && sprites[k].is_a?(Window) && !pbDisposed?(sprites[k]) + sprites[k].active=activeStatuses[k] ? true : false + end + end +end + +# Deactivates all windows. If a code block is given, deactivates all windows, +# runs the code in the block, and reactivates them. +def pbDeactivateWindows(sprites) + if block_given? + pbActivateWindow(sprites,nil) { yield } + else + pbActivateWindow(sprites,nil) + end +end + +# Activates a specific window of a sprite hash. _key_ is the key of the window +# in the sprite hash. If a code block is given, deactivates all windows except +# the specified window, runs the code in the block, and reactivates them. +def pbActivateWindow(sprites,key) + return if !sprites + activeStatuses={} + for i in sprites + if i[1] && i[1].is_a?(Window) && !pbDisposed?(i[1]) + activeStatuses[i[0]]=i[1].active + i[1].active=(i[0]==key) + end + end + if block_given? + begin + yield + ensure + pbRestoreActivations(sprites,activeStatuses) + end + return {} + else + return activeStatuses + end +end + +#=============================================================================== +# Create background planes for a sprite hash +#=============================================================================== +# Adds a background to the sprite hash. +# _planename_ is the hash key of the background. +# _background_ is a filename within the Graphics/Pictures/ folder and can be +# an animated image. +# _viewport_ is a viewport to place the background in. +def addBackgroundPlane(sprites,planename,background,viewport=nil) + sprites[planename]=AnimatedPlane.new(viewport) + bitmapName=pbResolveBitmap("Graphics/Pictures/#{background}") + if bitmapName==nil + # Plane should exist in any case + sprites[planename].bitmap=nil + sprites[planename].visible=false + else + sprites[planename].setBitmap(bitmapName) + for spr in sprites.values + if spr.is_a?(Window) + spr.windowskin=nil + end + end + end +end + +# Adds a background to the sprite hash. +# _planename_ is the hash key of the background. +# _background_ is a filename within the Graphics/Pictures/ folder and can be +# an animated image. +# _color_ is the color to use if the background can't be found. +# _viewport_ is a viewport to place the background in. +def addBackgroundOrColoredPlane(sprites,planename,background,color,viewport=nil) + bitmapName=pbResolveBitmap("Graphics/Pictures/#{background}") + if bitmapName==nil + # Plane should exist in any case + sprites[planename]=ColoredPlane.new(color,@viewport) + else + sprites[planename]=AnimatedPlane.new(viewport) + sprites[planename].setBitmap(bitmapName) + for spr in sprites.values + if spr.is_a?(Window) + spr.windowskin=nil + end + end + end +end + + + +#=============================================================================== +# Ensure required method definitions +#=============================================================================== +module Graphics + if !self.respond_to?("width") + def self.width; return 640; end + end + if !self.respond_to?("height") + def self.height; return 480; end + end +end + + + +if !defined?(_INTL) + def _INTL(*args) + string=args[0].clone + for i in 1...args.length + string.gsub!(/\{#{i}\}/,"#{args[i]}") + end + return string + end +end + +if !defined?(_ISPRINTF) + def _ISPRINTF(*args) + string=args[0].clone + for i in 1...args.length + string.gsub!(/\{#{i}\:([^\}]+?)\}/) { |m| + next sprintf("%"+$1,args[i]) + } + end + return string + end +end + +if !defined?(_MAPINTL) + def _MAPINTL(*args) + string=args[1].clone + for i in 2...args.length + string.gsub!(/\{#{i}\}/,"#{args[i+1]}") + end + return string + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/005_SpriteWindow_text.rb b/Data/Scripts/008_Objects and windows/005_SpriteWindow_text.rb new file mode 100644 index 000000000..67181a107 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/005_SpriteWindow_text.rb @@ -0,0 +1,2369 @@ +#=============================================================================== +# +#=============================================================================== +class SpriteWindowCursorRect < Rect + def initialize(window) + @window=window + @x=0 + @y=0 + @width=0 + @height=0 + end + + attr_reader :x,:y,:width,:height + + def empty + needupdate=@x!=0 || @y!=0 || @width!=0 || @height!=0 + if needupdate + @x=0 + @y=0 + @width=0 + @height=0 + @window.width=@window.width + end + end + + def isEmpty? + return @x==0 && @y==0 && @width==0 && @height==0 + end + + def set(x,y,width,height) + needupdate=@x!=x || @y!=y || @width!=width || @height!=height + if needupdate + @x=x + @y=y + @width=width + @height=height + @window.width=@window.width + end + end + + def height=(value) + @height=value; @window.width=@window.width + end + + def width=(value) + @width=value; @window.width=@window.width + end + + def x=(value) + @x=value; @window.width=@window.width + end + + def y=(value) + @y=value; @window.width=@window.width + end +end + + + +#=============================================================================== +# SpriteWindow is a class based on Window which emulates Window's functionality. +# This class is necessary in order to change the viewport of windows (with +# viewport=) and to make windows fade in and out (with tone=). +#=============================================================================== +class SpriteWindow < Window + attr_reader :tone + attr_reader :color + attr_reader :viewport + attr_reader :contents + attr_reader :ox + attr_reader :oy + attr_reader :x + attr_reader :y + attr_reader :z + attr_reader :zoom_x + attr_reader :zoom_y + attr_reader :offset_x + attr_reader :offset_y + attr_reader :width + attr_reader :active + attr_reader :pause + attr_reader :height + attr_reader :opacity + attr_reader :back_opacity + attr_reader :contents_opacity + attr_reader :visible + attr_reader :cursor_rect + attr_reader :contents_blend_type + attr_reader :blend_type + attr_reader :openness + + def windowskin + @_windowskin + end + + # Flags used to preserve compatibility + # with RGSS/RGSS2's version of Window + module CompatBits + CorrectZ = 1 + ExpandBack = 2 + ShowScrollArrows = 4 + StretchSides = 8 + ShowPause = 16 + ShowCursor = 32 + end + + attr_reader :compat + + def compat=(value) + @compat=value + privRefresh(true) + end + + def initialize(viewport=nil) + @sprites={} + @spritekeys=[ + "back", + "corner0","side0","scroll0", + "corner1","side1","scroll1", + "corner2","side2","scroll2", + "corner3","side3","scroll3", + "cursor","contents","pause" + ] + @viewport=viewport + @sidebitmaps=[nil,nil,nil,nil] + @cursorbitmap=nil + @bgbitmap=nil + for i in @spritekeys + @sprites[i]=Sprite.new(@viewport) + end + @disposed=false + @tone=Tone.new(0,0,0) + @color=Color.new(0,0,0,0) + @blankcontents=Bitmap.new(1,1) # RGSS2 requires this + @contents=@blankcontents + @_windowskin=nil + @rpgvx=false + @compat=CompatBits::ExpandBack|CompatBits::StretchSides + @x=0 + @y=0 + @width=0 + @height=0 + @offset_x=0 + @offset_y=0 + @zoom_x=1.0 + @zoom_y=1.0 + @ox=0 + @oy=0 + @z=0 + @stretch=true + @visible=true + @active=true + @openness=255 + @opacity=255 + @back_opacity=255 + @blend_type=0 + @contents_blend_type=0 + @contents_opacity=255 + @cursor_rect=SpriteWindowCursorRect.new(self) + @cursorblink=0 + @cursoropacity=255 + @pause=false + @pauseframe=0 + @flash=0 + @pauseopacity=0 + @skinformat=0 + @skinrect=Rect.new(0,0,0,0) + @trim=[16,16,16,16] + privRefresh(true) + end + + def dispose + if !self.disposed? + for i in @sprites + i[1].dispose if i[1] + @sprites[i[0]]=nil + end + for i in 0...@sidebitmaps.length + @sidebitmaps[i].dispose if @sidebitmaps[i] + @sidebitmaps[i]=nil + end + @blankcontents.dispose + @cursorbitmap.dispose if @cursorbitmap + @backbitmap.dispose if @backbitmap + @sprites.clear + @sidebitmaps.clear + @_windowskin=nil + @disposed=true + end + end + + def stretch=(value) + @stretch=value + privRefresh(true) + end + + def visible=(value) + @visible=value + privRefresh + end + + def viewport=(value) + @viewport=value + for i in @spritekeys + @sprites[i].dispose if @sprites[i] + end + for i in @spritekeys + if @sprites[i].is_a?(Sprite) + @sprites[i]=Sprite.new(@viewport) + else + @sprites[i]=nil + end + end + privRefresh(true) + end + + def z=(value) + @z=value + privRefresh + end + + def disposed? + return @disposed + end + + def contents=(value) + if @contents!=value + @contents=value + privRefresh if @visible + end + end + + def ox=(value) + if @ox!=value + @ox=value + privRefresh if @visible + end + end + + def oy=(value) + if @oy!=value + @oy=value + privRefresh if @visible + end + end + + def active=(value) + @active=value + privRefresh(true) + end + + def cursor_rect=(value) + if !value + @cursor_rect.empty + else + @cursor_rect.set(value.x,value.y,value.width,value.height) + end + end + + def openness=(value) + @openness=value + @openness=0 if @openness<0 + @openness=255 if @openness>255 + privRefresh + end + + def width=(value) + @width=value + privRefresh(true) + end + + def height=(value) + @height=value + privRefresh(true) + end + + def pause=(value) + @pause=value + @pauseopacity=0 if !value + privRefresh if @visible + end + + def x=(value) + @x=value + privRefresh if @visible + end + + def y=(value) + @y=value + privRefresh if @visible + end + + def zoom_x=(value) + @zoom_x=value + privRefresh if @visible + end + + def zoom_y=(value) + @zoom_y=value + privRefresh if @visible + end + + def offset_x=(value) + @x=value + privRefresh if @visible + end + + def offset_y=(value) + @y=value + privRefresh if @visible + end + + def opacity=(value) + @opacity=value + @opacity=0 if @opacity<0 + @opacity=255 if @opacity>255 + privRefresh if @visible + end + + def back_opacity=(value) + @back_opacity=value + @back_opacity=0 if @back_opacity<0 + @back_opacity=255 if @back_opacity>255 + privRefresh if @visible + end + + def contents_opacity=(value) + @contents_opacity=value + @contents_opacity=0 if @contents_opacity<0 + @contents_opacity=255 if @contents_opacity>255 + privRefresh if @visible + end + + def tone=(value) + @tone=value + privRefresh if @visible + end + + def color=(value) + @color=value + privRefresh if @visible + end + + def blend_type=(value) + @blend_type=value + privRefresh if @visible + end + + def flash(color,duration) + return if disposed? + @flash=duration+1 + for i in @sprites + i[1].flash(color,duration) + end + end + + def update + return if disposed? + mustchange=false + if @active + if @cursorblink==0 + @cursoropacity-=8 + @cursorblink=1 if @cursoropacity<=128 + else + @cursoropacity+=8 + @cursorblink=0 if @cursoropacity>=255 + end + privRefreshCursor + else + @cursoropacity=128 + privRefreshCursor + end + if @pause + oldpauseframe=@pauseframe + oldpauseopacity=@pauseopacity + @pauseframe=(Graphics.frame_count / 8) % 4 + @pauseopacity=[@pauseopacity+64,255].min + mustchange=@pauseframe!=oldpauseframe || @pauseopacity!=oldpauseopacity + end + privRefresh if mustchange + if @flash>0 + for i in @sprites.values + i.update + end + @flash-=1 + end + end + + ############# + attr_reader :skinformat + attr_reader :skinrect + + def loadSkinFile(file) + if (self.windowskin.width==80 || self.windowskin.width==96) && + self.windowskin.height==48 + # Body = X, Y, width, height of body rectangle within windowskin + @skinrect.set(32,16,16,16) + # Trim = X, Y, width, height of trim rectangle within windowskin + @trim=[32,16,16,16] + elsif self.windowskin.width==80 && self.windowskin.height==80 + @skinrect.set(32,32,16,16) + @trim=[32,16,16,48] + end + end + + def windowskin=(value) + oldSkinWidth=(@_windowskin && !@_windowskin.disposed?) ? @_windowskin.width : -1 + oldSkinHeight=(@_windowskin && !@_windowskin.disposed?) ? @_windowskin.height : -1 + @_windowskin=value + if @skinformat==1 + @rpgvx=false + if @_windowskin && !@_windowskin.disposed? + if @_windowskin.width!=oldSkinWidth || @_windowskin.height!=oldSkinHeight + # Update skinrect and trim if windowskin's dimensions have changed + @skinrect.set((@_windowskin.width-16)/2,(@_windowskin.height-16)/2,16,16) + @trim=[@skinrect.x,@skinrect.y,@skinrect.x,@skinrect.y] + end + else + @skinrect.set(16,16,16,16) + @trim=[16,16,16,16] + end + else + if value && value.is_a?(Bitmap) && !value.disposed? && value.width==128 + @rpgvx=true + else + @rpgvx=false + end + @trim=[16,16,16,16] + end + privRefresh(true) + end + + def skinrect=(value) + @skinrect=value + privRefresh + end + + def skinformat=(value) + if @skinformat!=value + @skinformat=value + privRefresh(true) + end + end + + def borderX + return 32 if !@trim || skinformat==0 + if @_windowskin && !@_windowskin.disposed? + return @trim[0]+(@_windowskin.width-@trim[2]-@trim[0]) + end + return 32 + end + + def borderY + return 32 if !@trim || skinformat==0 + if @_windowskin && !@_windowskin.disposed? + return @trim[1]+(@_windowskin.height-@trim[3]-@trim[1]) + end + return 32 + end + + def leftEdge; self.startX; end + def topEdge; self.startY; end + def rightEdge; self.borderX-self.leftEdge; end + def bottomEdge; self.borderY-self.topEdge; end + + def startX + return !@trim || skinformat==0 ? 16 : @trim[0] + end + + def startY + return !@trim || skinformat==0 ? 16 : @trim[1] + end + + def endX + return !@trim || skinformat==0 ? 16 : @trim[2] + end + + def endY + return !@trim || skinformat==0 ? 16 : @trim[3] + end + + def startX=(value) + @trim[0]=value + privRefresh + end + + def startY=(value) + @trim[1]=value + privRefresh + end + + def endX=(value) + @trim[2]=value + privRefresh + end + + def endY=(value) + @trim[3]=value + privRefresh + end + + ############# + private + + def ensureBitmap(bitmap,dwidth,dheight) + if !bitmap||bitmap.disposed?||bitmap.width0 && @skinformat==0 && !@rpgvx + # Compatibility Mode: Cursor, pause, and contents have higher Z + @sprites["cursor"].z=@z+1 + @sprites["contents"].z=@z+2 + @sprites["pause"].z=@z+2 + end + if @skinformat==0 + startX=16 + startY=16 + endX=16 + endY=16 + trimStartX=16 + trimStartY=16 + trimWidth=32 + trimHeight=32 + if @rpgvx + trimX=64 + trimY=0 + backRect=Rect.new(0,0,64,64) + blindsRect=Rect.new(0,64,64,64) + else + trimX=128 + trimY=0 + backRect=Rect.new(0,0,128,128) + blindsRect=nil + end + if @_windowskin && !@_windowskin.disposed? + @sprites["corner0"].src_rect.set(trimX,trimY+0,16,16); + @sprites["corner1"].src_rect.set(trimX+48,trimY+0,16,16); + @sprites["corner2"].src_rect.set(trimX,trimY+48,16,16); + @sprites["corner3"].src_rect.set(trimX+48,trimY+48,16,16); + @sprites["scroll0"].src_rect.set(trimX+24, trimY+16, 16, 8) # up + @sprites["scroll3"].src_rect.set(trimX+24, trimY+40, 16, 8) # down + @sprites["scroll1"].src_rect.set(trimX+16, trimY+24, 8, 16) # left + @sprites["scroll2"].src_rect.set(trimX+40, trimY+24, 8, 16) # right + cursorX=trimX + cursorY=trimY+64 + sideRects=[ + Rect.new(trimX+16,trimY+0,32,16), + Rect.new(trimX,trimY+16,16,32), + Rect.new(trimX+48,trimY+16,16,32), + Rect.new(trimX+16,trimY+48,32,16) + ] + pauseRects=[ + trimX+32,trimY+64, + trimX+48,trimY+64, + trimX+32,trimY+80, + trimX+48,trimY+80, + ] + pauseWidth=16 + pauseHeight=16 + @sprites["pause"].src_rect.set( + pauseRects[@pauseframe*2], + pauseRects[@pauseframe*2+1], + pauseWidth,pauseHeight + ) + end + else + trimStartX=@trim[0] + trimStartY=@trim[1] + trimWidth=@trim[0]+(@skinrect.width-@trim[2]+@trim[0]) + trimHeight=@trim[1]+(@skinrect.height-@trim[3]+@trim[1]) + if @_windowskin && !@_windowskin.disposed? + # width of left end of window + startX=@skinrect.x + # width of top end of window + startY=@skinrect.y + backWidth=@skinrect.width + backHeight=@skinrect.height + cx=@skinrect.x+@skinrect.width # right side of BODY rect + cy=@skinrect.y+@skinrect.height # bottom side of BODY rect + # width of right end of window + endX=(!@_windowskin || @_windowskin.disposed?) ? @skinrect.x : @_windowskin.width-cx + # height of bottom end of window + endY=(!@_windowskin || @_windowskin.disposed?) ? @skinrect.y : @_windowskin.height-cy + @sprites["corner0"].src_rect.set(0,0,startX,startY); + @sprites["corner1"].src_rect.set(cx,0,endX,startY); + @sprites["corner2"].src_rect.set(0,cy,startX,endY); + @sprites["corner3"].src_rect.set(cx,cy,endX,endY); + backRect=Rect.new(@skinrect.x,@skinrect.y, + @skinrect.width,@skinrect.height); + blindsRect=nil + sideRects=[ + Rect.new(startX,0,@skinrect.width,startY), # side0 (top) + Rect.new(0,startY,startX,@skinrect.height), # side1 (left) + Rect.new(cx,startY,endX,@skinrect.height), # side2 (right) + Rect.new(startX,cy,@skinrect.width,endY) # side3 (bottom) + ] + end + end + if @width>trimWidth && @height>trimHeight + @sprites["contents"].src_rect.set(@ox,@oy,@width-trimWidth,@height-trimHeight) + else + @sprites["contents"].src_rect.set(0,0,0,0) + end + @sprites["contents"].x=@x+trimStartX + @sprites["contents"].y=@y+trimStartY + if (@compat & CompatBits::ShowScrollArrows)>0 && @skinformat==0 + # Compatibility mode: Make scroll arrows visible + if @skinformat==0 && @_windowskin && !@_windowskin.disposed? && + @contents && !@contents.disposed? + @sprites["scroll0"].visible = @visible && hascontents && @oy > 0 + @sprites["scroll1"].visible = @visible && hascontents && @ox > 0 + @sprites["scroll2"].visible = @visible && (@contents.width - @ox) > @width-trimWidth + @sprites["scroll3"].visible = @visible && (@contents.height - @oy) > @height-trimHeight + end + end + if @_windowskin && !@_windowskin.disposed? + backTrimX=startX+endX + backTrimY=startX+endX + borderX=startX+endX + borderY=startY+endY + @sprites["corner0"].x=@x + @sprites["corner0"].y=@y + @sprites["corner1"].x=@x+@width-endX + @sprites["corner1"].y=@y + @sprites["corner2"].x=@x + @sprites["corner2"].y=@y+@height-endY + @sprites["corner3"].x=@x+@width-endX + @sprites["corner3"].y=@y+@height-endY + @sprites["side0"].x=@x+startX + @sprites["side0"].y=@y + @sprites["side1"].x=@x + @sprites["side1"].y=@y+startY + @sprites["side2"].x=@x+@width-endX + @sprites["side2"].y=@y+startY + @sprites["side3"].x=@x+startX + @sprites["side3"].y=@y+@height-endY + @sprites["scroll0"].x = @x+@width / 2 - 8 + @sprites["scroll0"].y = @y+8 + @sprites["scroll1"].x = @x+8 + @sprites["scroll1"].y = @y+@height / 2 - 8 + @sprites["scroll2"].x = @x+@width - 16 + @sprites["scroll2"].y = @y+@height / 2 - 8 + @sprites["scroll3"].x = @x+@width / 2 - 8 + @sprites["scroll3"].y = @y+@height - 16 + @sprites["cursor"].x=@x+startX+@cursor_rect.x + @sprites["cursor"].y=@y+startY+@cursor_rect.y + if (@compat & CompatBits::ExpandBack)>0 && @skinformat==0 + # Compatibility mode: Expand background + @sprites["back"].x=@x+2 + @sprites["back"].y=@y+2 + else + @sprites["back"].x=@x+startX + @sprites["back"].y=@y+startY + end + end + if changeBitmap && @_windowskin && !@_windowskin.disposed? + if @skinformat==0 + @sprites["cursor"].x=@x+startX+@cursor_rect.x + @sprites["cursor"].y=@y+startY+@cursor_rect.y + width=@cursor_rect.width + height=@cursor_rect.height + if width > 0 && height > 0 + cursorrects=[ + # sides + Rect.new(cursorX+2, cursorY+0, 28, 2), + Rect.new(cursorX+0, cursorY+2, 2, 28), + Rect.new(cursorX+30, cursorY+2, 2, 28), + Rect.new(cursorX+2, cursorY+30, 28, 2), + # corners + Rect.new(cursorX+0, cursorY+0, 2, 2), + Rect.new(cursorX+30, cursorY+0, 2, 2), + Rect.new(cursorX+0, cursorY+30, 2, 2), + Rect.new(cursorX+30, cursorY+30, 2, 2), + # back + Rect.new(cursorX+2, cursorY+2, 28, 28) + ] + margin=2 + fullmargin=4 + @cursorbitmap = ensureBitmap(@cursorbitmap, width, height) + @cursorbitmap.clear + @sprites["cursor"].bitmap=@cursorbitmap + @sprites["cursor"].src_rect.set(0,0,width,height) + rect = Rect.new(margin,margin,width - fullmargin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[8]) + @cursorbitmap.blt(0, 0, @_windowskin, cursorrects[4])# top left + @cursorbitmap.blt(width-margin, 0, @_windowskin, cursorrects[5]) # top right + @cursorbitmap.blt(0, height-margin, @_windowskin, cursorrects[6]) # bottom right + @cursorbitmap.blt(width-margin, height-margin, @_windowskin, cursorrects[7]) # bottom left + rect = Rect.new(margin, 0,width - fullmargin, margin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[0]) + rect = Rect.new(0, margin,margin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[1]) + rect = Rect.new(width - margin, margin, margin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[2]) + rect = Rect.new(margin, height-margin, width - fullmargin, margin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[3]) + else + @sprites["cursor"].visible=false + @sprites["cursor"].src_rect.set(0,0,0,0) + end + end + for i in 0..3 + case i + when 0 + dwidth = @width-startX-endX + dheight = startY + when 1 + dwidth = startX + dheight = @height-startY-endY + when 2 + dwidth = endX + dheight = @height-startY-endY + when 3 + dwidth = @width-startX-endX + dheight = endY + end + @sidebitmaps[i]=ensureBitmap(@sidebitmaps[i],dwidth,dheight) + @sprites["side#{i}"].bitmap=@sidebitmaps[i] + @sprites["side#{i}"].src_rect.set(0,0,dwidth,dheight) + @sidebitmaps[i].clear + if sideRects[i].width>0 && sideRects[i].height>0 + if (@compat & CompatBits::StretchSides)>0 && @skinformat==0 + # Compatibility mode: Stretch sides + @sidebitmaps[i].stretch_blt(@sprites["side#{i}"].src_rect, + @_windowskin,sideRects[i]) + else + tileBitmap(@sidebitmaps[i],@sprites["side#{i}"].src_rect, + @_windowskin,sideRects[i]) + end + end + end + if (@compat & CompatBits::ExpandBack)>0 && @skinformat==0 + # Compatibility mode: Expand background + backwidth=@width-4 + backheight=@height-4 + else + backwidth=@width-borderX + backheight=@height-borderY + end + if backwidth>0 && backheight>0 + @backbitmap=ensureBitmap(@backbitmap,backwidth,backheight) + @sprites["back"].bitmap=@backbitmap + @sprites["back"].src_rect.set(0,0,backwidth,backheight) + @backbitmap.clear + if @stretch + @backbitmap.stretch_blt(@sprites["back"].src_rect,@_windowskin,backRect) + else + tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,backRect) + end + if blindsRect + tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,blindsRect) + end + else + @sprites["back"].visible=false + @sprites["back"].src_rect.set(0,0,0,0) + end + end + if @openness!=255 + opn=@openness/255.0 + for k in @spritekeys + sprite=@sprites[k] + ratio=(@height<=0) ? 0 : (sprite.y-@y)*1.0/@height + sprite.zoom_y=opn + sprite.zoom_x=1.0 + sprite.oy=0 + sprite.y=(@y+(@height/2.0)+(@height*ratio*opn)-(@height/2*opn)).floor + oldbitmap=sprite.bitmap + oldsrcrect=sprite.src_rect.clone + end + else + for k in @spritekeys + sprite=@sprites[k] + sprite.zoom_x=1.0 + sprite.zoom_y=1.0 + end + end + i=0 + # Ensure Z order + for k in @spritekeys + sprite=@sprites[k] + y=sprite.y + sprite.y=i + sprite.oy=(sprite.zoom_y<=0) ? 0 : (i-y)/sprite.zoom_y + sprite.zoom_x*=@zoom_x + sprite.zoom_y*=@zoom_y + sprite.x*=@zoom_x + sprite.y*=@zoom_y + sprite.x+=(@offset_x/sprite.zoom_x) + sprite.y+=(@offset_y/sprite.zoom_y) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class SpriteWindow_Base < SpriteWindow + TEXTPADDING=4 # In pixels + + def initialize(x, y, width, height) + super() + self.x = x + self.y = y + self.width = width + self.height = height + self.z = 100 + @curframe=MessageConfig.pbGetSystemFrame() + @curfont=MessageConfig.pbGetSystemFontName() + @sysframe=AnimatedBitmap.new(@curframe) + @customskin=nil + __setWindowskin(@sysframe.bitmap) + __resolveSystemFrame() + pbSetSystemFont(self.contents) if self.contents + end + + def __setWindowskin(skin) + if skin && (skin.width==192 && skin.height==128) || # RPGXP Windowskin + (skin.width==128 && skin.height==128) # RPGVX Windowskin + self.skinformat=0 + else + self.skinformat=1 + end + self.windowskin=skin + end + + def __resolveSystemFrame + if self.skinformat==1 + if !@resolvedFrame + @resolvedFrame=MessageConfig.pbGetSystemFrame() + @resolvedFrame.sub!(/\.[^\.\/\\]+$/,"") + end + self.loadSkinFile("#{@resolvedFrame}.txt") if @resolvedFrame!="" + end + end + + def setSkin(skin) # Filename of windowskin to apply. Supports XP, VX, and animated skins. + @customskin.dispose if @customskin + @customskin=nil + resolvedName=pbResolveBitmap(skin) + return if !resolvedName || resolvedName=="" + @customskin=AnimatedBitmap.new(resolvedName) + __setWindowskin(@customskin.bitmap) + if self.skinformat==1 + skinbase=resolvedName.sub(/\.[^\.\/\\]+$/,"") + self.loadSkinFile("#{skinbase}.txt") + end + end + + def setSystemFrame + @customskin.dispose if @customskin + @customskin=nil + __setWindowskin(@sysframe.bitmap) + __resolveSystemFrame() + end + + def update + super + if self.windowskin + if @customskin + if @customskin.totalFrames>1 + @customskin.update + __setWindowskin(@customskin.bitmap) + end + elsif @sysframe + if @sysframe.totalFrames>1 + @sysframe.update + __setWindowskin(@sysframe.bitmap) + end + end + end + if @curframe!=MessageConfig.pbGetSystemFrame() + @curframe=MessageConfig.pbGetSystemFrame() + if @sysframe && !@customskin + @sysframe.dispose if @sysframe + @sysframe=AnimatedBitmap.new(@curframe) + @resolvedFrame=nil + __setWindowskin(@sysframe.bitmap) + __resolveSystemFrame() + end + begin + refresh + rescue NoMethodError + end + end + if @curfont!=MessageConfig.pbGetSystemFontName() + @curfont=MessageConfig.pbGetSystemFontName() + if self.contents && !self.contents.disposed? + pbSetSystemFont(self.contents) + end + begin + refresh + rescue NoMethodError + end + end + end + + def dispose + self.contents.dispose if self.contents + @sysframe.dispose + @customskin.dispose if @customskin + super + end +end + + + +#=============================================================================== +# +#=============================================================================== +# Represents a window with no formatting capabilities. Its text color can be set, +# though, and line breaks are supported, but the text is generally unformatted. +class Window_UnformattedTextPokemon < SpriteWindow_Base + attr_reader :text + attr_reader :baseColor + attr_reader :shadowColor + # Letter-by-letter mode. This mode is not supported in this class. + attr_accessor :letterbyletter + + def text=(value) + @text=value + refresh + end + + def baseColor=(value) + @baseColor=value + refresh + end + + def shadowColor=(value) + @shadowColor=value + refresh + end + + def initialize(text="") + super(0,0,33,33) + self.contents=Bitmap.new(1,1) + pbSetSystemFont(self.contents) + @text=text + @letterbyletter=false # Not supported in this class + colors=getDefaultTextColors(self.windowskin) + @baseColor=colors[0] + @shadowColor=colors[1] + resizeToFit(text) + end + + def self.newWithSize(text,x,y,width,height,viewport=nil) + ret=self.new(text) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + ret.refresh + return ret + end + + def resizeToFitInternal(text,maxwidth) # maxwidth is maximum acceptable window width + dims=[0,0] + cwidth=maxwidth<0 ? Graphics.width : maxwidth + getLineBrokenChunks(self.contents,text, + cwidth-self.borderX-SpriteWindow_Base::TEXTPADDING,dims,true) + return dims + end + + def setTextToFit(text,maxwidth=-1) + resizeToFit(text,maxwidth) + self.text=text + end + + def resizeToFit(text,maxwidth=-1) # maxwidth is maximum acceptable window width + dims=resizeToFitInternal(text,maxwidth) + self.width=dims[0]+self.borderX+SpriteWindow_Base::TEXTPADDING + self.height=dims[1]+self.borderY + refresh + end + + def resizeHeightToFit(text,width=-1) # width is current window width + dims=resizeToFitInternal(text,width) + self.width=width<0 ? Graphics.width : width + self.height=dims[1]+self.borderY + refresh + end + + def setSkin(skin) + super(skin) + privRefresh(true) + oldbaser = @baseColor.red + oldbaseg = @baseColor.green + oldbaseb = @baseColor.blue + oldbasea = @baseColor.alpha + oldshadowr = @shadowColor.red + oldshadowg = @shadowColor.green + oldshadowb = @shadowColor.blue + oldshadowa = @shadowColor.alpha + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + if oldbaser!=@baseColor.red || oldbaseg!=@baseColor.green || + oldbaseb!=@baseColor.blue || oldbasea!=@baseColor.alpha || + oldshadowr!=@shadowColor.red || oldshadowg!=@shadowColor.green || + oldshadowb!=@shadowColor.blue || oldshadowa!=@shadowColor.alpha + self.text = self.text + end + end + + def refresh + self.contents=pbDoEnsureBitmap(self.contents,self.width-self.borderX, + self.height-self.borderY) + self.contents.clear + drawTextEx(self.contents,0,0,self.contents.width,0, + @text.gsub(/\r/,""),@baseColor,@shadowColor) + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_AdvancedTextPokemon < SpriteWindow_Base + attr_reader :text + attr_reader :baseColor + attr_reader :shadowColor + attr_accessor :letterbyletter + attr_reader :lineHeight + attr_reader :waitcount + + def initialize(text="") + @cursorMode = MessageConfig::CURSORMODE + @endOfText = nil + @scrollstate = 0 + @realframes = 0 + @scrollY = 0 + @nodraw = false + @lineHeight = 32 + @linesdrawn = 0 + @bufferbitmap = nil + @letterbyletter = false + @starting = true + @displaying = false + @lastDrawnChar = -1 + @fmtchars = [] + @frameskipChanged = false + @frameskip = MessageConfig.pbGetTextSpeed() + super(0,0,33,33) + @pausesprite = nil + @text = "" + self.contents = Bitmap.new(1,1) + pbSetSystemFont(self.contents) + self.resizeToFit(text,Graphics.width) + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + self.text = text + @starting = false + end + + def self.newWithSize(text,x,y,width,height,viewport=nil) + ret = self.new(text) + ret.x = x + ret.y = y + ret.width = width + ret.height = height + ret.viewport = viewport + return ret + end + + def dispose + return if disposed? + @pausesprite.dispose if @pausesprite + @pausesprite = nil + super + end + + def waitcount=(value) + @waitcount = (value<=0) ? 0 : value + end + + attr_reader :cursorMode + + def cursorMode=(value) + @cursorMode = value + moveCursor + end + + def lineHeight(value) + @lineHeight = value + self.text = self.text + end + + def baseColor=(value) + @baseColor = value + refresh + end + + def shadowColor=(value) + @shadowColor = value + refresh + end + + def textspeed + @frameskip + end + + def textspeed=(value) + @frameskipChanged = true if @frameskip!=value + @frameskip = value + end + + def width=(value) + super + self.text = self.text if !@starting + end + + def height=(value) + super + self.text = self.text if !@starting + end + + def resizeToFit(text,maxwidth=-1) + dims = resizeToFitInternal(text,maxwidth) + oldstarting = @starting + @starting = true + self.width = dims[0]+self.borderX+SpriteWindow_Base::TEXTPADDING + self.height = dims[1]+self.borderY + @starting = oldstarting + redrawText + end + + def resizeToFit2(text,maxwidth,maxheight) + dims = resizeToFitInternal(text,maxwidth) + oldstarting = @starting + @starting = true + self.width = [dims[0]+self.borderX+SpriteWindow_Base::TEXTPADDING,maxwidth].min + self.height = [dims[1]+self.borderY,maxheight].min + @starting = oldstarting + redrawText + end + + def resizeToFitInternal(text,maxwidth) + dims = [0,0] + cwidth = (maxwidth<0) ? Graphics.width : maxwidth + chars = getFormattedTextForDims(self.contents,0,0, + cwidth-self.borderX-2-6,-1,text,@lineHeight,true) + for ch in chars + dims[0] = [dims[0],ch[1]+ch[3]].max + dims[1] = [dims[1],ch[2]+ch[4]].max + end + return dims + end + + def resizeHeightToFit(text,width=-1) + dims = resizeToFitInternal(text,width) + oldstarting = @starting + @starting = true + self.width = (width<0) ? Graphics.width : width + self.height = dims[1]+self.borderY + @starting = oldstarting + redrawText + end + + def setSkin(skin,redrawText=true) + super(skin) + privRefresh(true) + oldbaser = @baseColor.red + oldbaseg = @baseColor.green + oldbaseb = @baseColor.blue + oldbasea = @baseColor.alpha + oldshadowr = @shadowColor.red + oldshadowg = @shadowColor.green + oldshadowb = @shadowColor.blue + oldshadowa = @shadowColor.alpha + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + if redrawText && + (oldbaser!=@baseColor.red || oldbaseg!=@baseColor.green || + oldbaseb!=@baseColor.blue || oldbasea!=@baseColor.alpha || + oldshadowr!=@shadowColor.red || oldshadowg!=@shadowColor.green || + oldshadowb!=@shadowColor.blue || oldshadowa!=@shadowColor.alpha) + setText(self.text) + end + end + + def setTextToFit(text,maxwidth=-1) + resizeToFit(text,maxwidth) + self.text = text + end + + def text=(value) + setText(value) + end + + def setText(value) + @waitcount = 0 + @curchar = 0 + @drawncurchar = -1 + @lastDrawnChar = -1 + oldtext = @text + @text = value + @textlength = unformattedTextLength(value) + @scrollstate = 0 + @scrollY = 0 + @linesdrawn = 0 + @realframes = 0 + @textchars = [] + width = 1 + height = 1 + numlines = 0 + visiblelines = (self.height-self.borderY)/32 + if value.length==0 + @fmtchars = [] + @bitmapwidth = width + @bitmapheight = height + @numtextchars = 0 + else + if @letterbyletter + @fmtchars = [] + fmt = getFormattedText(self.contents,0,0, + self.width-self.borderX-SpriteWindow_Base::TEXTPADDING,-1, + shadowctag(@baseColor,@shadowColor)+value,32,true) + @oldfont = self.contents.font.clone + for ch in fmt + chx = ch[1]+ch[3] + chy = ch[2]+ch[4] + width = chx if width=visiblelines + fclone = ch.clone + fclone[0] = "\1" + @fmtchars.push(fclone) + @textchars.push("\1") + end + end + # Don't add newline characters, since they + # can slow down letter-by-letter display + if ch[5] || (ch[0]!="\r") + @fmtchars.push(ch) + @textchars.push(ch[5] ? "" : ch[0]) + end + end + fmt.clear + else + @fmtchars = getFormattedText(self.contents,0,0, + self.width-self.borderX-SpriteWindow_Base::TEXTPADDING,-1, + shadowctag(@baseColor,@shadowColor)+value,32,true) + @oldfont = self.contents.font.clone + for ch in @fmtchars + chx = ch[1]+ch[3] + chy = ch[2]+ch[4] + width = chx if width=@fmtchars.length + # index after the last character's index + return @fmtchars[@lastDrawnChar][14]+1 + end + + def maxPosition + pos = 0 + for ch in @fmtchars + # index after the last character's index + pos = ch[14]+1 if pos=@fmtchars.length # End of message + if @textchars[@curchar]=="\1" # Pause message + @pausing = true if @curchar<@numtextchars-1 + self.startPause + refresh + break + end + break if @textchars[@curchar]!="\n" # Skip past newlines only + break if @linesdrawn>=visiblelines-1 # No more empty lines to continue to + @linesdrawn += 1 + end + end + + def allocPause + return if @pausesprite + @pausesprite = AnimatedSprite.create("Graphics/Pictures/pause",4,3) + @pausesprite.z = 100000 + @pausesprite.visible = false + end + + def startPause + allocPause + @pausesprite.visible = true + @pausesprite.frame = 0 + @pausesprite.start + moveCursor + end + + def stopPause + return if !@pausesprite + @pausesprite.stop + @pausesprite.visible = false + end + + def moveCursor + return if !@pausesprite + cursor = @cursorMode + cursor = 2 if cursor==0 && !@endOfText + case cursor + when 0 # End of text + @pausesprite.x = self.x+self.startX+@endOfText.x+@endOfText.width-2 + @pausesprite.y = self.y+self.startY+@endOfText.y-@scrollY + when 1 # Lower right + pauseWidth = @pausesprite.bitmap ? @pausesprite.framewidth : 16 + pauseHeight = @pausesprite.bitmap ? @pausesprite.frameheight : 16 + @pausesprite.x = self.x+self.width-(20*2)+(pauseWidth/2) + @pausesprite.y = self.y+self.height-(30*2)+(pauseHeight/2) + when 2 # Lower middle + pauseWidth = @pausesprite.bitmap ? @pausesprite.framewidth : 16 + pauseHeight = @pausesprite.bitmap ? @pausesprite.frameheight : 16 + @pausesprite.x = self.x+(self.width/2)-(pauseWidth/2) + @pausesprite.y = self.y+self.height-(18*2)+(pauseHeight/2) + end + end + + def refresh + oldcontents = self.contents + self.contents = pbDoEnsureBitmap(oldcontents,@bitmapwidth,@bitmapheight) + self.oy = @scrollY + numchars = @numtextchars + numchars = [@curchar,@numtextchars].min if self.letterbyletter + startchar = 0 + return if busy? && @drawncurchar==@curchar && @scrollstate==0 + if !self.letterbyletter || !oldcontents.equal?(self.contents) + @drawncurchar = -1 + @needclear = true + end + if @needclear + self.contents.font = @oldfont if @oldfont + self.contents.clear + @needclear = false + end + if @nodraw + @nodraw = false + return + end + maxX = self.width-self.borderX + maxY = self.height-self.borderY + for i in @drawncurchar+1..numchars + next if i>=@fmtchars.length + if !self.letterbyletter + next if @fmtchars[i][1]>=maxX + next if @fmtchars[i][2]>=maxY + end + drawSingleFormattedChar(self.contents,@fmtchars[i]) + @lastDrawnChar = i + end + if !self.letterbyletter + # all characters were drawn, reset old font + self.contents.font = @oldfont if @oldfont + end + if numchars>0 && numchars!=@numtextchars + fch = @fmtchars[numchars-1] + if fch + rcdst = Rect.new(fch[1],fch[2],fch[3],fch[4]) + if @textchars[numchars]=="\1" + @endOfText = rcdst + allocPause + moveCursor + else + @endOfText = Rect.new(rcdst.x+rcdst.width,rcdst.y,8,1) + end + end + end + @drawncurchar = @curchar + end + + def redrawText + if @letterbyletter + oldPosition = self.position + self.text = self.text + oldPosition = @numtextchars if oldPosition>@numtextchars + while self.position!=oldPosition + refresh + updateInternal + end + else + self.text = self.text + end + end + + def updateInternal + curcharskip = @frameskip<0 ? @frameskip.abs : 1 + visiblelines = (self.height-self.borderY)/@lineHeight + if @textchars[@curchar]=="\1" + if !@pausing + @realframes += 1 + if @realframes>=@frameskip || @frameskip<0 + curcharSkip(curcharskip) + @realframes = 0 + end + end + elsif @textchars[@curchar]=="\n" + if @linesdrawn>=visiblelines-1 + if @scrollstate<@lineHeight + @scrollstate += [(@lineHeight/4),1].max + @scrollY += [(@lineHeight/4),1].max + end + if @scrollstate>=@lineHeight + @realframes += 1 + if @realframes>=@frameskip || @frameskip<0 + curcharSkip(curcharskip) + @linesdrawn += 1 + @realframes = 0 + @scrollstate = 0 + end + end + else + @realframes += 1 + if @realframes>=@frameskip || @frameskip<0 + curcharSkip(curcharskip) + @linesdrawn += 1 + @realframes = 0 + end + end + elsif @curchar<=@numtextchars + @realframes += 1 + if @realframes>=@frameskip || @frameskip<0 + curcharSkip(curcharskip) + @realframes = 0 + end + if @textchars[@curchar]=="\1" + @pausing = true if @curchar<@numtextchars-1 + self.startPause + refresh + end + else + @displaying = false + @scrollstate = 0 + @scrollY = 0 + @linesdrawn = 0 + end + end + + def update + super + @pausesprite.update if @pausesprite && @pausesprite.visible + if @waitcount>0 + @waitcount -= 1 + return + end + if busy? + refresh if !@frameskipChanged + updateInternal + # following line needed to allow "textspeed=-999" to work seamlessly + refresh if @frameskipChanged + end + @frameskipChanged = false + end + + private + + def curcharSkip(skip) + skip.times do + @curchar += 1 + break if @textchars[@curchar]=="\n" || # newline + @textchars[@curchar]=="\1" || # pause + @textchars[@curchar]=="\2" || # letter-by-letter break + @textchars[@curchar]==nil + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_InputNumberPokemon < SpriteWindow_Base + attr_reader :number + attr_reader :sign + + def initialize(digits_max) + @digits_max=digits_max + @number=0 + @frame=0 + @sign=false + @negative=false + super(0,0,32,32) + self.width=digits_max*24+8+self.borderX + self.height=32+self.borderY + colors=getDefaultTextColors(self.windowskin) + @baseColor=colors[0] + @shadowColor=colors[1] + @index=digits_max-1 + self.active=true + refresh + end + + def active=(value) + super + refresh + end + + def number + @number*(@sign && @negative ? -1 : 1) + end + + def sign=(value) + @sign=value + self.width=@digits_max*24+8+self.borderX+(@sign ? 24 : 0) + @index=(@digits_max-1)+(@sign ? 1 : 0) + refresh + end + + def number=(value) + value=0 if !value.is_a?(Numeric) + if @sign + @negative=(value<0) + @number = [value.abs, 10 ** @digits_max - 1].min + else + @number = [[value, 0].max, 10 ** @digits_max - 1].min + end + refresh + end + + def refresh + self.contents=pbDoEnsureBitmap(self.contents, + self.width-self.borderX,self.height-self.borderY) + pbSetSystemFont(self.contents) + self.contents.clear + s=sprintf("%0*d",@digits_max,@number.abs) + x=0 + if @sign + textHelper(0,0,@negative ? "-" : "+",0) + end + for i in 0...@digits_max + index=i+(@sign ? 1 : 0) + textHelper(index*24,0,s[i,1],index) + end + end + + def update + super + digits=@digits_max+(@sign ? 1 : 0) + refresh if @frame%15==0 + if self.active + if Input.repeat?(Input::UP) or Input.repeat?(Input::DOWN) + pbPlayCursorSE() + if @index==0 && @sign + @negative=!@negative + else + place = 10 ** (digits - 1 - @index) + n = @number / place % 10 + @number -= n*place + if Input.repeat?(Input::UP) + n = (n + 1) % 10 + elsif Input.repeat?(Input::DOWN) + n = (n + 9) % 10 + end + @number += n*place + end + refresh + elsif Input.repeat?(Input::RIGHT) + if digits >= 2 + pbPlayCursorSE() + @index = (@index + 1) % digits + @frame=0 + refresh + end + elsif Input.repeat?(Input::LEFT) + if digits >= 2 + pbPlayCursorSE() + @index = (@index + digits - 1) % digits + @frame=0 + refresh + end + end + end + @frame=(@frame+1)%30 + end + + private + + def textHelper(x,y,text,i) + textwidth=self.contents.text_size(text).width + self.contents.font.color=@shadowColor + pbDrawShadow(self.contents,x+(12-textwidth/2),y, textwidth+4, 32, text) + self.contents.font.color=@baseColor + self.contents.draw_text(x+(12-textwidth/2),y, textwidth+4, 32, text) + if @index==i && @active && @frame/15==0 + colors=getDefaultTextColors(self.windowskin) + self.contents.fill_rect(x+(12-textwidth/2),y+30,textwidth,2,colors[0]) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class SpriteWindow_Selectable < SpriteWindow_Base + attr_reader :index + + def initialize(x, y, width, height) + super(x, y, width, height) + @item_max = 1 + @column_max = 1 + @virtualOy=0 + @index = -1 + @row_height = 32 + @column_spacing = 32 + @ignore_input = false + end + + def itemCount + return @item_max || 0 + end + + def index=(index) + if @index!=index + @index = index + priv_update_cursor_rect(true) + end + end + + def rowHeight + return @row_height || 32 + end + + def rowHeight=(value) + if @row_height!=value + oldTopRow=self.top_row + @row_height=[1,value].max + self.top_row=oldTopRow + update_cursor_rect + end + end + + def columns + return @column_max || 1 + end + + def columns=(value) + if @column_max!=value + @column_max=[1,value].max + update_cursor_rect + end + end + + def columnSpacing + return @column_spacing || 32 + end + + def columnSpacing=(value) + if @column_spacing!=value + @column_spacing=[0,value].max + update_cursor_rect + end + end + + def ignore_input=(value) + @ignore_input=value + end + + def count + return @item_max + end + + def row_max + return ((@item_max + @column_max - 1) / @column_max).to_i + end + + def top_row + return (@virtualOy / (@row_height || 32)).to_i + end + + def top_row=(row) + row = row_max-1 if row>row_max-1 + row = 0 if row<0 + @virtualOy = row*@row_height + end + + def top_item + return top_row * @column_max + end + + def page_row_max + return priv_page_row_max.to_i + end + + def page_item_max + return priv_page_item_max.to_i + end + + def itemRect(item) + if item<0 || item>=@item_max || itemself.top_item+self.page_item_max + return Rect.new(0,0,0,0) + else + cursor_width = (self.width-self.borderX-(@column_max-1)*@column_spacing) / @column_max + x = item % @column_max * (cursor_width + @column_spacing) + y = item / @column_max * @row_height - @virtualOy + return Rect.new(x, y, cursor_width, @row_height) + end + end + + def refresh; end + + def update_cursor_rect + priv_update_cursor_rect + end + + def update + super + if self.active and @item_max > 0 and @index >= 0 and !@ignore_input + if Input.repeat?(Input::UP) + if @index >= @column_max or + (Input.trigger?(Input::UP) && (@item_max%@column_max)==0) + oldindex = @index + @index = (@index - @column_max + @item_max) % @item_max + if @index!=oldindex + pbPlayCursorSE() + update_cursor_rect + end + end + elsif Input.repeat?(Input::DOWN) + if @index < @item_max - @column_max or + (Input.trigger?(Input::DOWN) && (@item_max%@column_max)==0) + oldindex = @index + @index = (@index + @column_max) % @item_max + if @index!=oldindex + pbPlayCursorSE() + update_cursor_rect + end + end + elsif Input.repeat?(Input::LEFT) + if @column_max >= 2 and @index > 0 + oldindex = @index + @index -= 1 + if @index!=oldindex + pbPlayCursorSE() + update_cursor_rect + end + end + elsif Input.repeat?(Input::RIGHT) + if @column_max >= 2 and @index < @item_max - 1 + oldindex = @index + @index += 1 + if @index!=oldindex + pbPlayCursorSE() + update_cursor_rect + end + end + elsif Input.repeat?(Input::L) + if @index > 0 + oldindex = @index + @index = [self.index-self.page_item_max, 0].max + if @index!=oldindex + pbPlayCursorSE() + self.top_row -= self.page_row_max + update_cursor_rect + end + end + elsif Input.repeat?(Input::R) + if @index < @item_max-1 + oldindex = @index + @index = [self.index+self.page_item_max, @item_max-1].min + if @index!=oldindex + pbPlayCursorSE() + self.top_row += self.page_row_max + update_cursor_rect + end + end + end + end + end + + private + + def priv_page_row_max + return (self.height - self.borderY) / @row_height + end + + def priv_page_item_max + return (self.height - self.borderY) / @row_height * @column_max + end + + def priv_update_cursor_rect(force=false) + if @index < 0 + self.cursor_rect.empty + self.refresh + return + end + dorefresh = false + row = @index / @column_max + # This code makes lists scroll only when the cursor hits the top and bottom + # of the visible list. +# if row < self.top_row +# self.top_row = row +# dorefresh=true +# end +# if row > self.top_row + (self.page_row_max - 1) +# self.top_row = row - (self.page_row_max - 1) +# dorefresh=true +# end +# if oldindex-self.top_item>=((self.page_item_max - 1)/2) +# self.top_row+=1 +# end +# self.top_row = [self.top_row, self.row_max - self.page_row_max].min + # This code makes the cursor stay in the middle of the visible list as much + # as possible. + new_top_row = row - ((self.page_row_max - 1)/2).floor + new_top_row = [[new_top_row, self.row_max - self.page_row_max].min, 0].max + if self.top_row != new_top_row + self.top_row = new_top_row +# dorefresh = true + end + # End of code + cursor_width = (self.width-self.borderX) / @column_max + x = self.index % @column_max * (cursor_width + @column_spacing) + y = self.index / @column_max * @row_height - @virtualOy + self.cursor_rect.set(x, y, cursor_width, @row_height) + self.refresh if dorefresh || force + end +end + + + +#=============================================================================== +# +#=============================================================================== +module UpDownArrowMixin + def initUpDownArrow + @uparrow = AnimatedSprite.create("Graphics/Pictures/uparrow",8,2,self.viewport) + @downarrow = AnimatedSprite.create("Graphics/Pictures/downarrow",8,2,self.viewport) + @uparrow.z = 99998 + @downarrow.z = 99998 + @uparrow.visible = false + @downarrow.visible = false + @uparrow.play + @downarrow.play + end + + def dispose + @uparrow.dispose + @downarrow.dispose + super + end + + def viewport=(value) + super + @uparrow.viewport = self.viewport + @downarrow.viewport = self.viewport + end + + def color=(value) + super + @uparrow.color = value + @downarrow.color = value + end + + def adjustForZoom(sprite) + sprite.zoom_x = self.zoom_x + sprite.zoom_y = self.zoom_y + sprite.x = sprite.x*self.zoom_x + self.offset_x/self.zoom_x + sprite.y = sprite.y*self.zoom_y + self.offset_y/self.zoom_y + end + + def update + super + @uparrow.x = self.x+(self.width/2)-(@uparrow.framewidth/2) + @downarrow.x = self.x+(self.width/2)-(@downarrow.framewidth/2) + @uparrow.y = self.y + @downarrow.y = self.y+self.height-@downarrow.frameheight + @uparrow.visible = self.visible && self.active && (self.top_item!=0 && + @item_max > self.page_item_max) + @downarrow.visible = self.visible && self.active && + (self.top_item+self.page_item_max<@item_max && @item_max > self.page_item_max) + @uparrow.z = self.z+1 + @downarrow.z = self.z+1 + adjustForZoom(@uparrow) + adjustForZoom(@downarrow) + @uparrow.viewport = self.viewport + @downarrow.viewport = self.viewport + @uparrow.update + @downarrow.update + end +end + + + +#=============================================================================== +# +#=============================================================================== +class SpriteWindow_SelectableEx < SpriteWindow_Selectable + include UpDownArrowMixin + + def initialize(*arg) + super(*arg) + initUpDownArrow + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_DrawableCommand < SpriteWindow_SelectableEx + attr_reader :baseColor + attr_reader :shadowColor + + def initialize(x,y,width,height,viewport=nil) + super(x,y,width,height) + self.viewport = viewport if viewport + if isDarkWindowskin(self.windowskin) + @selarrow = AnimatedBitmap.new("Graphics/Pictures/selarrow_white") + else + @selarrow = AnimatedBitmap.new("Graphics/Pictures/selarrow") + end + @index = 0 + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + refresh + end + + def dispose + @selarrow.dispose + super + end + + def baseColor=(value) + @baseColor = value + refresh + end + + def shadowColor=(value) + @shadowColor = value + refresh + end + + def textWidth(bitmap,text) + return tmpbitmap.text_size(i).width + end + + def getAutoDims(commands,dims,width=nil) + rowMax = ((commands.length + self.columns - 1) / self.columns).to_i + windowheight = (rowMax*self.rowHeight) + windowheight += self.borderY + if !width || width<0 + width=0 + tmpbitmap = BitmapWrapper.new(1,1) + pbSetSystemFont(tmpbitmap) + for i in commands + width = [width,tmpbitmap.text_size(i).width].max + end + # one 16 to allow cursor + width += 16+16+SpriteWindow_Base::TEXTPADDING + tmpbitmap.dispose + end + # Store suggested width and height of window + dims[0] = [self.borderX+1,(width*self.columns)+self.borderX+ + (self.columns-1)*self.columnSpacing].max + dims[1] = [self.borderY+1,windowheight].max + dims[1] = [dims[1],Graphics.height].min + end + + def setSkin(skin) + super(skin) + privRefresh(true) + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + end + + def drawCursor(index,rect) + if self.index==index + pbCopyBitmap(self.contents,@selarrow.bitmap,rect.x,rect.y) + end + return Rect.new(rect.x+16,rect.y,rect.width-16,rect.height) + end + + def itemCount # to be implemented by derived classes + return 0 + end + + def drawItem(index,count,rect) # to be implemented by derived classes + end + + def refresh + @item_max = itemCount() + dwidth = self.width-self.borderX + dheight = self.height-self.borderY + self.contents = pbDoEnsureBitmap(self.contents,dwidth,dheight) + self.contents.clear + for i in 0...@item_max + next if iself.top_item+self.page_item_max + drawItem(i,@item_max,itemRect(i)) + end + end + + def update + oldindex = self.index + super + refresh if self.index!=oldindex + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_CommandPokemon < Window_DrawableCommand + attr_reader :commands + + def initialize(commands,width=nil) + @starting=true + @commands=[] + dims=[] + super(0,0,32,32) + getAutoDims(commands,dims,width) + self.width=dims[0] + self.height=dims[1] + @commands=commands + self.active=true + colors=getDefaultTextColors(self.windowskin) + self.baseColor=colors[0] + self.shadowColor=colors[1] + refresh + @starting=false + end + + def self.newWithSize(commands,x,y,width,height,viewport=nil) + ret=self.new(commands,width) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + return ret + end + + def self.newEmpty(x,y,width,height,viewport=nil) + ret=self.new([],width) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + return ret + end + + def index=(value) + super + refresh if !@starting + end + + def commands=(value) + @commands=value + @item_max=commands.length + self.update_cursor_rect + self.refresh + end + + def width=(value) + super + if !@starting + self.index=self.index + self.update_cursor_rect + end + end + + def height=(value) + super + if !@starting + self.index=self.index + self.update_cursor_rect + end + end + + def resizeToFit(commands,width=nil) + dims=[] + getAutoDims(commands,dims,width) + self.width=dims[0] + self.height=dims[1] + end + + def itemCount + return @commands ? @commands.length : 0 + end + + def drawItem(index,count,rect) + pbSetSystemFont(self.contents) if @starting + rect=drawCursor(index,rect) + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height, + @commands[index],self.baseColor,self.shadowColor) + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_CommandPokemonEx < Window_CommandPokemon +end + + +#=============================================================================== +# +#=============================================================================== +class Window_AdvancedCommandPokemon < Window_DrawableCommand + attr_reader :commands + + def textWidth(bitmap,text) + dims=[nil,0] + chars=getFormattedText(bitmap,0,0, + Graphics.width-self.borderX-SpriteWindow_Base::TEXTPADDING-16, + -1,text,self.rowHeight,true,true) + for ch in chars + dims[0]=dims[0] ? [dims[0],ch[1]].min : ch[1] + dims[1]=[dims[1],ch[1]+ch[3]].max + end + dims[0]=0 if !dims[0] + return dims[1]-dims[0] + end + + def initialize(commands,width=nil) + @starting=true + @commands=[] + dims=[] + super(0,0,32,32) + getAutoDims(commands,dims,width) + self.width=dims[0] + self.height=dims[1] + @commands=commands + self.active=true + colors=getDefaultTextColors(self.windowskin) + self.baseColor=colors[0] + self.shadowColor=colors[1] + refresh + @starting=false + end + + def self.newWithSize(commands,x,y,width,height,viewport=nil) + ret=self.new(commands,width) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + return ret + end + + def self.newEmpty(x,y,width,height,viewport=nil) + ret=self.new([],width) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + return ret + end + + def index=(value) + super + refresh if !@starting + end + + def commands=(value) + @commands=value + @item_max=commands.length + self.update_cursor_rect + self.refresh + end + + def width=(value) + oldvalue=self.width + super + if !@starting && oldvalue!=value + self.index=self.index + self.update_cursor_rect + end + end + + def height=(value) + oldvalue=self.height + super + if !@starting && oldvalue!=value + self.index=self.index + self.update_cursor_rect + end + end + + def resizeToFit(commands,width=nil) + dims=[] + getAutoDims(commands,dims,width) + self.width=dims[0] + self.height=dims[1] + end + + def itemCount + return @commands ? @commands.length : 0 + end + + def drawItem(index,count,rect) + pbSetSystemFont(self.contents) + rect=drawCursor(index,rect) + if toUnformattedText(@commands[index]).gsub(/\n/,"")==@commands[index] + # Use faster alternative for unformatted text without line breaks + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height, + @commands[index],self.baseColor,self.shadowColor) + else + chars=getFormattedText( + self.contents,rect.x,rect.y,rect.width,rect.height, + @commands[index],rect.height,true,true) + drawFormattedChars(self.contents,chars) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_AdvancedCommandPokemonEx < Window_AdvancedCommandPokemon +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/006_SpriteWindow_sprites.rb b/Data/Scripts/008_Objects and windows/006_SpriteWindow_sprites.rb new file mode 100644 index 000000000..56451f7ef --- /dev/null +++ b/Data/Scripts/008_Objects and windows/006_SpriteWindow_sprites.rb @@ -0,0 +1,1085 @@ +module GifLibrary + @@loadlib = Win32API.new("Kernel32.dll","LoadLibrary",'p','') + if safeExists?("gif.dll") + PngDll = @@loadlib.call("gif.dll") + GifToPngFiles = Win32API.new("gif.dll","GifToPngFiles",'pp','l') + GifToPngFilesInMemory = Win32API.new("gif.dll","GifToPngFilesInMemory",'plp','l') + CopyDataString = Win32API.new("gif.dll","CopyDataString",'lpl','l') + FreeDataString = Win32API.new("gif.dll","FreeDataString",'l','') + else + PngDll=nil + end + + def self.getDataFromResult(result) + datasize=CopyDataString.call(result,"",0) + ret=nil + if datasize!=0 + data="0"*datasize + CopyDataString.call(result,data,datasize) + ret=data.unpack("V*") + end + FreeDataString.call(result) + return ret + end +end + + + +class AnimatedBitmap + def initialize(file,hue=0) + if file==nil + raise "Filename is nil (missing graphic)\r\n\r\n"+ + "If you see this error in the Continue/New Game screen, you may be loading another game's save file. "+ + "Check your project's title (\"Game > Change Title...\" in RMXP).\r\n" + end + if file.split(/[\\\/]/)[-1][/^\[\d+(?:,\d+)?]/] # Starts with 1 or more digits in square brackets + @bitmap = PngAnimatedBitmap.new(file,hue) + else + @bitmap = GifBitmap.new(file,hue) + end + end + + def [](index); @bitmap[index]; end + def width; @bitmap.bitmap.width; end + def height; @bitmap.bitmap.height; end + def length; @bitmap.length; end + def each; @bitmap.each { |item| yield item }; end + def bitmap; @bitmap.bitmap; end + def currentIndex; @bitmap.currentIndex; end + def frameDelay; @bitmap.frameDelay; end + def totalFrames; @bitmap.totalFrames; end + def disposed?; @bitmap.disposed?; end + def update; @bitmap.update; end + def dispose; @bitmap.dispose; end + def deanimate; @bitmap.deanimate; end + def copy; @bitmap.copy; end +end + + + +class PngAnimatedBitmap + # Creates an animated bitmap from a PNG file. + def initialize(file,hue=0) + @frames=[] + @currentFrame=0 + @framecount=0 + panorama=BitmapCache.load_bitmap(file,hue) + if file.split(/[\\\/]/)[-1][/^\[(\d+)(?:,(\d+))?]/] # Starts with 1 or more digits in brackets + # File has a frame count + numFrames = $1.to_i + delay = $2.to_i || 10 + raise "Invalid frame count in #{file}" if numFrames<=0 + raise "Invalid frame delay in #{file}" if delay<=0 + if panorama.width % numFrames != 0 + raise "Bitmap's width (#{panorama.width}) is not divisible by frame count: #{file}" + end + @frameDelay = delay + subWidth=panorama.width/numFrames + for i in 0...numFrames + subBitmap=BitmapWrapper.new(subWidth,panorama.height) + subBitmap.blt(0,0,panorama,Rect.new(subWidth*i,0,subWidth,panorama.height)) + @frames.push(subBitmap) + end + panorama.dispose + else + @frames=[panorama] + end + end + + def [](index) + return @frames[index] + end + + def width; self.bitmap.width; end + + def height; self.bitmap.height; end + + def deanimate + for i in 1...@frames.length + @frames[i].dispose + end + @frames=[@frames[0]] + @currentFrame=0 + return @frames[0] + end + + def bitmap + @frames[@currentFrame] + end + + def currentIndex + @currentFrame + end + + def frameDelay(index) + return @frameDelay + end + + def length + @frames.length + end + + def each + @frames.each { |item| yield item} + end + + def totalFrames + @frameDelay*@frames.length + end + + def disposed? + @disposed + end + + def update + return if disposed? + if @frames.length>1 + @framecount+=1 + if @framecount>=@frameDelay + @framecount=0 + @currentFrame+=1 + @currentFrame%=@frames.length + end + end + end + + def dispose + if !@disposed + for i in @frames + i.dispose + end + end + @disposed=true + end + + attr_accessor :frames # internal + + def copy + x=self.clone + x.frames=x.frames.clone + for i in 0...x.frames.length + x.frames[i]=x.frames[i].copy + end + return x + end +end + + + +#internal class +class GifBitmap + # Creates a bitmap from a GIF file with the specified + # optional viewport. Can also load non-animated bitmaps. + def initialize(file,hue=0) + @gifbitmaps=[] + @gifdelays=[] + @totalframes=0 + @framecount=0 + @currentIndex=0 + @disposed=false + bitmap=nil + filestring=nil + filestrName=nil + file="" if !file + file=canonicalize(file) + begin + bitmap=BitmapCache.load_bitmap(file,hue) + rescue + bitmap=nil + end + if !bitmap || (bitmap.width==32 && bitmap.height==32) + if !file || file.length<1 || file[file.length-1]!=0x2F + if (filestring=pbGetFileChar(file)) + filestrName=file + elsif (filestring=pbGetFileChar(file+".gif")) + filestrName=file+".gif" + elsif (filestring=pbGetFileChar(file+".png")) + filestrName=file+".png" + elsif (filestring=pbGetFileChar(file+".jpg")) + filestrName=file+".jpg" + elsif (filestring=pbGetFileChar(file+".bmp")) + filestrName=file+".bmp" + end + end + end + if bitmap && filestring && filestring[0]==0x47 && + bitmap.width==32 && bitmap.height==32 + #File.open("debug.txt","ab") { |f| f.puts("rejecting bitmap") } + bitmap.dispose + bitmap=nil + end + if bitmap + #File.open("debug.txt","ab") { |f| f.puts("reusing bitmap") } + # Have a regular non-animated bitmap + @totalframes=1 + @framecount=0 + @gifbitmaps=[bitmap] + @gifdelays=[1] + else + tmpBase=File.basename(file)+"_tmp_" + filestring=pbGetFileString(filestrName) if filestring + Dir.chdir(ENV["TEMP"]) { # navigate to temp folder since game might be on a CD-ROM + if filestring && filestring[0]==0x47 && GifLibrary::PngDll + result=GifLibrary::GifToPngFilesInMemory.call(filestring, + filestring.length,tmpBase) + else + result=0 + end + if result>0 + @gifdelays=GifLibrary.getDataFromResult(result) + @totalframes=@gifdelays.pop + for i in 0...@gifdelays.length + @gifdelays[i]=[@gifdelays[i],1].max + bmfile=sprintf("%s%d.png",tmpBase,i) + if safeExists?(bmfile) + gifbitmap=BitmapWrapper.new(bmfile) + @gifbitmaps.push(gifbitmap) + bmfile.hue_change(hue) if hue!=0 + if hue==0 && @gifdelays.length==1 + BitmapCache.setKey(file,gifbitmap) + end + File.delete(bmfile) + else + @gifbitmaps.push(BitmapWrapper.new(32,32)) + end + end + end + } + if @gifbitmaps.length==0 + @gifbitmaps=[BitmapWrapper.new(32,32)] + @gifdelays=[1] + end + if @gifbitmaps.length==1 + BitmapCache.setKey(file,@gifbitmaps[0]) + end + end + end + + def [](index) + return @gifbitmaps[index] + end + + def width; self.bitmap.width; end + + def height; self.bitmap.height; end + + def deanimate + for i in 1...@gifbitmaps.length + @gifbitmaps[i].dispose + end + @gifbitmaps=[@gifbitmaps[0]] + @currentIndex=0 + return @gifbitmaps[0] + end + + def bitmap + @gifbitmaps[@currentIndex] + end + + def currentIndex + @currentIndex + end + + def frameDelay(index) + return @gifdelay[index]/2 # Due to frame count being incremented by 2 + end + + def length + @gifbitmaps.length + end + + def each + @gifbitmaps.each { |item| yield item } + end + + def totalFrames + @totalframes/2 # Due to frame count being incremented by 2 + end + + def disposed? + @disposed + end + + def width + @gifbitmaps.length==0 ? 0 : @gifbitmaps[0].width + end + + def height + @gifbitmaps.length==0 ? 0 : @gifbitmaps[0].height + end + + # This function must be called in order to animate the GIF image. + def update + return if disposed? + if @gifbitmaps.length>0 + @framecount+=2 + @framecount=@totalframes<=0 ? 0 : @framecount%@totalframes + frametoshow=0 + for i in 0...@gifdelays.length + frametoshow=i if @gifdelays[i]<=@framecount + end + @currentIndex=frametoshow + end + end + + def dispose + if !@disposed + for i in @gifbitmaps + i.dispose + end + end + @disposed=true + end + + attr_accessor :gifbitmaps # internal + attr_accessor :gifdelays # internal + + def copy + x=self.clone + x.gifbitmaps=x.gifbitmaps.clone + x.gifdelays=x.gifdelays.clone + for i in 0...x.gifbitmaps.length + x.gifbitmaps[i]=x.gifbitmaps[i].copy + end + return x + end +end + + + +def pbGetTileBitmap(filename, tile_id, hue) + return BitmapCache.tileEx(filename, tile_id, hue) { |f| + AnimatedBitmap.new("Graphics/Tilesets/"+filename).deanimate; + } +end + +def pbGetTileset(name,hue=0) + return AnimatedBitmap.new("Graphics/Tilesets/"+name,hue).deanimate +end + +def pbGetAutotile(name,hue=0) + return AnimatedBitmap.new("Graphics/Autotiles/"+name,hue).deanimate +end + +def pbGetAnimation(name,hue=0) + return AnimatedBitmap.new("Graphics/Animations/"+name,hue).deanimate +end + + + +#=============================================================================== +# SpriteWrapper is a class based on Sprite which wraps Sprite's properties. +#=============================================================================== +class SpriteWrapper < Sprite + def initialize(viewport=nil) + @sprite = Sprite.new(viewport) + end + + def dispose; @sprite.dispose; end + def disposed?; return @sprite.disposed?; end + def viewport; return @sprite.viewport; end + def flash(color,duration); return @sprite.flash(color,duration); end + def update; return @sprite.update; end + def x; @sprite.x; end + def x=(value); @sprite.x = value; end + def y; @sprite.y; end + def y=(value); @sprite.y = value; end + def bitmap; @sprite.bitmap; end + def bitmap=(value); @sprite.bitmap = value; end + def src_rect; @sprite.src_rect; end + def src_rect=(value); @sprite.src_rect = value; end + def visible; @sprite.visible; end + def visible=(value); @sprite.visible = value; end + def z; @sprite.z; end + def z=(value); @sprite.z = value; end + def ox; @sprite.ox; end + def ox=(value); @sprite.ox = value; end + def oy; @sprite.oy; end + def oy=(value); @sprite.oy = value; end + def zoom_x; @sprite.zoom_x; end + def zoom_x=(value); @sprite.zoom_x = value; end + def zoom_y; @sprite.zoom_y; end + def zoom_y=(value); @sprite.zoom_y = value; end + def angle; @sprite.angle; end + def angle=(value); @sprite.angle = value; end + def mirror; @sprite.mirror; end + def mirror=(value); @sprite.mirror = value; end + def bush_depth; @sprite.bush_depth; end + def bush_depth=(value); @sprite.bush_depth = value; end + def opacity; @sprite.opacity; end + def opacity=(value); @sprite.opacity = value; end + def blend_type; @sprite.blend_type; end + def blend_type=(value); @sprite.blend_type = value; end + def color; @sprite.color; end + def color=(value); @sprite.color = value; end + def tone; @sprite.tone; end + def tone=(value); @sprite.tone = value; end + + def viewport=(value) + return if self.viewport==value + bitmap = @sprite.bitmap + src_rect = @sprite.src_rect + visible = @sprite.visible + x = @sprite.x + y = @sprite.y + z = @sprite.z + ox = @sprite.ox + oy = @sprite.oy + zoom_x = @sprite.zoom_x + zoom_y = @sprite.zoom_y + angle = @sprite.angle + mirror = @sprite.mirror + bush_depth = @sprite.bush_depth + opacity = @sprite.opacity + blend_type = @sprite.blend_type + color = @sprite.color + tone = @sprite.tone + @sprite.dispose + @sprite = Sprite.new(value) + @sprite.bitmap = bitmap + @sprite.src_rect = src_rect + @sprite.visible = visible + @sprite.x = x + @sprite.y = y + @sprite.z = z + @sprite.ox = ox + @sprite.oy = oy + @sprite.zoom_x = zoom_x + @sprite.zoom_y = zoom_y + @sprite.angle = angle + @sprite.mirror = mirror + @sprite.bush_depth = bush_depth + @sprite.opacity = opacity + @sprite.blend_type = blend_type + @sprite.color = color + @sprite.tone = tone + end +end + + + +#=============================================================================== +# Sprite class that maintains a bitmap of its own. +# This bitmap can't be changed to a different one. +#=============================================================================== +class BitmapSprite < SpriteWrapper + def initialize(width,height,viewport=nil) + super(viewport) + self.bitmap=Bitmap.new(width,height) + @initialized=true + end + + def bitmap=(value) + super(value) if !@initialized + end + + def dispose + self.bitmap.dispose if !self.disposed? + super + end +end + + + +#=============================================================================== +# +#=============================================================================== +class AnimatedSprite < SpriteWrapper + attr_reader :frame + attr_reader :framewidth + attr_reader :frameheight + attr_reader :framecount + attr_reader :animname + + def initializeLong(animname,framecount,framewidth,frameheight,frameskip) + @animname=pbBitmapName(animname) + @realframes=0 + @frameskip=[1,frameskip].max + @frameskip *= Graphics.frame_rate/20 + raise _INTL("Frame width is 0") if framewidth==0 + raise _INTL("Frame height is 0") if frameheight==0 + begin + @animbitmap=AnimatedBitmap.new(animname).deanimate + rescue + @animbitmap=Bitmap.new(framewidth,frameheight) + end + if @animbitmap.width%framewidth!=0 + raise _INTL("Bitmap's width ({1}) is not a multiple of frame width ({2}) [Bitmap={3}]", + @animbitmap.width,framewidth,animname) + end + if @animbitmap.height%frameheight!=0 + raise _INTL("Bitmap's height ({1}) is not a multiple of frame height ({2}) [Bitmap={3}]", + @animbitmap.height,frameheight,animname) + end + @framecount=framecount + @framewidth=framewidth + @frameheight=frameheight + @framesperrow=@animbitmap.width/@framewidth + @playing=false + self.bitmap=@animbitmap + self.src_rect.width=@framewidth + self.src_rect.height=@frameheight + self.frame=0 + end + + # Shorter version of AnimationSprite. All frames are placed on a single row + # of the bitmap, so that the width and height need not be defined beforehand + def initializeShort(animname,framecount,frameskip) + @animname=pbBitmapName(animname) + @realframes=0 + @frameskip=[1,frameskip].max + @frameskip *= Graphics.frame_rate/20 + begin + @animbitmap=AnimatedBitmap.new(animname).deanimate + rescue + @animbitmap=Bitmap.new(framecount*4,32) + end + if @animbitmap.width%framecount!=0 + raise _INTL("Bitmap's width ({1}) is not a multiple of frame count ({2}) [Bitmap={3}]", + @animbitmap.width,framewidth,animname) + end + @framecount=framecount + @framewidth=@animbitmap.width/@framecount + @frameheight=@animbitmap.height + @framesperrow=framecount + @playing=false + self.bitmap=@animbitmap + self.src_rect.width=@framewidth + self.src_rect.height=@frameheight + self.frame=0 + end + + def initialize(*args) + if args.length==1 + super(args[0][3]) + initializeShort(args[0][0],args[0][1],args[0][2]) + else + super(args[5]) + initializeLong(args[0],args[1],args[2],args[3],args[4]) + end + end + + def self.create(animname,framecount,frameskip,viewport=nil) + return self.new([animname,framecount,frameskip,viewport]) + end + + def dispose + return if disposed? + @animbitmap.dispose + @animbitmap=nil + super + end + + def playing? + return @playing + end + + def frame=(value) + @frame=value + @realframes=0 + self.src_rect.x=@frame%@framesperrow*@framewidth + self.src_rect.y=@frame/@framesperrow*@frameheight + end + + def start + @playing=true + @realframes=0 + end + + alias play start + + def stop + @playing=false + end + + def update + super + if @playing + @realframes+=1 + if @realframes==@frameskip + @realframes=0 + self.frame+=1 + self.frame%=self.framecount + end + end + end +end + + + +#=============================================================================== +# Displays an icon bitmap in a sprite. Supports animated images. +#=============================================================================== +class IconSprite < SpriteWrapper + attr_reader :name + + def initialize(*args) + if args.length==0 + super(nil) + self.bitmap=nil + elsif args.length==1 + super(args[0]) + self.bitmap=nil + elsif args.length==2 + super(nil) + self.x=args[0] + self.y=args[1] + else + super(args[2]) + self.x=args[0] + self.y=args[1] + end + @name="" + @_iconbitmap=nil + end + + def dispose + clearBitmaps() + super + end + + # Sets the icon's filename. Alias for setBitmap. + def name=(value) + setBitmap(value) + end + + # Sets the icon's filename. + def setBitmap(file,hue=0) + oldrc=self.src_rect + clearBitmaps() + @name=file + return if file==nil + if file!="" + @_iconbitmap=AnimatedBitmap.new(file,hue) + # for compatibility + self.bitmap=@_iconbitmap ? @_iconbitmap.bitmap : nil + self.src_rect=oldrc + else + @_iconbitmap=nil + end + end + + def clearBitmaps + @_iconbitmap.dispose if @_iconbitmap + @_iconbitmap=nil + self.bitmap=nil if !self.disposed? + end + + def update + super + return if !@_iconbitmap + @_iconbitmap.update + if self.bitmap!=@_iconbitmap.bitmap + oldrc=self.src_rect + self.bitmap=@_iconbitmap.bitmap + self.src_rect=oldrc + end + end +end + + + +#=============================================================================== +# Old GifSprite class, retained for compatibility +#=============================================================================== +class GifSprite < IconSprite + def initialize(path) + super(0,0) + setBitmap(path) + end +end + + + +#=============================================================================== +# SpriteWrapper that stores multiple bitmaps, and displays only one at once. +#=============================================================================== +class ChangelingSprite < SpriteWrapper + def initialize(x=0,y=0,viewport=nil) + super(viewport) + self.x = x + self.y = y + @bitmaps = {} + @currentBitmap = nil + end + + def addBitmap(key,path) + @bitmaps[key].dispose if @bitmaps[key] + @bitmaps[key] = AnimatedBitmap.new(path) + end + + def changeBitmap(key) + @currentBitmap = @bitmaps[key] + self.bitmap = (@currentBitmap) ? @currentBitmap.bitmap : nil + end + + def dispose + return if disposed? + for bm in @bitmaps.values; bm.dispose; end + @bitmaps.clear + super + end + + def update + return if disposed? + for bm in @bitmaps.values; bm.update; end + self.bitmap = (@currentBitmap) ? @currentBitmap.bitmap : nil + end +end + + + +#=============================================================================== +# Displays an icon bitmap in a window. Supports animated images. +#=============================================================================== +class IconWindow < SpriteWindow_Base + attr_reader :name + + def initialize(x,y,width,height,viewport=nil) + super(x,y,width,height) + self.viewport=viewport + self.contents=nil + @name="" + @_iconbitmap=nil + end + + def dispose + clearBitmaps() + super + end + + def update + super + if @_iconbitmap + @_iconbitmap.update + self.contents=@_iconbitmap.bitmap + end + end + + def clearBitmaps + @_iconbitmap.dispose if @_iconbitmap + @_iconbitmap=nil + self.contents=nil if !self.disposed? + end + + # Sets the icon's filename. Alias for setBitmap. + def name=(value) + setBitmap(value) + end + + # Sets the icon's filename. + def setBitmap(file,hue=0) + clearBitmaps() + @name=file + return if file==nil + if file!="" + @_iconbitmap=AnimatedBitmap.new(file,hue) + # for compatibility + self.contents=@_iconbitmap ? @_iconbitmap.bitmap : nil + else + @_iconbitmap=nil + end + end +end + + + +#=============================================================================== +# Displays an icon bitmap in a window. Supports animated images. +# Accepts bitmaps and paths to bitmap files in its constructor. +#=============================================================================== +class PictureWindow < SpriteWindow_Base + def initialize(pathOrBitmap) + super(0,0,32,32) + self.viewport=viewport + self.contents=nil + @_iconbitmap=nil + setBitmap(pathOrBitmap) + end + + def dispose + clearBitmaps() + super + end + + def update + super + if @_iconbitmap + if @_iconbitmap.is_a?(Bitmap) + self.contents=@_iconbitmap + else + @_iconbitmap.update + self.contents=@_iconbitmap.bitmap + end + end + end + + def clearBitmaps + @_iconbitmap.dispose if @_iconbitmap + @_iconbitmap=nil + self.contents=nil if !self.disposed? + end + + # Sets the icon's bitmap or filename. (hue parameter + # is ignored unless pathOrBitmap is a filename) + def setBitmap(pathOrBitmap,hue=0) + clearBitmaps() + if pathOrBitmap!=nil && pathOrBitmap!="" + if pathOrBitmap.is_a?(Bitmap) + @_iconbitmap=pathOrBitmap + self.contents=@_iconbitmap + self.width=@_iconbitmap.width+self.borderX + self.height=@_iconbitmap.height+self.borderY + elsif pathOrBitmap.is_a?(AnimatedBitmap) + @_iconbitmap=pathOrBitmap + self.contents=@_iconbitmap.bitmap + self.width=@_iconbitmap.bitmap.width+self.borderX + self.height=@_iconbitmap.bitmap.height+self.borderY + else + @_iconbitmap=AnimatedBitmap.new(pathOrBitmap,hue) + self.contents=@_iconbitmap ? @_iconbitmap.bitmap : nil + self.width=@_iconbitmap ? @_iconbitmap.bitmap.width+self.borderX : + 32+self.borderX + self.height=@_iconbitmap ? @_iconbitmap.bitmap.height+self.borderY : + 32+self.borderY + end + else + @_iconbitmap=nil + self.width=32+self.borderX + self.height=32+self.borderY + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Plane + def update; end + def refresh; end +end + + + +#=============================================================================== +# This class works around a limitation that planes are always +# 640 by 480 pixels in size regardless of the window's size. +#=============================================================================== +class LargePlane < Plane + attr_accessor :borderX + attr_accessor :borderY + + def initialize(viewport=nil) + @__sprite=Sprite.new(viewport) + @__disposed=false + @__ox=0 + @__oy=0 + @__bitmap=nil + @__visible=true + @__sprite.visible=false + @borderX=0 + @borderY=0 + end + + def disposed? + return @__disposed + end + + def dispose + if !@__disposed + @__sprite.bitmap.dispose if @__sprite.bitmap + @__sprite.dispose + @__sprite=nil + @__bitmap=nil + @__disposed=true + end + super + end + + def ox; @__ox; end + def oy; @__oy; end + + def ox=(value); + return if @__ox==value + @__ox = value + refresh + end + + def oy=(value); + return if @__oy==value + @__oy = value + refresh + end + + def bitmap + return @__bitmap + end + + def bitmap=(value) + if value==nil + if @__bitmap!=nil + @__bitmap=nil + @__sprite.visible=(@__visible && !@__bitmap.nil?) + end + elsif @__bitmap!=value && !value.disposed? + @__bitmap=value + refresh + elsif value.disposed? + if @__bitmap!=nil + @__bitmap=nil + @__sprite.visible=(@__visible && !@__bitmap.nil?) + end + end + end + + def viewport; @__sprite.viewport; end + def zoom_x; @__sprite.zoom_x; end + def zoom_y; @__sprite.zoom_y; end + def opacity; @__sprite.opacity; end + def blend_type; @__sprite.blend_type; end + def visible; @__visible; end + def z; @__sprite.z; end + def color; @__sprite.color; end + def tone; @__sprite.tone; end + + def zoom_x=(v); + return if @__sprite.zoom_x==v + @__sprite.zoom_x = v + refresh + end + + def zoom_y=(v); + return if @__sprite.zoom_y==v + @__sprite.zoom_y = v + refresh + end + + def opacity=(v); @__sprite.opacity=(v); end + def blend_type=(v); @__sprite.blend_type=(v); end + def visible=(v); @__visible=v; @__sprite.visible=(@__visible && !@__bitmap.nil?); end + def z=(v); @__sprite.z=(v); end + def color=(v); @__sprite.color=(v); end + def tone=(v); @__sprite.tone=(v); end + def update; ;end + + def refresh + @__sprite.visible = (@__visible && !@__bitmap.nil?) + if @__bitmap + if !@__bitmap.disposed? + @__ox += @__bitmap.width*@__sprite.zoom_x if @__ox<0 + @__oy += @__bitmap.height*@__sprite.zoom_y if @__oy<0 + @__ox -= @__bitmap.width*@__sprite.zoom_x if @__ox>@__bitmap.width + @__oy -= @__bitmap.height*@__sprite.zoom_y if @__oy>@__bitmap.height + dwidth = (Graphics.width/@__sprite.zoom_x+@borderX).to_i # +2 + dheight = (Graphics.height/@__sprite.zoom_y+@borderY).to_i # +2 + @__sprite.bitmap = ensureBitmap(@__sprite.bitmap,dwidth,dheight) + @__sprite.bitmap.clear + tileBitmap(@__sprite.bitmap,@__bitmap,@__bitmap.rect) + else + @__sprite.visible = false + end + end + end + + private + + def ensureBitmap(bitmap,dwidth,dheight) + if !bitmap || bitmap.disposed? || bitmap.width0; left -= srcbitmap.width; end + while top>0; top -= srcbitmap.height; end + y = top; while y",ret) +end + +def shadowctag(base,shadow) + return sprintf("",colorToRgb16(base),colorToRgb16(shadow)) +end + +def shadowc3tag(base,shadow) + return sprintf("",colorToRgb32(base),colorToRgb32(shadow)) +end + +def shadowctagFromColor(color) + return shadowc3tag(color,getContrastColor(color)) +end + +def shadowctagFromRgb(param) + return shadowctagFromColor(rgbToColor(param)) +end + +def colorToRgb32(color) + return "" if !color + if color.alpha.to_i==255 + return sprintf("%02X%02X%02X",color.red.to_i,color.green.to_i,color.blue.to_i) + else + return sprintf("%02X%02X%02X%02X", + color.red.to_i,color.green.to_i,color.blue.to_i,color.alpha.to_i) + end +end + +def colorToRgb16(color) + ret=(color.red.to_i>>3) + ret|=((color.green.to_i>>3)<<5) + ret|=((color.blue.to_i>>3)<<10) + return sprintf("%04X",ret) +end + +def rgbToColor(param) + return Font.default_color if !param + baseint=param.to_i(16) + if param.length==8 # 32-bit hex + return Color.new( + (baseint>>24)&0xFF, + (baseint>>16)&0xFF, + (baseint>>8)&0xFF, + (baseint)&0xFF + ) + elsif param.length==6 # 24-bit hex + return Color.new( + (baseint>>16)&0xFF, + (baseint>>8)&0xFF, + (baseint)&0xFF + ) + elsif param.length==4 # 16-bit hex + return Color.new( + ((baseint)&0x1F)<<3, + ((baseint>>5)&0x1F)<<3, + ((baseint>>10)&0x1F)<<3 + ) + elsif param.length==1 # Color number + i=param.to_i + return Font.default_color if i>=8 + return [ + Color.new(255, 255, 255, 255), + Color.new(128, 128, 255, 255), + Color.new(255, 128, 128, 255), + Color.new(128, 255, 128, 255), + Color.new(128, 255, 255, 255), + Color.new(255, 128, 255, 255), + Color.new(255, 255, 128, 255), + Color.new(192, 192, 192, 255) + ][i] + else + return Font.default_color + end +end + +def Rgb16ToColor(param) + baseint=param.to_i(16) + return Color.new( + ((baseint)&0x1F)<<3, + ((baseint>>5)&0x1F)<<3, + ((baseint>>10)&0x1F)<<3 + ) +end + +def getContrastColor(color) + raise "No color given" if !color + r=color.red; g=color.green; b=color.blue + yuv=[ + r * 0.299 + g * 0.587 + b * 0.114, + r * -0.1687 + g * -0.3313 + b * 0.500 + 0.5, + r * 0.500 + g * -0.4187 + b * -0.0813 + 0.5 + ] + if yuv[0]<127.5 + yuv[0]+=(255-yuv[0])/2 + else + yuv[0]=yuv[0]/2 + end + return Color.new( + yuv[0] + 1.4075 * (yuv[2] - 0.5), + yuv[0] - 0.3455 * (yuv[1] - 0.5) - 0.7169 * (yuv[2] - 0.5), + yuv[0] + 1.7790 * (yuv[1] - 0.5), + color.alpha + ) +end + + + +#=============================================================================== +# Format text +#=============================================================================== +FORMATREGEXP = /<(\/?)(c|c2|c3|o|fn|br|fs|i|b|r|pg|pog|u|s|icon|img|ac|ar|al|outln|outln2)(\s*\=\s*([^>]*))?>/i + +def fmtescape(text) + if text[/[&<>]/] + text2=text.gsub(/&/,"&") + text2.gsub!(//,">") + return text2 + end + return text +end + +def toUnformattedText(text) + text2=text.gsub(FORMATREGEXP,"") + text2.gsub!(/</,"<") + text2.gsub!(/>/,">") + text2.gsub!(/'/,"'") + text2.gsub!(/"/,"\"") + text2.gsub!(/&/,"&") + return text2 +end + +def unformattedTextLength(text) + return toUnformattedText(text).scan(/./m).length +end + +def itemIconTag(item) + return "" if !item + if item.respond_to?("icon_name") + return sprintf("",item.icon_name) + else + ix=item.icon_index % 16 * 24 + iy=item.icon_index / 16 * 24 + return sprintf("",ix,iy) + end +end + +def getFormattedTextForDims(bitmap,xDst,yDst,widthDst,heightDst,text,lineheight, + newlineBreaks=true,explicitBreaksOnly=false) + text2=text.gsub(/<(\/?)(c|c2|c3|o|u|s)(\s*\=\s*([^>]*))?>/i,"") + if newlineBreaks + text2.gsub!(/<(\/?)(br)(\s*\=\s*([^>]*))?>/i,"\n") + end + return getFormattedText( + bitmap,xDst,yDst,widthDst,heightDst, + text2,lineheight,newlineBreaks, + explicitBreaksOnly,true) +end + +def getFormattedTextFast(bitmap,xDst,yDst,widthDst,heightDst,text,lineheight, + newlineBreaks=true,explicitBreaksOnly=false) + numchars=0 + x=y=0 + characters=[] + charactersInternal=[] + textchunks=[] + controls=[] + charsonline=0 + textchunks.push(text) + text=textchunks.join("") + textchars=text.scan(/./m) + lastword=[0,0] # position of last word + hadspace=false + hadnonspace=false + bold=bitmap.font.bold + italic=bitmap.font.italic + colorclone=bitmap.font.color + defaultfontname=bitmap.font.name + if defaultfontname.is_a?(Array) + defaultfontname=defaultfontname.find { |i| Font.exist?(i) } || "Arial" + elsif !Font.exist?(defaultfontname) + defaultfontname="Arial" + end + defaultfontname=defaultfontname.clone + havenl=false + position=0;while positionwidthDst && lastword[1]!=0 && + (!hadnonspace || !hadspace) + havenl=true + characters.insert(lastword[0],["\n",x,y*lineheight+yDst,0,lineheight, + false,false,false,colorclone,nil,false,false,"",8,position]) + lastword[0]+=1 + y+=1 + x=0 + for i in lastword[0]...characters.length + characters[i][2]+=lineheight + charwidth=characters[i][3]-2 + characters[i][1]=x + x+=charwidth + end + lastword[1]=0 + end + position+=1 + end + # This code looks at whether the text occupies exactly two lines when + # displayed. If it does, it balances the length of each line. +=begin + # Count total number of lines + numlines = (x==0 && y>0) ? y-1 : y + realtext = (newlineBreaks) ? text : text.gsub(/\n/," ") + if numlines==2 && !explicitBreaksOnly && !realtext[/\n/] && realtext.length>=50 + # Set half to middle of text (known to contain no formatting) + half = realtext.length/2 + leftSearch = 0 + rightSearch = 0 + # Search left for a space + i = half; while i>=0 + break if realtext[i,1][/\s/]||isWaitChar(realtext[i]) # found a space + leftSearch += 1 + i -= 1 + end + # Search right for a space + i = half; while i=0 + for j in firstspace...i + characters[j]=nil + end + firstspace=-1 + elsif characters[i][0][/[ \r\t]/] + if firstspace<0 + firstspace=i + end + else + firstspace=-1 + end + end + if firstspace>0 + for j in firstspace...characters.length + characters[j]=nil + end + end + characters.compact! + end + for i in 0...characters.length + characters[i][1]=xDst+characters[i][1] + end + # Remove all characters with Y greater or equal to _yDst_+_heightDst_ + if heightDst>=0 + for i in 0...characters.length + if characters[i][2]>=yDst+heightDst + characters[i]=nil + end + end + characters.compact! + end + return characters +end + +def isWaitChar(x) + return (x=="\001" || x=="\002") +end + +def getLastParam(array,default) + i=array.length-1 + while i>=0 + return array[i] if array[i] + i-=1 + end + return default +end + +def getLastColors(colorstack,opacitystack,defaultcolors) + colors=getLastParam(colorstack,defaultcolors) + opacity=getLastParam(opacitystack,255) + if opacity!=255 + colors=[Color.new(colors[0].red,colors[0].green,colors[0].blue, + colors[0].alpha*opacity/255), + colors[1] ? Color.new(colors[1].red,colors[1].green,colors[1].blue, + colors[1].alpha*opacity/255) : nil] + end + return colors +end + + + +#=============================================================================== +# Formats a string of text and returns an array containing a list of formatted +# characters. +#=============================================================================== +=begin +Parameters: +bitmap: Source bitmap. Will be used to determine the default font of + the text. +xDst: X coordinate of the text's top left corner. +yDst: Y coordinate of the text's top left corner. +widthDst: Width of the text. Used to determine line breaks. +heightDst: Height of the text. If -1, there is no height restriction. If + 1 or greater, any characters exceeding the height are removed + from the returned list. +newLineBreaks: If true, newline characters will be treated as line breaks. The + default is true. + +Return Values: +A list of formatted characters. Returns an empty array if _bitmap_ is nil +or disposed, or if _widthDst_ is 0 or less or _heightDst_ is 0. + +Formatting Specification: +This function uses the following syntax when formatting the text. + ... - Formats the text in bold. + ... - Formats the text in italics. + ... - Underlines the text. + ... - Draws a strikeout line over the text. + ... - Left-aligns the text. Causes line breaks before and after + the text. + - Right-aligns the text until the next line break. + ... - Right-aligns the text. Causes line breaks before and after + the text. + ... - Centers the text. Causes line breaks before and after the + text. +
- Causes a line break. + ... - Color specification. A total of four formats are supported: + RRGGBBAA, RRGGBB, 16-bit RGB, and Window_Base color numbers. + ... - Color specification where the first half is the base color + and the second half is the shadow color. 16-bit RGB is + supported. +Added 2009-10-20 + ... - Color specification where B is the base color and S is the + shadow color. B and/or S can be omitted. A total of four + formats are supported: + RRGGBBAA, RRGGBB, 16-bit RGB, and Window_Base color numbers. +Added 2009-9-12 + - Displays the text in the given opacity (0-255) +Added 2009-10-19 + - Displays the text in outline format. +Added 2010-05-12 + - Displays the text in outline format (outlines more + exaggerated. + ... - Formats the text in the specified font, or Arial if the + font doesn't exist. + ... - Changes the font size to X. + - Displays the icon X (in Graphics/Icons/). + +In addition, the syntax supports the following: +' - Converted to "'". +< - Converted to "<". +> - Converted to ">". +& - Converted to "&". +" - Converted to double quotation mark. + +To draw the characters, pass the returned array to the +_drawFormattedChars_ function. +=end + +def getFormattedText(bitmap,xDst,yDst,widthDst,heightDst,text,lineheight=32, + newlineBreaks=true,explicitBreaksOnly=false, + collapseAlignments=false) + dummybitmap=nil + if !bitmap || bitmap.disposed? # allows function to be called with nil bitmap + dummybitmap=Bitmap.new(1,1) + bitmap=dummybitmap + return + end + if !bitmap || bitmap.disposed? || widthDst<=0 || heightDst==0 || text.length==0 + return [] + end + textchunks=[] + controls=[] + oldtext=text + while text[FORMATREGEXP] + textchunks.push($~.pre_match) + if $~[3] + controls.push([$~[2].downcase,$~[4],-1,$~[1]=="/" ? true : false]) + else + controls.push([$~[2].downcase,"",-1,$~[1]=="/" ? true : false]) + end + text=$~.post_match + end + if controls.length==0 + ret=getFormattedTextFast(bitmap,xDst,yDst,widthDst,heightDst,text,lineheight, + newlineBreaks,explicitBreaksOnly) + dummybitmap.dispose if dummybitmap + return ret + end + numchars=0 + x=y=0 + characters=[] + charactersInternal=[] + charsonline=0 + realtext=nil + realtextStart="" + if !explicitBreaksOnly && textchunks.join("").length==0 + # All commands occurred at the beginning of the text string + realtext=(newlineBreaks) ? text : text.gsub(/\n/," ") + realtextStart=oldtext[0,oldtext.length-realtext.length] + realtextHalf=text.length/2 + end + textchunks.push(text) + for chunk in textchunks + chunk.gsub!(/</,"<") + chunk.gsub!(/>/,">") + chunk.gsub!(/'/,"'") + chunk.gsub!(/"/,"\"") + chunk.gsub!(/&/,"&") + end + textlen=0 + for i in 0...controls.length + textlen+=textchunks[i].scan(/./m).length + controls[i][2]=textlen + end + text=textchunks.join("") + textchars=text.scan(/./m) + colorstack=[] + boldcount=0 + italiccount=0 + outlinecount=0 + underlinecount=0 + strikecount=0 + rightalign=0 + outline2count=0 + opacitystack=[] + oldfont=bitmap.font.clone + defaultfontname=bitmap.font.name + defaultfontsize=bitmap.font.size + fontsize=defaultfontsize + fontnamestack=[] + fontsizestack=[] + defaultcolors=[oldfont.color.clone,nil] + if defaultfontname.is_a?(Array) + defaultfontname=defaultfontname.find { |i| Font.exist?(i) } || "Arial" + elsif !Font.exist?(defaultfontname) + defaultfontname="Arial" + end + defaultfontname=defaultfontname.clone + fontname=defaultfontname + alignstack=[] + lastword=[0,0] # position of last word + hadspace=false + hadnonspace=false + havenl=false + position=0; while position0 && nextline==0 + else + alignstack.pop + nextline=1 if x>0 && nextline==0 + end + elsif control=="al" # Left align + if !endtag + alignstack.push(0) + nextline=1 if x>0 && nextline==0 + else + alignstack.pop; + nextline=1 if x>0 && nextline==0 + end + elsif control=="ac" # Center align + if !endtag + alignstack.push(2) + nextline=1 if x>0 && nextline==0 + else + alignstack.pop; + nextline=1 if x>0 && nextline==0 + end + elsif control=="icon" # Icon + if !endtag + param=param.sub(/\s+$/,"") + graphic="Graphics/Icons/#{param}" + controls[i]=nil + break + end + elsif control=="img" # Icon + if !endtag + param=param.sub(/\s+$/,"") + param=param.split("|") + graphic=param[0] + if param.length>1 + graphicX=param[1].to_i + graphicY=param[2].to_i + graphicWidth=param[3].to_i + graphicHeight=param[4].to_i + end + controls[i]=nil + break + end + elsif control=="br" # Line break + if !endtag + nextline+=1 + end + elsif control=="r" # Right align this line + if !endtag + x=0 + rightalign=1; lastword=[characters.length,x] + end + end + controls[i]=nil + end + end + bitmap.font.bold=(boldcount>0) + bitmap.font.italic=(italiccount>0) + if graphic + if !graphicWidth + tempgraphic=Bitmap.new(graphic) + graphicWidth=tempgraphic.width + graphicHeight=tempgraphic.height + tempgraphic.dispose + end + width=graphicWidth # +8 # No padding + xStart=0 # 4 + yStart=[(lineheight/2)-(graphicHeight/2),0].max + graphicRect=Rect.new(graphicX,graphicY,graphicWidth,graphicHeight) + else + yStart=0 + xStart=0 + width=isWaitChar(textchars[position]) ? 0 : bitmap.text_size(textchars[position]).width + width+=2 if width>0 && outline2count>0 + end + if rightalign==1 && nextline==0 + alignment=1 + else + alignment=getLastParam(alignstack,0) + end + nextline.times do + havenl=true + characters.push(["\n",x,y*lineheight+yDst,0,lineheight,false,false,false, + defaultcolors[0],defaultcolors[1],false,false,"",8,position,nil,0]) + charactersInternal.push([alignment,y,0]) + y+=1; + x=0; + rightalign=0; + lastword=[characters.length,x] + hadspace=false + hadnonspace=false + end + if textchars[position]=="\n" + if newlineBreaks + if nextline==0 + havenl=true + characters.push(["\n",x,y*lineheight+yDst,0,lineheight,false,false,false, + defaultcolors[0],defaultcolors[1],false,false,"",8,position,nil,0]) + charactersInternal.push([alignment,y,0]) + y+=1 + x=0 + end + rightalign=0 + hadspace=true + hadnonspace=false + position+=1 + next + else + textchars[position]=" " + if !graphic + width=bitmap.text_size(textchars[position]).width + width+=2 if width>0 && outline2count>0 + end + end + end + isspace=(textchars[position][/\s/] || isWaitChar(textchars[position])) ? true : false + if hadspace && !isspace + # set last word to here + lastword[0]=characters.length + lastword[1]=x + hadspace=false + hadnonspace=true + elsif isspace + hadspace=true + end + textx=x+xStart # independent of xDst + texty=(lineheight*y)+yDst+yStart + colors=getLastColors(colorstack,opacitystack,defaultcolors) + oldx=x + # Push character, textx will be calculated later + if heightDst<0 || texty0) ? 2+(width/2) : 2 + characters.push([ + graphic ? graphic : textchars[position], + x+xStart,texty,width+extraspace,lineheight, + graphic ? true : false, + (boldcount>0),(italiccount>0),colors[0],colors[1], + (underlinecount>0),(strikecount>0),fontname,fontsize, + position,graphicRect, + ((outlinecount>0) ? 1 : 0)+((outline2count>0) ? 2 : 0) + ]) + charactersInternal.push([alignment,y,xStart,textchars[position],extraspace]) + end + x+=width + if !explicitBreaksOnly && x+2>widthDst && lastword[1]!=0 && + (!hadnonspace || !hadspace) + havenl=true + characters.insert(lastword[0],["\n",x,y*lineheight+yDst,0,lineheight,false, + false,false,defaultcolors[0],defaultcolors[1],false,false,"",8,position, + nil]) + charactersInternal.insert(lastword[0],[alignment,y,0]) + lastword[0]+=1 + y+=1 + x=0 + for i in lastword[0]...characters.length + characters[i][2]+=lineheight + charactersInternal[i][1]+=1 + extraspace=(charactersInternal[i][4]) ? charactersInternal[i][4] : 0 + charwidth=characters[i][3]-extraspace + characters[i][1]=x+charactersInternal[i][2] + x+=charwidth + end + lastword[1]=0 + end + position+=1 if !graphic + end + # This code looks at whether the text occupies exactly two lines when + # displayed. If it does, it balances the length of each line. +=begin + # Count total number of lines + numlines = (x==0 && y>0) ? y : y+1 + if numlines==2 && realtext && !realtext[/\n/] && realtext.length>=50 + # Set half to middle of text (known to contain no formatting) + half = realtext.length/2 + leftSearch = 0 + rightSearch = 0 + # Search left for a space + i = half; while i>=0 + break if realtext[i,1][/\s/]||isWaitChar(realtext[i,1]) # found a space + leftSearch += 1 + i -= 1 + end + # Search right for a space + i = half; while i=0 + for j in firstspace...i + characters[j]=nil + charactersInternal[j]=nil + end + firstspace=-1 + elsif characters[i][0][/[ \r\t]/] + if firstspace<0 + firstspace=i + end + else + firstspace=-1 + end + end + if firstspace>0 + for j in firstspace...characters.length + characters[j]=nil + charactersInternal[j]=nil + end + end + characters.compact! + charactersInternal.compact! + end + # Calculate Xs based on alignment + # First, find all text runs with the same alignment on the same line + totalwidth=0 + widthblocks=[] + lastalign=0 + lasty=0 + runstart=0 + for i in 0...characters.length + c=characters[i] + if i>0 && (charactersInternal[i][0]!=lastalign || + charactersInternal[i][1]!=lasty) + # Found end of run + widthblocks.push([runstart,i,lastalign,totalwidth,lasty]) + runstart=i + totalwidth=0 + end + lastalign=charactersInternal[i][0] + lasty=charactersInternal[i][1] + extraspace=(charactersInternal[i][4]) ? charactersInternal[i][4] : 0 + totalwidth+=c[3]-extraspace + end + widthblocks.push([runstart,characters.length,lastalign,totalwidth,lasty]) + if collapseAlignments + # Calculate the total width of each line + totalLineWidths=[] + for block in widthblocks + y=block[4] + if !totalLineWidths[y] + totalLineWidths[y]=0 + end + if totalLineWidths[y]!=0 + # padding in case more than one line has different alignments + totalLineWidths[y]+=16 + end + totalLineWidths[y]+=block[3] + end + # Calculate a new width for the next step + widthDst=[widthDst,(totalLineWidths.compact.max || 0)].min + end + # Now, based on the text runs found, recalculate Xs + for block in widthblocks + next if block[0]>=block[1] + for i in block[0]...block[1] + case block[2] + when 1; characters[i][1]=xDst+(widthDst-block[3]-4)+characters[i][1] + when 2; characters[i][1]=xDst+((widthDst/2)-(block[3]/2))+characters[i][1] + else; characters[i][1]=xDst+characters[i][1] + end + end + end + # Remove all characters with Y greater or equal to _yDst_+_heightDst_ + if heightDst>=0 + for i in 0...characters.length + if characters[i][2]>=yDst+heightDst + characters[i]=nil + end + end + characters.compact! + end + bitmap.font=oldfont + dummybitmap.dispose if dummybitmap + return characters +end + + + +#=============================================================================== +# Draw text and images on a bitmap +#=============================================================================== +def getLineBrokenChunks(bitmap,value,width,dims,plain=false) + x=0 + y=0 + textheight=0 + ret=[] + if dims + dims[0]=0 + dims[1]=0 + end + re=/]+)>/ + reNoMatch=/]+>/ + return ret if !bitmap || bitmap.disposed? || width<=0 + textmsg=value.clone + lines=0 + color=Font.default_color + while (c = textmsg.slice!(/\n|[^ \r\t\f\n\-]*\-+|(\S*([ \r\t\f]?))/)) != nil + break if c=="" + ccheck=c + if ccheck=="\n" + x=0 +# y+=(textheight==0) ? bitmap.text_size("X").height : textheight + y+=(textheight==0) ? bitmap.text_size("X").height+1 : textheight + textheight=0 + next + end + if ccheck[/0 && x+textwidth>width + minTextSize=bitmap.text_size(word.gsub(/\s*/,"")) + if x>0 && x+minTextSize.width>width + x=0 +# y+=32 # (textheight==0) ? bitmap.text_size("X").height : textheight + y+=(textheight==0) ? bitmap.text_size("X").height+1 : textheight + textheight=0 + end + end +# textheight=32 # [textheight,textSize.height].max + textheight=[textheight,textSize.height+1].max + ret.push([word,x,y,textwidth,textheight,color]) + x+=textwidth + dims[0]=x if dims && dims[0]"+text + chars=getFormattedText(bitmap,x,y,width,-1,text,lineheight) + drawFormattedChars(bitmap,chars) +end + +# Deprecated -- not to be used in new code +def coloredToFormattedText(text,baseColor=nil,shadowColor=nil) + base=!baseColor ? Color.new(12*8,12*8,12*8) : baseColor.clone + shadow=!shadowColor ? Color.new(26*8,26*8,25*8) : shadowColor.clone + text2=text.gsub(/&/,"&") + text2.gsub!(//,">") + text2.gsub!(/\\\[([A-Fa-f0-9]{8,8})\]/) { "" } + text2=""+text2 + text2.gsub!(/\\\\/,"\\") + return text2 +end + +# Deprecated -- not to be used in new code +def drawColoredTextEx(bitmap,x,y,width,text,baseColor=nil,shadowColor=nil) + chars=getFormattedText(bitmap,x,y,width,-1,coloredToFormattedText(text),32) + drawFormattedChars(bitmap,chars) +end + +def pbDrawShadow(bitmap,x,y,width,height,string) + return if !bitmap || !string + pbDrawShadowText(bitmap,x,y,width,height,string,nil,bitmap.font.color) +end + +def pbDrawShadowText(bitmap,x,y,width,height,string,baseColor,shadowColor=nil,align=0) + return if !bitmap || !string + width=(width<0) ? bitmap.text_size(string).width+4 : width + height=(height<0) ? bitmap.text_size(string).height+4 : height + if shadowColor && shadowColor.alpha>0 + bitmap.font.color=shadowColor + bitmap.draw_text(x+2,y,width,height,string,align) + bitmap.draw_text(x,y+2,width,height,string,align) + bitmap.draw_text(x+2,y+2,width,height,string,align) + end + if baseColor && baseColor.alpha>0 + bitmap.font.color=baseColor + bitmap.draw_text(x,y,width,height,string,align) + end +end + +def pbDrawOutlineText(bitmap,x,y,width,height,string,baseColor,shadowColor=nil,align=0) + return if !bitmap || !string + width=(width<0) ? bitmap.text_size(string).width+4 : width + height=(height<0) ? bitmap.text_size(string).height+4 : height + if shadowColor && shadowColor.alpha>0 + bitmap.font.color=shadowColor + bitmap.draw_text(x-2,y-2,width,height,string,align) + bitmap.draw_text(x,y-2,width,height,string,align) + bitmap.draw_text(x+2,y-2,width,height,string,align) + bitmap.draw_text(x-2,y,width,height,string,align) + bitmap.draw_text(x+2,y,width,height,string,align) + bitmap.draw_text(x-2,y+2,width,height,string,align) + bitmap.draw_text(x,y+2,width,height,string,align) + bitmap.draw_text(x+2,y+2,width,height,string,align) + end + if baseColor && baseColor.alpha>0 + bitmap.font.color=baseColor + bitmap.draw_text(x,y,width,height,string,align) + end +end + +# Draws text on a bitmap. _textpos_ is an array of text commands. Each text +# command is an array that contains the following: +# 0 - Text to draw +# 1 - X coordinate +# 2 - Y coordinate +# 3 - If true or 1, the text is right aligned. If 2, the text is centered. +# Otherwise, the text is left aligned. +# 4 - Base color +# 5 - Shadow color +# 6 - If true or 1, the text has an outline. Otherwise, the text has a shadow. +def pbDrawTextPositions(bitmap,textpos) + for i in textpos + textsize = bitmap.text_size(i[0]) + x = i[1] + y = i[2] + if i[3]==true || i[3]==1 # right align + x -= textsize.width + elsif i[3]==2 # centered + x -= (textsize.width/2) + end + if i[6]==true || i[6]==1 # outline text + pbDrawOutlineText(bitmap,x,y,textsize.width,textsize.height,i[0],i[4],i[5]) + else + pbDrawShadowText(bitmap,x,y,textsize.width,textsize.height,i[0],i[4],i[5]) + end + end +end + + + +#=============================================================================== +# Draw images on a bitmap +#=============================================================================== +def pbCopyBitmap(dstbm,srcbm,x,y,opacity=255) + rc = Rect.new(0,0,srcbm.width,srcbm.height) + dstbm.blt(x,y,srcbm,rc,opacity) +end + +def pbDrawImagePositions(bitmap,textpos) + for i in textpos + srcbitmap=AnimatedBitmap.new(pbBitmapName(i[0])) + x=i[1] + y=i[2] + srcx=i[3] || 0 + srcy=i[4] || 0 + width=(i[5] && i[5]>=0) ? i[5] : srcbitmap.width + height=(i[6] && i[6]>=0) ? i[6] : srcbitmap.height + srcrect=Rect.new(srcx,srcy,width,height) + bitmap.blt(x,y,srcbitmap.bitmap,srcrect) + srcbitmap.dispose + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/008_Messages.rb b/Data/Scripts/008_Objects and windows/008_Messages.rb new file mode 100644 index 000000000..2b4a19379 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/008_Messages.rb @@ -0,0 +1,1431 @@ +#=============================================================================== +# Message variables +#=============================================================================== +class Game_Temp + attr_accessor :background + attr_writer :message_window_showing + attr_writer :player_transferring + attr_writer :transition_processing + + def message_window_showing + return @message_window_showing || false + end + + def player_transferring + return @player_transferring || false + end + + def transition_processing + return @transition_processing || false + end +end + + + +class Game_Message + attr_writer :background + attr_writer :visible + + def visible + return @visible || false + end + + def background + return @background || 0 + end +end + + + +class Game_System + attr_writer :message_position + + def message_position + return @message_position || 2 + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Scene_Map + def updatemini + oldmws=$game_temp.message_window_showing + oldvis=$game_message ? $game_message.visible : false + $game_temp.message_window_showing=true + $game_message.visible=true if $game_message + loop do + $game_map.update + $game_player.update + $game_system.update + if $game_screen + $game_screen.update + else + $game_map.screen.update + end + break unless $game_temp.player_transferring + transfer_player + break if $game_temp.transition_processing + end + $game_temp.message_window_showing=oldmws + $game_message.visible=oldvis if $game_message + @spriteset.update if @spriteset + @message_window.update if @message_window + end +end + + + +class Scene_Battle + def updatemini + if self.respond_to?("update_basic") + update_basic(true) + update_info_viewport # Update information viewport + if $game_message && $game_message.visible + @info_viewport.visible = false + @message_window.visible = true + end + else + oldmws=$game_temp.message_window_showing + $game_temp.message_window_showing=true + # Update system (timer) and screen + $game_system.update + if $game_screen + $game_screen.update + else + $game_map.screen.update + end + # If timer has reached 0 + if $game_system.timer_working and $game_system.timer == 0 + # Abort battle + $game_temp.battle_abort = true + end + # Update windows + @help_window.update if @help_window + @party_command_window.update if @party_command_window + @actor_command_window.update if @actor_command_window + @status_window.update if @status_window + $game_temp.message_window_showing=oldmws + @message_window.update if @message_window + # Update sprite set + @spriteset.update if @spriteset + end + end +end + + + +def pbMapInterpreter + if $game_map.respond_to?("interpreter") + return $game_map.interpreter + elsif $game_system + return $game_system.map_interpreter + end + return nil +end + +def pbMapInterpreterRunning? + interp = pbMapInterpreter + return interp && interp.running? +end + +def pbRefreshSceneMap + if $scene && $scene.is_a?(Scene_Map) + if $scene.respond_to?("miniupdate") + $scene.miniupdate + else + $scene.updatemini + end + elsif $scene && $scene.is_a?(Scene_Battle) + $scene.updatemini + end +end + +def pbUpdateSceneMap + if $scene && $scene.is_a?(Scene_Map) && !pbIsFaded? + if $scene.respond_to?("miniupdate") + $scene.miniupdate + else + $scene.updatemini + end + elsif $scene && $scene.is_a?(Scene_Battle) + $scene.updatemini + end +end + + + +#=============================================================================== +# +#=============================================================================== +def pbEventCommentInput(*args) + parameters = [] + list = *args[0].list # Event or event page + elements = *args[1] # Number of elements + trigger = *args[2] # Trigger + return nil if list == nil + return nil unless list.is_a?(Array) + for item in list + next unless item.code == 108 || item.code == 408 + if item.parameters[0] == trigger + start = list.index(item) + 1 + finish = start + elements + for id in start...finish + next if !list[id] + parameters.push(list[id].parameters[0]) + end + return parameters + end + end + return nil +end + +def pbCurrentEventCommentInput(elements,trigger) + return nil if !pbMapInterpreterRunning? + event = pbMapInterpreter.get_character(0) + return nil if !event + return pbEventCommentInput(event,elements,trigger) +end + +def pbButtonInputProcessing(variableNumber=0,timeoutFrames=0) + ret=0 + timeoutFrames = timeoutFrames*Graphics.frame_rate/20 + loop do + Graphics.update + Input.update + pbUpdateSceneMap + for i in 1..18 + ret=i if Input.trigger?(i) + end + break if ret!=0 + if timeoutFrames>0 + i+=1 + break if i>=timeoutFrames + end + end + Input.update + if variableNumber && variableNumber>0 + $game_variables[variableNumber]=ret + $game_map.need_refresh = true if $game_map + end + return ret +end + + + +#=============================================================================== +# Interpreter functions for displaying messages +#=============================================================================== +module InterpreterMixin + # Freezes all events on the map (for use at the beginning of common events) + def pbGlobalLock + for event in $game_map.events.values + event.minilock + end + end + + # Unfreezes all events on the map (for use at the end of common events) + def pbGlobalUnlock + for event in $game_map.events.values + event.unlock + end + end + + def pbRepeatAbove(index) + index=@list[index].indent + loop do + index-=1 + return index+1 if @list[index].indent==indent + end + end + + def pbBreakLoop(index) + indent = @list[index].indent + temp_index=index + # Copy index to temporary variables + loop do + # Advance index + temp_index += 1 + # If a fitting loop was not found + return index+1 if temp_index >= @list.size-1 + return temp_index+1 if @list[temp_index].code == 413 and + @list[temp_index].indent < indent + end + end + + def pbJumpToLabel(index,label_name) + temp_index = 0 + loop do + return index+1 if temp_index >= @list.size-1 + return temp_index+1 if @list[temp_index].code == 118 and + @list[temp_index].parameters[0] == label_name + temp_index += 1 + end + end + + # Gets the next index in the interpreter, ignoring + # certain events between messages + def pbNextIndex(index) + return -1 if !@list || @list.length==0 + i=index+1 + loop do + return i if i>=@list.length-1 + case @list[i].code + when 118, 108, 408 # Label, Comment + i+=1 + when 413 # Repeat Above + i=pbRepeatAbove(i) + when 113 # Break Loop + i=pbBreakLoop(i) + when 119 # Jump to Label + newI=pbJumpToLabel(i,@list[i].parameters[0]) + i = (newI>i) ? newI : i+1 + else + return i + end + end + end + + # Helper function that shows a picture in a script. To be used in + # a script event command. + def pbShowPicture(number,name,origin,x,y,zoomX=100,zoomY=100,opacity=255,blendType=0) + number = number + ($game_temp.in_battle ? 50 : 0) + $game_screen.pictures[number].show(name,origin, + x, y, zoomX,zoomY,opacity,blendType) + end + + # Erases an event and adds it to the list of erased events so that + # it can stay erased when the game is saved then loaded again. To be used in + # a script event command. + def pbEraseThisEvent + if $game_map.events[@event_id] + $game_map.events[@event_id].erase + $PokemonMap.addErasedEvent(@event_id) if $PokemonMap + end + @index+=1 + return true + end + + # Runs a common event. To be used in a script event command. + def pbCommonEvent(id) + common_event = $data_common_events[id] + if $game_temp.in_battle + if common_event != nil + interp = Interpreter.new + interp.setup(common_event.list,0) + begin + Graphics.update + Input.update + interp.update + pbUpdateSceneMap + end while interp.running? + end + else + $game_system.battle_interpreter.setup(common_event.list, 0) + end + end + + # Sets another event's self switch (eg. pbSetSelfSwitch(20,"A",true) ). + # To be used in a script event command. + def pbSetSelfSwitch(event,swtch,value,mapid=-1) + mapid = @map_id if mapid<0 + oldValue = $game_self_switches[[mapid,event,swtch]] + $game_self_switches[[mapid,event,swtch]] = value + if value!=oldValue && $MapFactory.hasMap?(mapid) + $MapFactory.getMap(mapid,false).need_refresh = true + end + end + + # Must use this approach to share the methods because the methods already + # defined in a class override those defined in an included module + CustomEventCommands=<<_END_ + + def command_242 + pbBGMFade(pbParams[0]) + return true + end + + def command_246 + pbBGSFade(pbParams[0]) + return true + end + + def command_251 + pbSEStop + return true + end + + def command_241 + pbBGMPlay(pbParams[0]) + return true + end + + def command_245 + pbBGSPlay(pbParams[0]) + return true + end + + def command_249 + pbMEPlay(pbParams[0]) + return true + end + + def command_250 + pbSEPlay(pbParams[0]) + return true + end +_END_ +end + + + +class Game_Interpreter # Used by RMVX + include InterpreterMixin + eval(InterpreterMixin::CustomEventCommands) + @@immediateDisplayAfterWait=false + @buttonInput=false + + def pbParams + return @params + end + + def command_105 + return false if @buttonInput + @buttonInput=true + pbButtonInputProcessing(@list[@index].parameters[0]) + @buttonInput=false + @index+=1 + return true + end + + def command_101 + if $game_temp.message_window_showing + return false + end + $game_message=Game_Message.new if !$game_message + message="" + commands=nil + numInputVar=nil + numInputDigitsMax=nil + text="" + facename=@list[@index].parameters[0] + faceindex=@list[@index].parameters[1] + if facename && facename!="" + text+="\\ff[#{facename},#{faceindex}]" + end + if $game_message + $game_message.background=@list[@index].parameters[2] + end + $game_system.message_position=@list[@index].parameters[3] + message+=text + messageend="" + loop do + nextIndex=pbNextIndex(@index) + code=@list[nextIndex].code + if code == 401 + text=@list[nextIndex].parameters[0] + text+=" " if text!="" && text[text.length-1,1]!=" " + message+=text + @index=nextIndex + else + if code == 102 + commands=@list[nextIndex].parameters + @index=nextIndex + elsif code == 106 && @@immediateDisplayAfterWait + params=@list[nextIndex].parameters + if params[0]<=10 + nextcode=@list[nextIndex+1].code + if nextcode==101||nextcode==102||nextcode==103 + @index=nextIndex + else + break + end + else + break + end + elsif code == 103 + numInputVar=@list[nextIndex].parameters[0] + numInputDigitsMax=@list[nextIndex].parameters[1] + @index=nextIndex + elsif code == 101 + messageend="\1" + end + break + end + end + message=_MAPINTL($game_map.map_id,message) + @message_waiting=true + if commands + cmdlist=[] + for cmd in commands[0] + cmdlist.push(_MAPINTL($game_map.map_id,cmd)) + end + command=pbMessage(message+messageend,cmdlist,commands[1]) + @branch[@list[@index].indent] = command + elsif numInputVar + params=ChooseNumberParams.new + params.setMaxDigits(numInputDigitsMax) + params.setDefaultValue($game_variables[numInputVar]) + $game_variables[numInputVar]=pbMessageChooseNumber(message+messageend,params) + $game_map.need_refresh = true if $game_map + else + pbMessage(message+messageend) + end + @message_waiting=false + return true + end + + def command_102 + @message_waiting=true + command=pbShowCommands(nil,@list[@index].parameters[0],@list[@index].parameters[1]) + @message_waiting=false + @branch[@list[@index].indent] = command + Input.update # Must call Input.update again to avoid extra triggers + return true + end + + def command_103 + varnumber=@list[@index].parameters[0] + @message_waiting=true + params=ChooseNumberParams.new + params.setMaxDigits(@list[@index].parameters[1]) + params.setDefaultValue($game_variables[varnumber]) + $game_variables[varnumber]=pbChooseNumber(nil,params) + $game_map.need_refresh = true if $game_map + @message_waiting=false + return true + end +end + + + +class Interpreter # Used by RMXP + include InterpreterMixin + eval(InterpreterMixin::CustomEventCommands) + @@immediateDisplayAfterWait=false + @buttonInput=false + + def pbParams + return @parameters + end + + def command_105 + return false if @buttonInput + @buttonInput=true + pbButtonInputProcessing(@list[@index].parameters[0]) + @buttonInput=false + @index+=1 + return true + end + + def command_101 + if $game_temp.message_window_showing + return false + end + message="" + commands=nil + numInputVar=nil + numInputDigitsMax=nil + text="" + firstText=nil + if @list[@index].parameters.length==1 + text+=@list[@index].parameters[0] + firstText=@list[@index].parameters[0] + text+=" " if text[text.length-1,1]!=" " + message+=text + else + facename=@list[@index].parameters[0] + faceindex=@list[@index].parameters[1] + if facename && facename!="" + text+="\\ff[#{facename},#{faceindex}]" + message+=text + end + end + messageend="" + loop do + nextIndex=pbNextIndex(@index) + code=@list[nextIndex].code + if code == 401 + text=@list[nextIndex].parameters[0] + text+=" " if text[text.length-1,1]!=" " + message+=text + @index=nextIndex + else + if code == 102 + commands=@list[nextIndex].parameters + @index=nextIndex + elsif code == 106 && @@immediateDisplayAfterWait + params=@list[nextIndex].parameters + if params[0]<=10 + nextcode=@list[nextIndex+1].code + if nextcode==101 || nextcode==102 || nextcode==103 + @index=nextIndex + else + break + end + else + break + end + elsif code == 103 + numInputVar=@list[nextIndex].parameters[0] + numInputDigitsMax=@list[nextIndex].parameters[1] + @index=nextIndex + elsif code == 101 + if @list[@index].parameters.length==1 + text=@list[@index].parameters[0] + if text[/\A\\ignr/] && text==firstText + text+=" " if text[text.length-1,1]!=" " + message+=text + @index=nextIndex + continue + end + end + messageend="\1" + end + break + end + end + @message_waiting=true # needed to allow parallel process events to work while + # a message is displayed + message=_MAPINTL($game_map.map_id,message) + if commands + cmdlist=[] + for cmd in commands[0] + cmdlist.push(_MAPINTL($game_map.map_id,cmd)) + end + command=pbMessage(message+messageend,cmdlist,commands[1]) + @branch[@list[@index].indent] = command + elsif numInputVar + params=ChooseNumberParams.new + params.setMaxDigits(numInputDigitsMax) + params.setDefaultValue($game_variables[numInputVar]) + $game_variables[numInputVar]=pbMessageChooseNumber(message+messageend,params) + $game_map.need_refresh = true if $game_map + else + pbMessage(message+messageend,nil) + end + @message_waiting=false + return true + end + + def command_102 + @message_waiting=true + command=pbShowCommands(nil,@list[@index].parameters[0],@list[@index].parameters[1]) + @message_waiting=false + @branch[@list[@index].indent] = command + Input.update # Must call Input.update again to avoid extra triggers + return true + end + + def command_103 + varnumber=@list[@index].parameters[0] + @message_waiting=true + params=ChooseNumberParams.new + params.setMaxDigits(@list[@index].parameters[1]) + params.setDefaultValue($game_variables[varnumber]) + $game_variables[varnumber]=pbChooseNumber(nil,params) + $game_map.need_refresh = true if $game_map + @message_waiting=false + return true + end +end + + + +#=============================================================================== +# +#=============================================================================== +class ChooseNumberParams + def initialize + @maxDigits=0 + @minNumber=0 + @maxNumber=0 + @skin=nil + @messageSkin=nil + @negativesAllowed=false + @initialNumber=0 + @cancelNumber=nil + end + + def setMessageSkin(value) + @messageSkin=value + end + + def messageSkin # Set the full path for the message's window skin + @messageSkin + end + + def setSkin(value) + @skin=value + end + + def skin + @skin + end + + def setNegativesAllowed(value) + @negativeAllowed=value + end + + def negativesAllowed + @negativeAllowed ? true : false + end + + def setRange(minNumber,maxNumber) + maxNumber=minNumber if minNumber>maxNumber + @maxDigits=0 + @minNumber=minNumber + @maxNumber=maxNumber + end + + def setDefaultValue(number) + @initialNumber=number + @cancelNumber=nil + end + + def setInitialValue(number) + @initialNumber=number + end + + def setCancelValue(number) + @cancelNumber=number + end + + def initialNumber + return clamp(@initialNumber,self.minNumber,self.maxNumber) + end + + def cancelNumber + return @cancelNumber || self.initialNumber + end + + def minNumber + ret=0 + if @maxDigits>0 + ret=-((10**@maxDigits)-1) + elsif + ret=@minNumber + end + ret=0 if !@negativeAllowed && ret<0 + return ret + end + + def maxNumber + ret=0 + if @maxDigits>0 + ret=((10**@maxDigits)-1) + elsif + ret=@maxNumber + end + ret=0 if !@negativeAllowed && ret<0 + return ret + end + + def setMaxDigits(value) + @maxDigits=[1,value].max + end + + def maxDigits + if @maxDigits>0 + return @maxDigits + else + return [numDigits(self.minNumber),numDigits(self.maxNumber)].max + end + end + + private + + def clamp(v,mn,mx) + return vmx ? mx : v) + end + + def numDigits(number) + ans = 1 + number=number.abs + while number >= 10 + ans+=1 + number/=10 + end + return ans + end +end + + + +def pbChooseNumber(msgwindow,params) + return 0 if !params + ret=0 + maximum=params.maxNumber + minimum=params.minNumber + defaultNumber=params.initialNumber + cancelNumber=params.cancelNumber + cmdwindow=Window_InputNumberPokemon.new(params.maxDigits) + cmdwindow.z=99999 + cmdwindow.visible=true + cmdwindow.setSkin(params.skin) if params.skin + cmdwindow.sign=params.negativesAllowed # must be set before number + cmdwindow.number=defaultNumber + curnumber=defaultNumber + pbPositionNearMsgWindow(cmdwindow,msgwindow,:right) + command=0 + loop do + Graphics.update + Input.update + pbUpdateSceneMap + cmdwindow.update + msgwindow.update if msgwindow + yield if block_given? + if Input.trigger?(Input::C) + ret=cmdwindow.number + if ret>maximum + pbPlayBuzzerSE() + elsif ret1 + @facebitmaptmp.update + @facebitmap.blt(0,0,@facebitmaptmp.bitmap,Rect.new( + (@faceIndex % 4) * 96, + (@faceIndex / 4) * 96, 96, 96 + )) + end + end + + def dispose + @facebitmaptmp.dispose + @facebitmap.dispose if @facebitmap + super + end +end + + + +#=============================================================================== +# +#=============================================================================== +def pbGetBasicMapNameFromId(id) + begin + map = pbLoadRxData("Data/MapInfos") + return "" if !map + return map[id].name + rescue + return "" + end +end + +def pbGetMapNameFromId(id) + map=pbGetBasicMapNameFromId(id) + map.gsub!(/\\PN/,$Trainer.name) if $Trainer + return map +end + +def pbCsvField!(str) + ret="" + str.sub!(/\A\s*/,"") + if str[0,1]=="\"" + str[0,1]="" + escaped=false + fieldbytes=0 + str.scan(/./) do |s| + fieldbytes+=s.length + break if s=="\"" && !escaped + if s=="\\" && !escaped + escaped=true + else + ret+=s + escaped=false + end + end + str[0,fieldbytes]="" + if !str[/\A\s*,/] && !str[/\A\s*$/] + raise _INTL("Invalid quoted field (in: {1})",ret) + end + str[0,str.length]=$~.post_match + else + if str[/,/] + str[0,str.length]=$~.post_match + ret=$~.pre_match + else + ret=str.clone + str[0,str.length]="" + end + ret.gsub!(/\s+$/,"") + end + return ret +end + +def pbCsvPosInt!(str) + ret=pbCsvField!(str) + if !ret[/\A\d+$/] + raise _INTL("Field {1} is not a positive integer",ret) + end + return ret.to_i +end + + + +#=============================================================================== +# Money and coins windows +#=============================================================================== +def pbGetGoldString + moneyString="" + begin + moneyString=_INTL("${1}",$Trainer.money.to_s_formatted) + rescue + if $data_system.respond_to?("words") + moneyString=_INTL("{1} {2}",$game_party.gold,$data_system.words.gold) + else + moneyString=_INTL("{1} {2}",$game_party.gold,Vocab.gold) + end + end + return moneyString +end + +def pbDisplayGoldWindow(msgwindow) + moneyString=pbGetGoldString() + goldwindow=Window_AdvancedTextPokemon.new(_INTL("Money:\n{1}",moneyString)) + goldwindow.setSkin("Graphics/Windowskins/goldskin") + goldwindow.resizeToFit(goldwindow.text,Graphics.width) + goldwindow.width=160 if goldwindow.width<=160 + if msgwindow.y==0 + goldwindow.y=Graphics.height-goldwindow.height + else + goldwindow.y=0 + end + goldwindow.viewport=msgwindow.viewport + goldwindow.z=msgwindow.z + return goldwindow +end + +def pbDisplayCoinsWindow(msgwindow,goldwindow) + coinString=($PokemonGlobal) ? $PokemonGlobal.coins.to_s_formatted : "0" + coinwindow=Window_AdvancedTextPokemon.new(_INTL("Coins:\n{1}",coinString)) + coinwindow.setSkin("Graphics/Windowskins/goldskin") + coinwindow.resizeToFit(coinwindow.text,Graphics.width) + coinwindow.width=160 if coinwindow.width<=160 + if msgwindow.y==0 + coinwindow.y=(goldwindow) ? goldwindow.y-coinwindow.height : Graphics.height-coinwindow.height + else + coinwindow.y=(goldwindow) ? goldwindow.height : 0 + end + coinwindow.viewport=msgwindow.viewport + coinwindow.z=msgwindow.z + return coinwindow +end + + + +#=============================================================================== +# +#=============================================================================== +def pbCreateStatusWindow(viewport=nil) + msgwindow=Window_AdvancedTextPokemon.new("") + if !viewport + msgwindow.z=99999 + else + msgwindow.viewport=viewport + end + msgwindow.visible=false + msgwindow.letterbyletter=false + pbBottomLeftLines(msgwindow,2) + skinfile=MessageConfig.pbGetSpeechFrame() + msgwindow.setSkin(skinfile) + return msgwindow +end + +def pbCreateMessageWindow(viewport=nil,skin=nil) + msgwindow=Window_AdvancedTextPokemon.new("") + if !viewport + msgwindow.z=99999 + else + msgwindow.viewport=viewport + end + msgwindow.visible=true + msgwindow.letterbyletter=true + msgwindow.back_opacity=MessageConfig::WindowOpacity + pbBottomLeftLines(msgwindow,2) + $game_temp.message_window_showing=true if $game_temp + $game_message.visible=true if $game_message + skin=MessageConfig.pbGetSpeechFrame() if !skin + msgwindow.setSkin(skin) + return msgwindow +end + +def pbDisposeMessageWindow(msgwindow) + $game_temp.message_window_showing=false if $game_temp + $game_message.visible=false if $game_message + msgwindow.dispose +end + + + +#=============================================================================== +# Main message-displaying function +#=============================================================================== +def pbMessageDisplay(msgwindow,message,letterbyletter=true,commandProc=nil) + return if !msgwindow + oldletterbyletter=msgwindow.letterbyletter + msgwindow.letterbyletter=(letterbyletter) ? true : false + ret=nil + count=0 + commands=nil + facewindow=nil + goldwindow=nil + coinwindow=nil + cmdvariable=0 + cmdIfCancel=0 + msgwindow.waitcount=0 + autoresume=false + text=message.clone + msgback=nil + linecount=(Graphics.height>400) ? 3 : 2 + ### Text replacement + text.gsub!(/\\sign\[([^\]]*)\]/i) { # \sign[something] gets turned into + next "\\op\\cl\\ts[]\\w["+$1+"]" # \op\cl\ts[]\w[something] + } + text.gsub!(/\\\\/,"\5") + text.gsub!(/\\1/,"\1") + if $game_actors + text.gsub!(/\\n\[([1-8])\]/i) { + m = $1.to_i + next $game_actors[m].name + } + end + text.gsub!(/\\pn/i,$Trainer.name) if $Trainer + text.gsub!(/\\pm/i,_INTL("${1}",$Trainer.money.to_s_formatted)) if $Trainer + text.gsub!(/\\n/i,"\n") + text.gsub!(/\\\[([0-9a-f]{8,8})\]/i) { "" } + text.gsub!(/\\pg/i,"\\b") if $Trainer && $Trainer.male? + text.gsub!(/\\pg/i,"\\r") if $Trainer && $Trainer.female? + text.gsub!(/\\pog/i,"\\r") if $Trainer && $Trainer.male? + text.gsub!(/\\pog/i,"\\b") if $Trainer && $Trainer.female? + text.gsub!(/\\pg/i,"") + text.gsub!(/\\pog/i,"") + text.gsub!(/\\b/i,"") + text.gsub!(/\\r/i,"") + isDarkSkin = isDarkWindowskin(msgwindow.windowskin) + text.gsub!(/\\[Cc]\[([0-9]+)\]/) { + m = $1.to_i + next getSkinColor(msgwindow.windowskin,m,isDarkSkin) + } + begin + last_text = text.clone + text.gsub!(/\\v\[([0-9]+)\]/i) { $game_variables[$1.to_i] } + end until text == last_text + begin + last_text = text.clone + text.gsub!(/\\l\[([0-9]+)\]/i) { + linecount = [1,$1.to_i].max + next "" + } + end until text == last_text + colortag = "" + if ($game_message && $game_message.background>0) || + ($game_system && $game_system.respond_to?("message_frame") && + $game_system.message_frame != 0) + colortag = getSkinColor(msgwindow.windowskin,0,true) + else + colortag = getSkinColor(msgwindow.windowskin,0,isDarkSkin) + end + text = colortag+text + ### Controls + textchunks=[] + controls=[] + while text[/(?:\\(w|f|ff|ts|cl|me|se|wt|wtnp|ch)\[([^\]]*)\]|\\(g|cn|wd|wm|op|cl|wu|\.|\||\!|\^))/i] + textchunks.push($~.pre_match) + if $~[1] + controls.push([$~[1].downcase,$~[2],-1]) + else + controls.push([$~[3].downcase,"",-1]) + end + text=$~.post_match + end + textchunks.push(text) + for chunk in textchunks + chunk.gsub!(/\005/,"\\") + end + textlen = 0 + for i in 0...controls.length + control = controls[i][0] + case control + when "wt", "wtnp", ".", "|" + textchunks[i] += "\2" + when "!" + textchunks[i] += "\1" + end + textlen += toUnformattedText(textchunks[i]).scan(/./m).length + controls[i][2] = textlen + end + text = textchunks.join("") + unformattedText = toUnformattedText(text) + signWaitCount = 0 + signWaitTime = Graphics.frame_rate/2 + haveSpecialClose = false + specialCloseSE = "" + for i in 0...controls.length + control = controls[i][0] + param = controls[i][1] + case control + when "op" + signWaitCount = signWaitTime+1 + when "cl" + text = text.sub(/\001\z/,"") # fix: '$' can match end of line as well + haveSpecialClose = true + specialCloseSE = param + when "f" + facewindow.dispose if facewindow + facewindow = PictureWindow.new("Graphics/Pictures/#{param}") + when "ff" + facewindow.dispose if facewindow + facewindow = FaceWindowVX.new(param) + when "ch" + cmds = param.clone + cmdvariable = pbCsvPosInt!(cmds) + cmdIfCancel = pbCsvField!(cmds).to_i + commands = [] + while cmds.length>0 + commands.push(pbCsvField!(cmds)) + end + when "wtnp", "^" + text = text.sub(/\001\z/,"") # fix: '$' can match end of line as well + when "se" + if controls[i][2]==0 + startSE = param + controls[i] = nil + end + end + end + if startSE!=nil + pbSEPlay(pbStringToAudioFile(startSE)) + elsif signWaitCount==0 && letterbyletter + pbPlayDecisionSE() + end + ########## Position message window ############## + pbRepositionMessageWindow(msgwindow,linecount) + if $game_message && $game_message.background==1 + msgback = IconSprite.new(0,msgwindow.y,msgwindow.viewport) + msgback.z = msgwindow.z-1 + msgback.setBitmap("Graphics/System/MessageBack") + end + if facewindow + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + facewindow.viewport = msgwindow.viewport + facewindow.z = msgwindow.z + end + atTop = (msgwindow.y==0) + ########## Show text ############################# + msgwindow.text = text + Graphics.frame_reset if Graphics.frame_rate>40 + begin + if signWaitCount>0 + signWaitCount -= 1 + if atTop + msgwindow.y = -msgwindow.height*signWaitCount/signWaitTime + else + msgwindow.y = Graphics.height-msgwindow.height*(signWaitTime-signWaitCount)/signWaitTime + end + end + for i in 0...controls.length + next if !controls[i] + next if controls[i][2]>msgwindow.position || msgwindow.waitcount!=0 + control = controls[i][0] + param = controls[i][1] + case control + when "f" + facewindow.dispose if facewindow + facewindow = PictureWindow.new("Graphics/Pictures/#{param}") + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + facewindow.viewport = msgwindow.viewport + facewindow.z = msgwindow.z + when "ff" + facewindow.dispose if facewindow + facewindow = FaceWindowVX.new(param) + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + facewindow.viewport = msgwindow.viewport + facewindow.z = msgwindow.z + when "g" # Display gold window + goldwindow.dispose if goldwindow + goldwindow = pbDisplayGoldWindow(msgwindow) + when "cn" # Display coins window + coinwindow.dispose if coinwindow + coinwindow = pbDisplayCoinsWindow(msgwindow,goldwindow) + when "wu" + msgwindow.y = 0 + atTop = true + msgback.y = msgwindow.y if msgback + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + msgwindow.y = -msgwindow.height*signWaitCount/signWaitTime + when "wm" + atTop = false + msgwindow.y = (Graphics.height-msgwindow.height)/2 + msgback.y = msgwindow.y if msgback + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + when "wd" + atTop = false + msgwindow.y = Graphics.height-msgwindow.height + msgback.y = msgwindow.y if msgback + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + msgwindow.y = Graphics.height-msgwindow.height*(signWaitTime-signWaitCount)/signWaitTime + when "w" # Change windowskin + if param=="" + msgwindow.windowskin = nil + else + msgwindow.setSkin("Graphics/Windowskins/#{param}",false) + end + when "ts" # Change text speed + msgwindow.textspeed = (param=="") ? -999 : param.to_i + when "." # Wait 0.25 seconds + msgwindow.waitcount += Graphics.frame_rate/4 + when "|" # Wait 1 second + msgwindow.waitcount += Graphics.frame_rate + when "wt" # Wait X/20 seconds + param = param.sub(/\A\s+/,"").sub(/\s+\z/,"") + msgwindow.waitcount += param.to_i*Graphics.frame_rate/20 + when "wtnp" # Wait X/20 seconds, no pause + param = param.sub(/\A\s+/,"").sub(/\s+\z/,"") + msgwindow.waitcount = param.to_i*Graphics.frame_rate/20 + autoresume = true + when "^" # Wait, no pause + autoresume = true + when "se" # Play SE + pbSEPlay(pbStringToAudioFile(param)) + when "me" # Play ME + pbMEPlay(pbStringToAudioFile(param)) + end + controls[i] = nil + end + break if !letterbyletter + Graphics.update + Input.update + facewindow.update if facewindow + if $DEBUG && Input.trigger?(Input::F6) + pbRecord(unformattedText) + end + if autoresume && msgwindow.waitcount==0 + msgwindow.resume if msgwindow.busy? + break if !msgwindow.busy? + end + if Input.trigger?(Input::C) || Input.trigger?(Input::B) + if msgwindow.busy? + pbPlayDecisionSE if msgwindow.pausing? + msgwindow.resume + else + break if signWaitCount==0 + end + end + pbUpdateSceneMap + msgwindow.update + yield if block_given? + end until (!letterbyletter || commandProc || commands) && !msgwindow.busy? + Input.update # Must call Input.update again to avoid extra triggers + msgwindow.letterbyletter=oldletterbyletter + if commands + $game_variables[cmdvariable]=pbShowCommands(msgwindow,commands,cmdIfCancel) + $game_map.need_refresh = true if $game_map + end + if commandProc + ret=commandProc.call(msgwindow) + end + msgback.dispose if msgback + goldwindow.dispose if goldwindow + coinwindow.dispose if coinwindow + facewindow.dispose if facewindow + if haveSpecialClose + pbSEPlay(pbStringToAudioFile(specialCloseSE)) + atTop = (msgwindow.y==0) + for i in 0..signWaitTime + if atTop + msgwindow.y = -msgwindow.height*i/signWaitTime + else + msgwindow.y = Graphics.height-msgwindow.height*(signWaitTime-i)/signWaitTime + end + Graphics.update + Input.update + pbUpdateSceneMap + msgwindow.update + end + end + return ret +end + + + +#=============================================================================== +# Message-displaying functions +#=============================================================================== +def pbMessage(message,commands=nil,cmdIfCancel=0,skin=nil,defaultCmd=0,&block) + ret = 0 + msgwindow = pbCreateMessageWindow(nil,skin) + if commands + ret = pbMessageDisplay(msgwindow,message,true, + proc { |msgwindow| + next Kernel.pbShowCommands(msgwindow,commands,cmdIfCancel,defaultCmd,&block) + },&block) + else + pbMessageDisplay(msgwindow,message,&block) + end + pbDisposeMessageWindow(msgwindow) + Input.update + return ret +end + +def pbConfirmMessage(message,&block) + return (pbMessage(message,[_INTL("Yes"),_INTL("No")],2,&block)==0) +end + +def pbConfirmMessageSerious(message,&block) + return (pbMessage(message,[_INTL("No"),_INTL("Yes")],1,&block)==1) +end + +def pbMessageChooseNumber(message,params,&block) + msgwindow = pbCreateMessageWindow(nil,params.messageSkin) + ret = pbMessageDisplay(msgwindow,message,true, + proc { |msgwindow| + next pbChooseNumber(msgwindow,params,&block) + },&block) + pbDisposeMessageWindow(msgwindow) + return ret +end + +def pbShowCommands(msgwindow,commands=nil,cmdIfCancel=0,defaultCmd=0) + return 0 if !commands + cmdwindow=Window_CommandPokemonEx.new(commands) + cmdwindow.z=99999 + cmdwindow.visible=true + cmdwindow.resizeToFit(cmdwindow.commands) + pbPositionNearMsgWindow(cmdwindow,msgwindow,:right) + cmdwindow.index=defaultCmd + command=0 + loop do + Graphics.update + Input.update + cmdwindow.update + msgwindow.update if msgwindow + yield if block_given? + if Input.trigger?(Input::B) + if cmdIfCancel>0 + command=cmdIfCancel-1 + break + elsif cmdIfCancel<0 + command=cmdIfCancel + break + end + end + if Input.trigger?(Input::C) + command=cmdwindow.index + break + end + pbUpdateSceneMap + end + ret=command + cmdwindow.dispose + Input.update + return ret +end + +def pbShowCommandsWithHelp(msgwindow,commands,help,cmdIfCancel=0,defaultCmd=0) + msgwin=msgwindow + msgwin=pbCreateMessageWindow(nil) if !msgwindow + oldlbl=msgwin.letterbyletter + msgwin.letterbyletter=false + if commands + cmdwindow=Window_CommandPokemonEx.new(commands) + cmdwindow.z=99999 + cmdwindow.visible=true + cmdwindow.resizeToFit(cmdwindow.commands) + cmdwindow.height=msgwin.y if cmdwindow.height>msgwin.y + cmdwindow.index=defaultCmd + command=0 + msgwin.text=help[cmdwindow.index] + msgwin.width=msgwin.width # Necessary evil to make it use the proper margins + loop do + Graphics.update + Input.update + oldindex=cmdwindow.index + cmdwindow.update + if oldindex!=cmdwindow.index + msgwin.text=help[cmdwindow.index] + end + msgwin.update + yield if block_given? + if Input.trigger?(Input::B) + if cmdIfCancel>0 + command=cmdIfCancel-1 + break + elsif cmdIfCancel<0 + command=cmdIfCancel + break + end + end + if Input.trigger?(Input::C) + command=cmdwindow.index + break + end + pbUpdateSceneMap + end + ret=command + cmdwindow.dispose + Input.update + end + msgwin.letterbyletter=oldlbl + msgwin.dispose if !msgwindow + return ret +end + +# frames is the number of 1/20 seconds to wait for +def pbMessageWaitForInput(msgwindow,frames,showPause=false) + return if !frames || frames<=0 + msgwindow.startPause if msgwindow && showPause + frames = frames*Graphics.frame_rate/20 + frames.times do + Graphics.update + Input.update + msgwindow.update if msgwindow + pbUpdateSceneMap + if Input.trigger?(Input::C) || Input.trigger?(Input::B) + break + end + yield if block_given? + end + msgwindow.stopPause if msgwindow && showPause +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/009_TextEntry.rb b/Data/Scripts/008_Objects and windows/009_TextEntry.rb new file mode 100644 index 000000000..d9b7bd11c --- /dev/null +++ b/Data/Scripts/008_Objects and windows/009_TextEntry.rb @@ -0,0 +1,1676 @@ +#=============================================================================== +# +#=============================================================================== +class Window_CharacterEntry < Window_DrawableCommand + XSIZE=13 + YSIZE=4 + + def initialize(charset,viewport=nil) + @viewport=viewport + @charset=charset + @othercharset="" + super(0,96,480,192) + colors=getDefaultTextColors(self.windowskin) + self.baseColor=colors[0] + self.shadowColor=colors[1] + self.columns=XSIZE + refresh + end + + def setOtherCharset(value) + @othercharset=value.clone + refresh + end + + def setCharset(value) + @charset=value.clone + refresh + end + + def character + if self.index<0 || self.index>=@charset.length + return ""; + else + return @charset[self.index] + end + end + + def command + return -1 if self.index==@charset.length + return -2 if self.index==@charset.length+1 + return -3 if self.index==@charset.length+2 + return self.index + end + + def itemCount + return @charset.length+3 + end + + def drawItem(index,count,rect) + rect=drawCursor(index,rect) + if index==@charset.length # -1 + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height,"[ ]", + self.baseColor,self.shadowColor) + elsif index==@charset.length+1 # -2 + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height,@othercharset, + self.baseColor,self.shadowColor) + elsif index==@charset.length+2 # -3 + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height,_INTL("OK"), + self.baseColor,self.shadowColor) + else + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height,@charset[index], + self.baseColor,self.shadowColor) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class CharacterEntryHelper + attr_reader :text + attr_reader :maxlength + attr_reader :passwordChar + attr_accessor :cursor + + def initialize(text) + @maxlength=-1 + @text=text + @passwordChar="" + @cursor=text.scan(/./m).length + ensure + end + + def text=(value) + @text=value + ensure + end + + def textChars + chars=text.scan(/./m) + if @passwordChar!="" + chars.length.times { |i| chars[i] = @passwordChar } + end + return chars + end + + def passwordChar=(value) + @passwordChar=value ? value : "" + end + + def maxlength=(value) + @maxlength=value + ensure + end + + def length + return self.text.scan(/./m).length + end + + def canInsert? + chars=self.text.scan(/./m) + return false if @maxlength>=0 && chars.length>=@maxlength + return true + end + + def insert(ch) + chars=self.text.scan(/./m) + return false if @maxlength>=0 && chars.length>=@maxlength + chars.insert(@cursor,ch) + @text="" + for ch in chars + @text+=ch if ch + end + @cursor+=1 + return true + end + + def canDelete? + chars=self.text.scan(/./m) + return false if chars.length<=0 || @cursor<=0 + return true + end + + def delete + chars=self.text.scan(/./m) + return false if chars.length<=0 || @cursor<=0 + chars.delete_at(@cursor-1) + @text="" + for ch in chars + @text+=ch if ch + end + @cursor-=1 + return true + end + + private + + def ensure + return if @maxlength<0 + chars=self.text.scan(/./m) + if chars.length>@maxlength && @maxlength>=0 + chars=chars[0,@maxlength] + end + @text="" + for ch in chars + @text+=ch if ch + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_TextEntry < SpriteWindow_Base + def initialize(text,x,y,width,height,heading=nil,usedarkercolor=false) + super(x,y,width,height) + colors=getDefaultTextColors(self.windowskin) + @baseColor=colors[0] + @shadowColor=colors[1] + if usedarkercolor + @baseColor=Color.new(16,24,32) + @shadowColor=Color.new(168,184,184) + end + @helper=CharacterEntryHelper.new(text) + @heading=heading + self.active=true + @frame=0 + refresh + end + + def text + @helper.text + end + + def maxlength + @helper.maxlength + end + + def passwordChar + @helper.passwordChar + end + + def text=(value) + @helper.text=value + self.refresh + end + + def passwordChar=(value) + @helper.passwordChar=value + refresh + end + + def maxlength=(value) + @helper.maxlength=value + self.refresh + end + + def insert(ch) + if @helper.insert(ch) + @frame=0 + self.refresh + return true + end + return false + end + + def delete + if @helper.delete + @frame=0 + self.refresh + return true + end + return false + end + + def update + @frame += 1 + @frame %= 20 + self.refresh if (@frame%10)==0 + return if !self.active + # Moving cursor + if Input.repeat?(Input::LEFT) && Input.press?(Input::A) + if @helper.cursor > 0 + @helper.cursor -= 1 + @frame = 0 + self.refresh + end + elsif Input.repeat?(Input::RIGHT) && Input.press?(Input::A) + if @helper.cursor < self.text.scan(/./m).length + @helper.cursor += 1 + @frame = 0 + self.refresh + end + elsif Input.repeat?(Input::B) # Backspace + self.delete if @helper.cursor > 0 + end + end + + def refresh + self.contents=pbDoEnsureBitmap(self.contents,self.width-self.borderX, + self.height-self.borderY) + bitmap=self.contents + bitmap.clear + x=0 + y=0 + if @heading + textwidth=bitmap.text_size(@heading).width + pbDrawShadowText(bitmap,x,y, textwidth+4, 32, @heading,@baseColor,@shadowColor) + y+=32 + end + x+=4 + width=self.width-self.borderX + height=self.height-self.borderY + cursorcolor=Color.new(16,24,32) + textscan=self.text.scan(/./m) + scanlength=textscan.length + @helper.cursor=scanlength if @helper.cursor>scanlength + @helper.cursor=0 if @helper.cursor<0 + startpos=@helper.cursor + fromcursor=0 + while (startpos>0) + c=(@helper.passwordChar!="") ? @helper.passwordChar : textscan[startpos-1] + fromcursor+=bitmap.text_size(c).width + break if fromcursor>width-4 + startpos-=1 + end + for i in startpos...scanlength + c=(@helper.passwordChar!="") ? @helper.passwordChar : textscan[i] + textwidth=bitmap.text_size(c).width + next if c=="\n" + # Draw text + pbDrawShadowText(bitmap,x,y, textwidth+4, 32, c,@baseColor,@shadowColor) + # Draw cursor if necessary + if ((@frame/10)&1) == 0 && i==@helper.cursor + bitmap.fill_rect(x,y+4,2,24,cursorcolor) + end + # Add x to drawn text width + x += textwidth + end + if ((@frame/10)&1) == 0 && textscan.length==@helper.cursor + bitmap.fill_rect(x,y+4,2,24,cursorcolor) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +def getLineBrokenText(bitmap,value,width,dims) + x=0 + y=0 + textheight=0 + ret=[] + if dims + dims[0]=0 + dims[1]=0 + end + line=0 + position=0 + column=0 + return ret if !bitmap || bitmap.disposed? || width<=0 + textmsg=value.clone + lines=0 + color=Font.default_color + ret.push(["",0,0,0,bitmap.text_size("X").height,0,0,0,0]) + while ((c = textmsg.slice!(/\n|(\S*([ \r\t\f]?))/)) != nil) + break if c=="" + length=c.scan(/./m).length + ccheck=c + if ccheck=="\n" + ret.push(["\n",x,y,0,textheight,line,position,column,0]) + x=0 + y+=(textheight==0) ? bitmap.text_size("X").height : textheight + line+=1 + textheight=0 + column=0 + position+=length + ret.push(["",x,y,0,textheight,line,position,column,0]) + next + end + textcols=[] + words=[ccheck] + for i in 0...words.length + word=words[i] + if word && word!="" + textSize=bitmap.text_size(word) + textwidth=textSize.width + if x>0 && x+textwidth>=width-2 + # Zero-length word break + ret.push(["",x,y,0,textheight,line,position,column,0]) + x=0 + column=0 + y+=(textheight==0) ? bitmap.text_size("X").height : textheight + line+=1 + textheight=0 + end + textheight=[textheight,textSize.height].max + ret.push([word,x,y,textwidth,textheight,line,position,column,length]) + x+=textwidth + dims[0]=x if dims && dims[0]=totallines + maximumY=0 + for i in 0...textchars.length + thisline=textchars[i][5] + y=textchars[i][2] + return y if thisline==line + maximumY=y if maximumY=totallines + endpos=0 + for i in 0...textchars.length + thisline=textchars[i][5] + thispos=textchars[i][6] + thislength=textchars[i][8] + if thisline==line + endpos+=thislength + end + end + return endpos + end + end + + def getPosFromLineAndColumn(line,column) + textchars=getTextChars + if textchars.length==0 + return 0 + else + totallines=getTotalLines() + line=0 if line<0 + line=totallines-1 if line>=totallines + endpos=0 + for i in 0...textchars.length + thisline=textchars[i][5] + thispos=textchars[i][6] + thiscolumn=textchars[i][7] + thislength=textchars[i][8] + if thisline==line + endpos=thispos+thislength +# echoln [endpos,thispos+(column-thiscolumn),textchars[i]] + if column>=thiscolumn && column<=thiscolumn+thislength && thislength>0 + return thispos+(column-thiscolumn) + end + end + end + if endpos==0 +# echoln [totallines,line,column] +# echoln textchars + end +# echoln "endpos=#{endpos}" + return endpos + end + end + + def getLastVisibleLine + textchars=getTextChars() + textheight=[1,self.contents.text_size("X").height].max + lastVisible=@firstline+((self.height-self.borderY)/textheight)-1 + return lastVisible + end + + def updateCursorPos(doRefresh) + # Calculate new cursor position + @helper.cursor=getPosFromLineAndColumn(@cursorLine,@cursorColumn) + if doRefresh + @frame=0 + self.refresh + end + if @cursorLine<@firstline + @firstline=@cursorLine + end + lastVisible=getLastVisibleLine() + if @cursorLine>lastVisible + @firstline+=(@cursorLine-lastVisible) + end + end + + def moveCursor(lineOffset, columnOffset) + # Move column offset first, then lines (since column offset + # can affect line offset) +# echoln ["beforemoving",@cursorLine,@cursorColumn] + totalColumns=getColumnsInLine(@cursorLine) # check current line + totalLines=getTotalLines() + oldCursorLine=@cursorLine + oldCursorColumn=@cursorColumn + @cursorColumn+=columnOffset + if @cursorColumn<0 && @cursorLine>0 + # Will happen if cursor is moved left from the beginning of a line + @cursorLine-=1 + @cursorColumn=getColumnsInLine(@cursorLine) + elsif @cursorColumn>totalColumns && @cursorLinetotalColumns + @cursorColumn=0 if @cursorColumn<0 # totalColumns can be 0 + # Move line offset + @cursorLine+=lineOffset + @cursorLine=0 if @cursorLine<0 + @cursorLine=totalLines-1 if @cursorLine>=totalLines + # Ensure column bounds again + totalColumns=getColumnsInLine(@cursorLine) + @cursorColumn=totalColumns if @cursorColumn>totalColumns + @cursorColumn=0 if @cursorColumn<0 # totalColumns can be 0 + updateCursorPos( + oldCursorLine!=@cursorLine || + oldCursorColumn!=@cursorColumn + ) +# echoln ["aftermoving",@cursorLine,@cursorColumn] + end + + def update + @frame+=1 + @frame%=20 + self.refresh if ((@frame%10)==0) + return if !self.active + # Moving cursor + if Input.repeat?(Input::UP) + moveCursor(-1,0) + return + elsif Input.repeat?(Input::DOWN) + moveCursor(1,0) + return + elsif Input.repeat?(Input::LEFT) + moveCursor(0,-1) + return + elsif Input.repeat?(Input::RIGHT) + moveCursor(0,1) + return + end + if !@peekMessage + @peekMessage = Win32API.new("user32.dll","PeekMessage","pliii","i") rescue nil + end + if @peekMessage + msg=[0,0,0,0,0,0,0].pack("V*") + retval=@peekMessage.call(msg,0,0x102,0x102,1) + if retval!=0 + p "WM_CHAR #{msg[2]}" + end + end + if Input.press?(Input::CTRL) && Input.trigger?(Input::HOME) + # Move cursor to beginning + @cursorLine=0 + @cursorColumn=0 + updateCursorPos(true) + return + elsif Input.press?(Input::CTRL) && Input.trigger?(Input::ENDKEY) + # Move cursor to end + @cursorLine=getTotalLines()-1 + @cursorColumn=getColumnsInLine(@cursorLine) + updateCursorPos(true) + return + elsif Input.repeat?(Input::ENTER) + self.insert("\n") + return + elsif Input.repeat?(Input::BACKSPACE) # Backspace + self.delete + return + end + # Letter keys + for i in 65..90 + if Input.repeatex?(i) + shift=(Input.press?(Input::SHIFT)) ? 0x41 : 0x61 + insert((shift+i-65).chr) + return + end + end + # Number keys + shifted=")!@\#$%^&*(" + unshifted="0123456789" + for i in 48..57 + if Input.repeatex?(i) + insert((Input.press?(Input::SHIFT)) ? shifted[i-48].chr : unshifted[i-48].chr) + return + end + end + keys=[ + [32," "," "], + [106,"*","*"], + [107,"+","+"], + [109,"-","-"], + [111,"/","/"], + [186,";",":"], + [187,"=","+"], + [188,",","<"], + [189,"-","_"], + [190,".",">"], + [191,"/","?"], + [219,"[","{"], + [220,"\\","|"], + [221,"]","}"], + [222,"'","\""] + ] + for i in keys + if Input.repeatex?(i[0]) + insert((Input.press?(Input::SHIFT)) ? i[2] : i[1]) + return + end + end + end + + def refresh + newContents=pbDoEnsureBitmap(self.contents,self.width-self.borderX, + self.height-self.borderY) + @textchars=nil if self.contents!=newContents + self.contents=newContents + bitmap=self.contents + bitmap.clear + x=0 + y=0 + getTextChars + x+=4 + width=self.width-self.borderX + height=self.height-self.borderY + cursorcolor=Color.new(0,0,0) + textchars=getTextChars() + scanlength=@helper.length + startY=getLineY(@firstline) + lastheight=32 + for i in 0...textchars.length + thisline=textchars[i][5] + thispos=textchars[i][6] + thiscolumn=textchars[i][7] + thislength=textchars[i][8] + textY=textchars[i][2]-startY + # Don't draw lines before the first or zero-length segments + next if thisline<@firstline || thislength==0 + # Don't draw lines beyond the window's height + break if textY >= height + c=textchars[i][0] + # Don't draw spaces + next if c==" " + textwidth=textchars[i][3]+4 # add 4 to prevent draw_text from stretching text + textheight=textchars[i][4] + lastheight=textheight + # Draw text + pbDrawShadowText(bitmap,textchars[i][1],textY, textwidth, textheight, c,@baseColor,@shadowColor) + end + # Draw cursor + if ((@frame/10)&1) == 0 + textheight=bitmap.text_size("X").height + cursorY=(textheight*@cursorLine)-startY + cursorX=0 + for i in 0...textchars.length + thisline=textchars[i][5] + thispos=textchars[i][6] + thiscolumn=textchars[i][7] + thislength=textchars[i][8] + if thisline==@cursorLine && @cursorColumn>=thiscolumn && + @cursorColumn<=thiscolumn+thislength + cursorY=textchars[i][2]-startY + cursorX=textchars[i][1] + textheight=textchars[i][4] + posToCursor=@cursorColumn-thiscolumn + if posToCursor>=0 + partialString=textchars[i][0].scan(/./m)[0,posToCursor].join("") + cursorX+=bitmap.text_size(partialString).width + end + break + end + end + cursorY+=4 + cursorHeight=[4,textheight-4,bitmap.text_size("X").height-4].max + bitmap.fill_rect(cursorX,cursorY,2,cursorHeight,cursorcolor) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_TextEntry_Keyboard < Window_TextEntry + def update + @frame+=1 + @frame%=20 + self.refresh if ((@frame%10)==0) + return if !self.active + # Moving cursor + if Input.repeat?(Input::LEFT) + if @helper.cursor > 0 + @helper.cursor-=1 + @frame=0 + self.refresh + end + return + elsif Input.repeat?(Input::RIGHT) + if @helper.cursor < self.text.scan(/./m).length + @helper.cursor+=1 + @frame=0 + self.refresh + end + return + elsif Input.repeat?(Input::BACKSPACE) + self.delete if @helper.cursor>0 + return + elsif Input.trigger?(Input::ENTER) || Input.trigger?(Input::ESC) + return + end + if !@toUnicode + @toUnicode = Win32API.new("user32.dll","ToUnicode","iippii","i") rescue nil + @mapVirtualKey = Win32API.new("user32.dll","MapVirtualKey","ii","i") rescue nil + @getKeyboardState = Win32API.new("user32.dll","GetKeyboardState","p","i") rescue nil + end + if @getKeyboardState + kbs = "\0"*256 + @getKeyboardState.call(kbs) + kbcount = 0 + for i in 3...256 + next if !Input.triggerex?(i) + vsc = @mapVirtualKey.call(i,0) + buf = "\0"*8 + ret = @toUnicode.call(i,vsc,kbs,buf,4,0) + next if ret<=0 + b = buf.unpack("v*") + for j in 0...ret + if buf[j]<=0x7F + insert(buf[j].chr) + elsif buf[j]<=0x7FF + insert((0xC0|((buf[j]>>6)&0x1F)).chr+(0x80|(buf[j]&0x3F)).chr) + else + str = (0xE0|((buf[j]>>12)&0x0F)).chr + str += (0x80|((buf[j]>>6)&0x3F)).chr + str += (0x80|(buf[j]&0x3F)).chr + insert(str) + end + kbcount += 1 + end + end + return if kbcount>0 + end + # Letter keys + for i in 65..90 + if Input.repeatex?(i) + shift=(Input.press?(Input::SHIFT)) ? 0x41 : 0x61 + insert((shift+i-65).chr) + return + end + end + # Number keys + shifted = ")!@\#$%^&*(" + unshifted = "0123456789" + for i in 48..57 + if Input.repeatex?(i) + insert((Input.press?(Input::SHIFT)) ? shifted[i-48].chr : unshifted[i-48].chr) + return + end + end + keys = [ + [32," "," "], + [106,"*","*"], + [107,"+","+"], + [109,"-","-"], + [111,"/","/"], + [186,";",":"], + [187,"=","+"], + [188,",","<"], + [189,"-","_"], + [190,".",">"], + [191,"/","?"], + [219,"[","{"], + [221,"]","}"], + [222,"'","\""] + ] + for i in keys + if Input.repeatex?(i[0]) + insert((Input.press?(Input::SHIFT)) ? i[2] : i[1]) + return + end + end + end +end + + + +#=============================================================================== +# Text entry screen - free typing. +#=============================================================================== +class PokemonEntryScene + @@Characters=[ + [("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz").scan(/./),"[*]"], + [("0123456789 !@\#$%^&*() ~`-_+={}[] :;'\"<>,.?/ ").scan(/./),"[A]"], + ] + USEKEYBOARD=true + + def pbStartScene(helptext,minlength,maxlength,initialText,subject=0,pokemon=nil) + @sprites={} + @viewport=Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z=99999 + if USEKEYBOARD + @sprites["entry"]=Window_TextEntry_Keyboard.new(initialText, + 0,0,400-112,96,helptext,true) + $fullInputUpdate = true + else + @sprites["entry"]=Window_TextEntry.new(initialText,0,0,400,96,helptext,true) + end + @sprites["entry"].x=(Graphics.width/2)-(@sprites["entry"].width/2)+32 + @sprites["entry"].viewport=@viewport + @sprites["entry"].visible=true + @minlength=minlength + @maxlength=maxlength + @symtype=0 + @sprites["entry"].maxlength=maxlength + if !USEKEYBOARD + @sprites["entry2"]=Window_CharacterEntry.new(@@Characters[@symtype][0]) + @sprites["entry2"].setOtherCharset(@@Characters[@symtype][1]) + @sprites["entry2"].viewport=@viewport + @sprites["entry2"].visible=true + @sprites["entry2"].x=(Graphics.width/2)-(@sprites["entry2"].width/2) + end + if minlength==0 + @sprites["helpwindow"]=Window_UnformattedTextPokemon.newWithSize( + _INTL("Enter text using the keyboard. Press\nEnter to confirm, or Esc to cancel."), + 32,Graphics.height-96,Graphics.width-64,96,@viewport + ) + else + @sprites["helpwindow"]=Window_UnformattedTextPokemon.newWithSize( + _INTL("Enter text using the keyboard.\nPress Enter to confirm."), + 32,Graphics.height-96,Graphics.width-64,96,@viewport + ) + end + @sprites["helpwindow"].letterbyletter=false + @sprites["helpwindow"].viewport=@viewport + @sprites["helpwindow"].visible=USEKEYBOARD + @sprites["helpwindow"].baseColor=Color.new(16,24,32) + @sprites["helpwindow"].shadowColor=Color.new(168,184,184) + addBackgroundPlane(@sprites,"background","Naming/bg_2",@viewport) + case subject + when 1 # Player + meta=pbGetMetadata(0,MetadataPlayerA+$PokemonGlobal.playerID) + if meta + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + filename=pbGetPlayerCharset(meta,1,nil,true) + @sprites["subject"]=TrainerWalkingCharSprite.new(filename,@viewport) + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 38*2 - charheight/4 + end + when 2 # Pokémon + if pokemon + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + @sprites["subject"]=PokemonIconSprite.new(pokemon,@viewport) + @sprites["subject"].setOffset(PictureOrigin::Center) + @sprites["subject"].x=88 + @sprites["subject"].y=54 + @sprites["gender"]=BitmapSprite.new(32,32,@viewport) + @sprites["gender"].x=430 + @sprites["gender"].y=54 + @sprites["gender"].bitmap.clear + pbSetSystemFont(@sprites["gender"].bitmap) + textpos=[] + if pokemon.male? + textpos.push([_INTL("♂"),0,0,false,Color.new(0,128,248),Color.new(168,184,184)]) + elsif pokemon.female? + textpos.push([_INTL("♀"),0,0,false,Color.new(248,24,24),Color.new(168,184,184)]) + end + pbDrawTextPositions(@sprites["gender"].bitmap,textpos) + end + when 3 # NPC + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + @sprites["subject"]=TrainerWalkingCharSprite.new(pokemon.to_s,@viewport) + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 38*2 - charheight/4 + when 4 # Storage box + @sprites["subject"]=TrainerWalkingCharSprite.new(nil,@viewport) + @sprites["subject"].altcharset="Graphics/Pictures/Naming/icon_storage" + @sprites["subject"].animspeed=4 + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 26*2 - charheight/2 + end + pbFadeInAndShow(@sprites) + end + + def pbEntry1 + ret="" + loop do + Graphics.update + Input.update + if Input.trigger?(Input::ESC) && @minlength==0 + ret="" + break + elsif Input.trigger?(Input::ENTER) && @sprites["entry"].text.length>=@minlength + ret=@sprites["entry"].text + break + end + @sprites["helpwindow"].update + @sprites["entry"].update + @sprites["subject"].update if @sprites["subject"] + end + Input.update + return ret + end + + def pbEntry2 + ret="" + loop do + Graphics.update + Input.update + @sprites["helpwindow"].update + @sprites["entry"].update + @sprites["entry2"].update + @sprites["subject"].update if @sprites["subject"] + if Input.trigger?(Input::C) + index=@sprites["entry2"].command + if index==-3 # Confirm text + ret=@sprites["entry"].text + if ret.length<@minlength || ret.length>@maxlength + pbPlayBuzzerSE() + else + pbPlayDecisionSE() + break + end + elsif index==-1 # Insert a space + if @sprites["entry"].insert(" ") + pbPlayDecisionSE() + else + pbPlayBuzzerSE() + end + elsif index==-2 # Change character set + pbPlayDecisionSE() + @symtype+=1 + @symtype=0 if @symtype>=@@Characters.length + @sprites["entry2"].setCharset(@@Characters[@symtype][0]) + @sprites["entry2"].setOtherCharset(@@Characters[@symtype][1]) + else # Insert given character + if @sprites["entry"].insert(@sprites["entry2"].character) + pbPlayDecisionSE() + else + pbPlayBuzzerSE() + end + end + next + end + end + Input.update + return ret + end + + def pbEntry + return USEKEYBOARD ? pbEntry1 : pbEntry2 + end + + def pbEndScene + $fullInputUpdate = false + pbFadeOutAndHide(@sprites) + pbDisposeSpriteHash(@sprites) + @viewport.dispose + end +end + + + +#=============================================================================== +# Text entry screen - arrows to select letter. +#=============================================================================== +class PokemonEntryScene2 + @@Characters = [ + [("ABCDEFGHIJ ,."+"KLMNOPQRST '-"+"UVWXYZ ♂♀"+" "+"0123456789 ").scan(/./),_INTL("UPPER")], + [("abcdefghij ,."+"klmnopqrst '-"+"uvwxyz ♂♀"+" "+"0123456789 ").scan(/./),_INTL("lower")], + [(",.:;!? ♂♀ "+"\"'()<>[] "+"~@#%*&$ "+"+-=^_/\\| "+" ").scan(/./),_INTL("other")], + ] + ROWS = 13 + COLUMNS = 5 + MODE1 = -5 + MODE2 = -4 + MODE3 = -3 + BACK = -2 + OK = -1 + + class NameEntryCursor + def initialize(viewport) + @sprite = SpriteWrapper.new(viewport) + @cursortype = 0 + @cursor1 = AnimatedBitmap.new("Graphics/Pictures/Naming/cursor_1") + @cursor2 = AnimatedBitmap.new("Graphics/Pictures/Naming/cursor_2") + @cursor3 = AnimatedBitmap.new("Graphics/Pictures/Naming/cursor_3") + @cursorPos = 0 + updateInternal + end + + def setCursorPos(value) + @cursorPos = value + end + + def updateCursorPos + value=@cursorPos + if value==PokemonEntryScene2::MODE1 # Upper case + @sprite.x=48 + @sprite.y=120 + @cursortype=1 + elsif value==PokemonEntryScene2::MODE2 # Lower case + @sprite.x=112 + @sprite.y=120 + @cursortype=1 + elsif value==PokemonEntryScene2::MODE3 # Other symbols + @sprite.x=176 + @sprite.y=120 + @cursortype=1 + elsif value==PokemonEntryScene2::BACK # Back + @sprite.x=312 + @sprite.y=120 + @cursortype=2 + elsif value==PokemonEntryScene2::OK # OK + @sprite.x=392 + @sprite.y=120 + @cursortype=2 + elsif value>=0 + @sprite.x=52+32*(value%PokemonEntryScene2::ROWS) + @sprite.y=180+38*(value/PokemonEntryScene2::ROWS) + @cursortype=0 + end + end + + def visible=(value) + @sprite.visible=value + end + + def visible + @sprite.visible + end + + def color=(value) + @sprite.color=value + end + + def color + @sprite.color + end + + def disposed? + @sprite.disposed? + end + + def updateInternal + @cursor1.update + @cursor2.update + @cursor3.update + updateCursorPos + case @cursortype + when 0; @sprite.bitmap=@cursor1.bitmap + when 1; @sprite.bitmap=@cursor2.bitmap + when 2; @sprite.bitmap=@cursor3.bitmap + end + end + + def update + updateInternal + end + + def dispose + @cursor1.dispose + @cursor2.dispose + @cursor3.dispose + @sprite.dispose + end + end + + + + def pbStartScene(helptext,minlength,maxlength,initialText,subject=0,pokemon=nil) + @sprites={} + @viewport=Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z=99999 + @helptext=helptext + @helper=CharacterEntryHelper.new(initialText) + @bitmaps=[ + AnimatedBitmap.new("Graphics/Pictures/Naming/overlay_tab_1"), + AnimatedBitmap.new("Graphics/Pictures/Naming/overlay_tab_2"), + AnimatedBitmap.new("Graphics/Pictures/Naming/overlay_tab_3") + ] + @bitmaps[3]=@bitmaps[0].bitmap.clone + @bitmaps[4]=@bitmaps[1].bitmap.clone + @bitmaps[5]=@bitmaps[2].bitmap.clone + for i in 0...3 + pos=0 + pbSetSystemFont(@bitmaps[i+3]) + textPos=[] + for y in 0...COLUMNS + for x in 0...ROWS + textPos.push([@@Characters[i][0][pos],44+x*32,18+y*38,2, + Color.new(16,24,32), Color.new(160,160,160)]) + pos+=1 + end + end + pbDrawTextPositions(@bitmaps[i+3],textPos) + end + @bitmaps[6]=BitmapWrapper.new(24,6) + @bitmaps[6].fill_rect(2,2,22,4,Color.new(168,184,184)) + @bitmaps[6].fill_rect(0,0,22,4,Color.new(16,24,32)) + @sprites["bg"]=IconSprite.new(0,0,@viewport) + @sprites["bg"].setBitmap("Graphics/Pictures/Naming/bg") + case subject + when 1 # Player + meta=pbGetMetadata(0,MetadataPlayerA+$PokemonGlobal.playerID) + if meta + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + filename=pbGetPlayerCharset(meta,1,nil,true) + @sprites["subject"]=TrainerWalkingCharSprite.new(filename,@viewport) + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 38*2 - charheight/4 + end + when 2 # Pokémon + if pokemon + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + @sprites["subject"]=PokemonIconSprite.new(pokemon,@viewport) + @sprites["subject"].setOffset(PictureOrigin::Center) + @sprites["subject"].x=88 + @sprites["subject"].y=54 + @sprites["gender"]=BitmapSprite.new(32,32,@viewport) + @sprites["gender"].x=430 + @sprites["gender"].y=54 + @sprites["gender"].bitmap.clear + pbSetSystemFont(@sprites["gender"].bitmap) + textpos=[] + if pokemon.male? + textpos.push([_INTL("♂"),0,0,false,Color.new(0,128,248),Color.new(168,184,184)]) + elsif pokemon.female? + textpos.push([_INTL("♀"),0,0,false,Color.new(248,24,24),Color.new(168,184,184)]) + end + pbDrawTextPositions(@sprites["gender"].bitmap,textpos) + end + when 3 # NPC + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + @sprites["subject"]=TrainerWalkingCharSprite.new(pokemon.to_s,@viewport) + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 38*2 - charheight/4 + when 4 # Storage box + @sprites["subject"]=TrainerWalkingCharSprite.new(nil,@viewport) + @sprites["subject"].altcharset="Graphics/Pictures/Naming/icon_storage" + @sprites["subject"].animspeed=4 + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 26*2 - charheight/2 + end + @sprites["bgoverlay"]=BitmapSprite.new(Graphics.width,Graphics.height,@viewport) + pbDoUpdateOverlay + @blanks=[] + @mode=0 + @minlength=minlength + @maxlength=maxlength + @maxlength.times { |i| + @sprites["blank#{i}"]=SpriteWrapper.new(@viewport) + @sprites["blank#{i}"].bitmap=@bitmaps[6] + @sprites["blank#{i}"].x=160+24*i + @blanks[i]=0 + } + @sprites["bottomtab"]=SpriteWrapper.new(@viewport) # Current tab + @sprites["bottomtab"].x=22 + @sprites["bottomtab"].y=162 + @sprites["bottomtab"].bitmap=@bitmaps[0+3] + @sprites["toptab"]=SpriteWrapper.new(@viewport) # Next tab + @sprites["toptab"].x=22-504 + @sprites["toptab"].y=162 + @sprites["toptab"].bitmap=@bitmaps[1+3] + @sprites["controls"]=IconSprite.new(0,0,@viewport) + @sprites["controls"].setBitmap(_INTL("Graphics/Pictures/Naming/overlay_controls")) + @sprites["controls"].x=16 + @sprites["controls"].y=96 + @init=true + @sprites["overlay"]=BitmapSprite.new(Graphics.width,Graphics.height,@viewport) + pbDoUpdateOverlay2 + @sprites["cursor"]=NameEntryCursor.new(@viewport) + @cursorpos=0 + @refreshOverlay=true + @sprites["cursor"].setCursorPos(@cursorpos) + pbFadeInAndShow(@sprites) { pbUpdate } + end + + def pbUpdateOverlay + @refreshOverlay=true + end + + def pbDoUpdateOverlay2 + overlay=@sprites["overlay"].bitmap + overlay.clear + modeIcon=[[_INTL("Graphics/Pictures/Naming/icon_mode"),48+@mode*64,120,@mode*60,0,60,44]] + pbDrawImagePositions(overlay,modeIcon) + end + + def pbDoUpdateOverlay + return if !@refreshOverlay + @refreshOverlay=false + bgoverlay=@sprites["bgoverlay"].bitmap + bgoverlay.clear + pbSetSystemFont(bgoverlay) + textPositions=[ + [@helptext,160,12,false,Color.new(16,24,32),Color.new(168,184,184)] + ] + chars=@helper.textChars + x=166 + for ch in chars + textPositions.push([ch,x,48,false,Color.new(16,24,32),Color.new(168,184,184)]) + x+=24 + end + pbDrawTextPositions(bgoverlay,textPositions) + end + + def pbChangeTab(newtab=@mode+1) + pbSEPlay("GUI naming tab swap start") + @sprites["cursor"].visible = false + @sprites["toptab"].bitmap = @bitmaps[(newtab%3)+3] + # Move bottom (old) tab down off the screen, and move top (new) tab right + # onto the screen + deltaX = 48*20/Graphics.frame_rate + deltaY = 24*20/Graphics.frame_rate + loop do + if @sprites["bottomtab"].y<414 + @sprites["bottomtab"].y += deltaY + @sprites["bottomtab"].y = 414 if @sprites["bottomtab"].y>414 + end + if @sprites["toptab"].x<22 + @sprites["toptab"].x += deltaX + @sprites["toptab"].x = 22 if @sprites["toptab"].x>22 + end + Graphics.update + Input.update + pbUpdate + break if @sprites["toptab"].x>=22 && @sprites["bottomtab"].y>=414 + end + # Swap top and bottom tab around + @sprites["toptab"].x, @sprites["bottomtab"].x = @sprites["bottomtab"].x, @sprites["toptab"].x + @sprites["toptab"].y, @sprites["bottomtab"].y = @sprites["bottomtab"].y, @sprites["toptab"].y + @sprites["toptab"].bitmap, @sprites["bottomtab"].bitmap = @sprites["bottomtab"].bitmap, @sprites["toptab"].bitmap + Graphics.update + Input.update + pbUpdate + # Set the current mode + @mode = (newtab)%3 + # Set the top tab up to be the next tab + newtab = @bitmaps[((@mode+1)%3)+3] + @sprites["cursor"].visible = true + @sprites["toptab"].bitmap = newtab + @sprites["toptab"].x = 22-504 + @sprites["toptab"].y = 162 + pbSEPlay("GUI naming tab swap end") + pbDoUpdateOverlay2 + end + + def pbUpdate + for i in 0...3 + @bitmaps[i].update + end + if @init || Graphics.frame_count%5==0 + @init = false + cursorpos = @helper.cursor + cursorpos = @maxlength-1 if cursorpos>=@maxlength + cursorpos = 0 if cursorpos<0 + @maxlength.times { |i| + @blanks[i] = (i==cursorpos) ? 1 : 0 + @sprites["blank#{i}"].y = [78,82][@blanks[i]] + } + end + pbDoUpdateOverlay + pbUpdateSpriteHash(@sprites) + end + + def pbColumnEmpty?(m) + return false if m>=ROWS-1 + chset=@@Characters[@mode][0] + return ( + chset[m]==" " && + chset[m+((ROWS-1))]==" " && + chset[m+((ROWS-1)*2)]==" " && + chset[m+((ROWS-1)*3)]==" " + ) + end + + def wrapmod(x,y) + result=x%y + result+=y if result<0 + return result + end + + def pbMoveCursor + oldcursor=@cursorpos + cursordiv=@cursorpos/ROWS + cursormod=@cursorpos%ROWS + cursororigin=@cursorpos-cursormod + if Input.repeat?(Input::LEFT) + if @cursorpos<0 # Controls + @cursorpos-=1 + @cursorpos=OK if @cursorposOK + else + begin + cursormod=wrapmod((cursormod+1),ROWS) + @cursorpos=cursororigin+cursormod + end while pbColumnEmpty?(cursormod) + end + elsif Input.repeat?(Input::UP) + if @cursorpos<0 # Controls + case @cursorpos + when MODE1; @cursorpos = ROWS*(COLUMNS-1) + when MODE2; @cursorpos = ROWS*(COLUMNS-1)+2 + when MODE3; @cursorpos = ROWS*(COLUMNS-1)+4 + when BACK; @cursorpos = ROWS*(COLUMNS-1)+8 + when OK; @cursorpos = ROWS*(COLUMNS-1)+11 + end + elsif @cursorpos=ROWS*(COLUMNS-1) # Bottom row of letters + case @cursorpos + when ROWS*(COLUMNS-1),ROWS*(COLUMNS-1)+1 + @cursorpos = MODE1 + when ROWS*(COLUMNS-1)+2,ROWS*(COLUMNS-1)+3 + @cursorpos = MODE2 + when ROWS*(COLUMNS-1)+4,ROWS*(COLUMNS-1)+5,ROWS*(COLUMNS-1)+6 + @cursorpos = MODE3 + when ROWS*(COLUMNS-1)+7,ROWS*(COLUMNS-1)+8,ROWS*(COLUMNS-1)+9,ROWS*(COLUMNS-1)+10 + @cursorpos = BACK + when ROWS*(COLUMNS-1)+11,ROWS*(COLUMNS-1)+12 + @cursorpos = OK + end + else + cursordiv=wrapmod((cursordiv+1),COLUMNS) + @cursorpos=(cursordiv*ROWS)+cursormod + end + end + if @cursorpos!=oldcursor # Cursor position changed + @sprites["cursor"].setCursorPos(@cursorpos) + pbPlayCursorSE() + return true + else + return false + end + end + + def pbEntry + ret="" + loop do + Graphics.update + Input.update + pbUpdate + next if pbMoveCursor + if Input.trigger?(Input::F5) + pbChangeTab + elsif Input.trigger?(Input::A) + @cursorpos = OK + @sprites["cursor"].setCursorPos(@cursorpos) + elsif Input.trigger?(Input::B) + @helper.delete + pbPlayCancelSE() + pbUpdateOverlay + elsif Input.trigger?(Input::C) + case @cursorpos + when BACK # Backspace + @helper.delete + pbPlayCancelSE() + pbUpdateOverlay + when OK # Done + pbSEPlay("GUI naming confirm") + if @helper.length>=@minlength + ret=@helper.text + break + end + when MODE1 + pbChangeTab(0) if @mode!=0 + when MODE2 + pbChangeTab(1) if @mode!=1 + when MODE3 + pbChangeTab(2) if @mode!=2 + else + cursormod=@cursorpos%ROWS + cursordiv=@cursorpos/ROWS + charpos=cursordiv*(ROWS)+cursormod + chset=@@Characters[@mode][0] + if @helper.length>=@maxlength + @helper.delete + end + @helper.insert(chset[charpos]) + pbPlayCursorSE() + if @helper.length>=@maxlength + @cursorpos=OK + @sprites["cursor"].setCursorPos(@cursorpos) + end + pbUpdateOverlay + end + end + end + Input.update + return ret + end + + def pbEndScene + pbFadeOutAndHide(@sprites) { pbUpdate } + for bitmap in @bitmaps + bitmap.dispose if bitmap + end + @bitmaps.clear + pbDisposeSpriteHash(@sprites) + @viewport.dispose + end +end + + + +class PokemonEntry + def initialize(scene) + @scene=scene + end + + def pbStartScreen(helptext,minlength,maxlength,initialText,mode=-1,pokemon=nil) + @scene.pbStartScene(helptext,minlength,maxlength,initialText,mode,pokemon) + ret=@scene.pbEntry + @scene.pbEndScene + return ret + end +end + + + +#=============================================================================== +# Interpreter functions for naming the player +#=============================================================================== +class Interpreter + def command_303 + if $Trainer + $Trainer.name=pbEnterPlayerName(_INTL("Your name?"),1,@parameters[1],$Trainer.name) + return true + end + if $game_actors && $data_actors && $data_actors[@parameters[0]] != nil + # Set battle abort flag + $game_temp.battle_abort = true + ret="" + pbFadeOutIn { + sscene=PokemonEntryScene.new + sscreen=PokemonEntry.new(sscene) + $game_actors[@parameters[0]].name=sscreen.pbStartScreen( + _INTL("Enter {1}'s name.",$game_actors[@parameters[0]].name), + 1,@parameters[1],$game_actors[@parameters[0]].name) + } + end + return true + end +end + + + +class Game_Interpreter + def command_303 + if $Trainer + $Trainer.name=pbEnterPlayerName(_INTL("Your name?"),1,@params[1],$Trainer.name) + return true + end + if $game_actors && $data_actors && $data_actors[@params[0]] != nil + # Set battle abort flag + ret="" + pbFadeOutIn { + sscene=PokemonEntryScene.new + sscreen=PokemonEntry.new(sscene) + $game_actors[@params[0]].name=sscreen.pbStartScreen( + _INTL("Enter {1}'s name.",$game_actors[@params[0]].name), + 1,@params[1],$game_actors[@params[0]].name) + } + end + return true + end +end + + + +#=============================================================================== +# +#=============================================================================== +def pbEnterText(helptext,minlength,maxlength,initialText="",mode=0,pokemon=nil,nofadeout=false) + ret="" + if ($PokemonSystem.textinput==1 rescue false) # Keyboard + pbFadeOutIn(99999,nofadeout) { + sscene=PokemonEntryScene.new + sscreen=PokemonEntry.new(sscene) + ret=sscreen.pbStartScreen(helptext,minlength,maxlength,initialText,mode,pokemon) + } + else # Cursor + pbFadeOutIn(99999,nofadeout) { + sscene=PokemonEntryScene2.new + sscreen=PokemonEntry.new(sscene) + ret=sscreen.pbStartScreen(helptext,minlength,maxlength,initialText,mode,pokemon) + } + end + return ret +end + +def pbEnterPlayerName(helptext,minlength,maxlength,initialText="",nofadeout=false) + return pbEnterText(helptext,minlength,maxlength,initialText,1,nil,nofadeout) +end + +def pbEnterPokemonName(helptext,minlength,maxlength,initialText="",pokemon=nil,nofadeout=false) + return pbEnterText(helptext,minlength,maxlength,initialText,2,pokemon,nofadeout) +end + +def pbEnterNPCName(helptext,minlength,maxlength,initialText="",id=0,nofadeout=false) + return pbEnterText(helptext,minlength,maxlength,initialText,3,id,nofadeout) +end + +def pbEnterBoxName(helptext,minlength,maxlength,initialText="",nofadeout=false) + return pbEnterText(helptext,minlength,maxlength,initialText,4,nil,nofadeout) +end + +def pbFreeText(msgwindow,currenttext,passwordbox,maxlength,width=240) + window=Window_TextEntry_Keyboard.new(currenttext,0,0,width,64) + ret="" + window.maxlength=maxlength + window.visible=true + window.z=99999 + pbPositionNearMsgWindow(window,msgwindow,:right) + window.text=currenttext + window.passwordChar="*" if passwordbox + $fullInputUpdate = true + loop do + Graphics.update + Input.update + if Input.trigger?(Input::ESC) + ret=currenttext + break + elsif Input.trigger?(Input::ENTER) + ret=window.text + break + end + window.update + msgwindow.update if msgwindow + yield if block_given? + end + $fullInputUpdate = false + window.dispose + Input.update + return ret +end + +def pbMessageFreeText(message,currenttext,passwordbox,maxlength,width=240,&block) + msgwindow=pbCreateMessageWindow + retval=pbMessageDisplay(msgwindow,message,true, + proc { |msgwindow| + next pbFreeText(msgwindow,currenttext,passwordbox,maxlength,width,&block) + },&block) + pbDisposeMessageWindow(msgwindow) + return retval +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/010_EventScene.rb b/Data/Scripts/008_Objects and windows/010_EventScene.rb new file mode 100644 index 000000000..0b9f16a9a --- /dev/null +++ b/Data/Scripts/008_Objects and windows/010_EventScene.rb @@ -0,0 +1,709 @@ +def getCubicPoint2(src,t) + x0 = src[0]; y0 = src[1] + cx0 = src[2]; cy0 = src[3] + cx1 = src[4]; cy1 = src[5] + x1 = src[6]; y1 = src[7] + + x1 = cx1+(x1-cx1)*t + x0 = x0+(cx0-x0)*t + cx0 = cx0+(cx1-cx0)*t + cx1 = cx0+(x1-cx0)*t + cx0 = x0+(cx0-x0)*t + cx = cx0+(cx1-cx0)*t + # a = x1 - 3 * cx1 + 3 * cx0 - x0 + # b = 3 * (cx1 - 2 * cx0 + x0) + # c = 3 * (cx0 - x0) + # d = x0 + # cx = a*t*t*t + b*t*t + c*t + d + y1 = cy1+(y1-cy1)*t + y0 = y0+(cy0-y0)*t + cy0 = cy0+(cy1-cy0)*t + cy1 = cy0+(y1-cy0)*t + cy0 = y0+(cy0-y0)*t + cy = cy0+(cy1-cy0)*t + # a = y1 - 3 * cy1 + 3 * cy0 - y0 + # b = 3 * (cy1 - 2 * cy0 + y0) + # c = 3 * (cy0 - y0) + # d = y0 + # cy = a*t*t*t + b*t*t + c*t + d + return [cx,cy] +end + + + +class Processes + XY = 0 + DeltaXY = 1 + Z = 2 + Curve = 3 + Zoom = 4 + Angle = 5 + Tone = 6 + Color = 7 + Hue = 8 + Opacity = 9 + Visible = 10 + BlendType = 11 + SE = 12 + Name = 13 + Origin = 14 + Src = 15 + SrcSize = 16 + CropBottom = 17 +end + + + +class PictureEx + attr_accessor :x # x-coordinate + attr_accessor :y # y-coordinate + attr_accessor :z # z value + attr_accessor :zoom_x # x directional zoom rate + attr_accessor :zoom_y # y directional zoom rate + attr_accessor :angle # rotation angle + attr_accessor :tone # tone + attr_accessor :color # color + attr_accessor :hue # filename hue + attr_accessor :opacity # opacity level + attr_accessor :visible # visibility boolean + attr_accessor :blend_type # blend method + attr_accessor :name # file name + attr_accessor :origin # starting point + attr_reader :src_rect # source rect + attr_reader :cropBottom # crops sprite to above this y-coordinate + attr_reader :frameUpdates # Array of processes updated in a frame + + def initialize(z) + # process: [type, delay, total_duration, frame_counter, cb, etc.] + @processes = [] + @x = 0.0 + @y = 0.0 + @z = z + @zoom_x = 100.0 + @zoom_y = 100.0 + @angle = 0 + @rotate_speed = 0 + @tone = Tone.new(0, 0, 0, 0) + @tone_duration = 0 + @color = Color.new(0, 0, 0, 0) + @hue = 0 + @opacity = 255.0 + @visible = true + @blend_type = 0 + @name = "" + @origin = PictureOrigin::TopLeft + @src_rect = Rect.new(0,0,-1,-1) + @cropBottom = -1 + @frameUpdates = [] + end + + def callback(cb) + if cb.is_a?(Proc); proc.call(self) + elsif cb.is_a?(Array); cb[0].method(cb[1]).call(self) + elsif cb.is_a?(Method); cb.call(self) + end + end + + def setCallback(delay, cb=nil) + delay = ensureDelayAndDuration(delay) + @processes.push([nil,delay,0,0,cb]) + end + + def running? + return @processes.length>0 + end + + def totalDuration + ret = 0 + for process in @processes + dur = process[1]+process[2] + ret = dur if dur>ret + end + ret *= 20.0/Graphics.frame_rate + return ret.to_i + end + + def ensureDelayAndDuration(delay, duration=nil) + delay = self.totalDuration if delay<0 + delay *= Graphics.frame_rate/20.0 + if !duration.nil? + duration *= Graphics.frame_rate/20.0 + return delay.to_i, duration.to_i + end + return delay.to_i + end + + def ensureDelay(delay) + return ensureDelayAndDuration(delay) + end + + # speed is the angle to change by in 1/20 of a second. @rotate_speed is the + # angle to change by per frame. + # NOTE: This is not compatible with manually changing the angle at a certain + # point. If you make a sprite auto-rotate, you should not try to alter + # the angle another way too. + def rotate(speed) + @rotate_speed = speed*20.0/Graphics.frame_rate + while @rotate_speed<0; @rotate_speed += 360; end + @rotate_speed %= 360 + end + + def erase + self.name = "" + end + + def clearProcesses + @processes = [] + end + + def adjustPosition(xOffset, yOffset) + for process in @processes + next if process[0]!=Processes::XY + process[5] += xOffset + process[6] += yOffset + process[7] += xOffset + process[8] += yOffset + end + end + + def move(delay, duration, origin, x, y, zoom_x=100.0, zoom_y=100.0, opacity=255) + setOrigin(delay,duration,origin) + moveXY(delay,duration,x,y) + moveZoomXY(delay,duration,zoom_x,zoom_y) + moveOpacity(delay,duration,opacity) + end + + def moveXY(delay, duration, x, y, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::XY,delay,duration,0,cb,@x,@y,x,y]) + end + + def setXY(delay, x, y, cb=nil) + moveXY(delay,0,x,y,cb) + end + + def moveCurve(delay, duration, x1, y1, x2, y2, x3, y3, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Curve,delay,duration,0,cb,[@x,@y,x1,y1,x2,y2,x3,y3]]) + end + + def moveDelta(delay, duration, x, y, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::DeltaXY,delay,duration,0,cb,@x,@y,x,y]) + end + + def setDelta(delay, x, y, cb=nil) + moveDelta(delay,0,x,y,cb) + end + + def moveZ(delay, duration, z, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Z,delay,duration,0,cb,@z,z]) + end + + def setZ(delay, z, cb=nil) + moveZ(delay,0,z,cb) + end + + def moveZoomXY(delay, duration, zoom_x, zoom_y, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Zoom,delay,duration,0,cb,@zoom_x,@zoom_y,zoom_x,zoom_y]) + end + + def setZoomXY(delay, zoom_x, zoom_y, cb=nil) + moveZoomXY(delay,0,zoom_x,zoom_y,cb) + end + + def moveZoom(delay, duration, zoom, cb=nil) + moveZoomXY(delay,duration,zoom,zoom,cb) + end + + def setZoom(delay, zoom, cb=nil) + moveZoomXY(delay,0,zoom,zoom,cb) + end + + def moveAngle(delay, duration, angle, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Angle,delay,duration,0,cb,@angle,angle]) + end + + def setAngle(delay, angle, cb=nil) + moveAngle(delay,0,angle,cb) + end + + def moveTone(delay, duration, tone, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + target = (tone) ? tone.clone : Tone.new(0,0,0,0) + @processes.push([Processes::Tone,delay,duration,0,cb,@tone.clone,target]) + end + + def setTone(delay, tone, cb=nil) + moveTone(delay,0,tone,cb) + end + + def moveColor(delay, duration, color, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + target = (color) ? color.clone : Color.new(0,0,0,0) + @processes.push([Processes::Color,delay,duration,0,cb,@color.clone,target]) + end + + def setColor(delay, color, cb=nil) + moveColor(delay,0,color,cb) + end + + # Hue changes don't actually work. + def moveHue(delay, duration, hue, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Hue,delay,duration,0,cb,@hue,hue]) + end + + # Hue changes don't actually work. + def setHue(delay, hue, cb=nil) + moveHue(delay,0,hue,cb) + end + + def moveOpacity(delay, duration, opacity, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Opacity,delay,duration,0,cb,@opacity,opacity]) + end + + def setOpacity(delay, opacity, cb=nil) + moveOpacity(delay,0,opacity,cb) + end + + def setVisible(delay, visible, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::Visible,delay,0,0,cb,visible]) + end + + # Only values of 0 (normal), 1 (additive) and 2 (subtractive) are allowed. + def setBlendType(delay, blend, cb=nil) + delay = ensureDelayAndDuration(delay) + @processes.push([Processes::BlendType,delay,0,0,cb,blend]) + end + + def setSE(delay, seFile, volume=nil, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::SE,delay,0,0,cb,seFile,volume]) + end + + def setName(delay, name, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::Name,delay,0,0,cb,name]) + end + + def setOrigin(delay, origin, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::Origin,delay,0,0,cb,origin]) + end + + def setSrc(delay, srcX, srcY, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::Src,delay,0,0,cb,srcX,srcY]) + end + + def setSrcSize(delay, srcWidth, srcHeight, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::SrcSize,delay,0,0,cb,srcWidth,srcHeight]) + end + + # Used to cut Pokémon sprites off when they faint and sink into the ground. + def setCropBottom(delay, y, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::CropBottom,delay,0,0,cb,y]) + end + + def update + procEnded = false + @frameUpdates.clear + for i in 0...@processes.length + process = @processes[i] + # Decrease delay of processes that are scheduled to start later + if process[1]>=0 + # Set initial values if the process will start this frame + if process[1]==0 + case process[0] + when Processes::XY + process[5] = @x + process[6] = @y + when Processes::DeltaXY + process[5] = @x + process[6] = @y + process[7] += @x + process[8] += @y + when Processes::Curve + process[5][0] = @x + process[5][1] = @y + when Processes::Z + process[5] = @z + when Processes::Zoom + process[5] = @zoom_x + process[6] = @zoom_y + when Processes::Angle + process[5] = @angle + when Processes::Tone + process[5] = @tone.clone + when Processes::Color + process[5] = @color.clone + when Processes::Hue + process[5] = @hue + when Processes::Opacity + process[5] = @opacity + end + end + # Decrease delay counter + process[1] -= 1 + # Process hasn't started yet, skip to the next one + next if process[1]>=0 + end + # Update process + @frameUpdates.push(process[0]) if !@frameUpdates.include?(process[0]) + fra = (process[2]==0) ? 1 : process[3] # Frame counter + dur = (process[2]==0) ? 1 : process[2] # Total duration of process + case process[0] + when Processes::XY, Processes::DeltaXY + @x = process[5] + fra * (process[7] - process[5]) / dur + @y = process[6] + fra * (process[8] - process[6]) / dur + when Processes::Curve + @x, @y = getCubicPoint2(process[5],fra.to_f/dur) + when Processes::Z + @z = process[5] + fra * (process[6] - process[5]) / dur + when Processes::Zoom + @zoom_x = process[5] + fra * (process[7] - process[5]) / dur + @zoom_y = process[6] + fra * (process[8] - process[6]) / dur + when Processes::Angle + @angle = process[5] + fra * (process[6] - process[5]) / dur + when Processes::Tone + @tone.red = process[5].red + fra * (process[6].red - process[5].red) / dur + @tone.green = process[5].green + fra * (process[6].green - process[5].green) / dur + @tone.blue = process[5].blue + fra * (process[6].blue - process[5].blue) / dur + @tone.gray = process[5].gray + fra * (process[6].gray - process[5].gray) / dur + when Processes::Color + @color.red = process[5].red + fra * (process[6].red - process[5].red) / dur + @color.green = process[5].green + fra * (process[6].green - process[5].green) / dur + @color.blue = process[5].blue + fra * (process[6].blue - process[5].blue) / dur + @color.alpha = process[5].alpha + fra * (process[6].alpha - process[5].alpha) / dur + when Processes::Hue + @hue = (process[6] - process[5]).to_f / dur + when Processes::Opacity + @opacity = process[5] + fra * (process[6] - process[5]) / dur + when Processes::Visible + @visible = process[5] + when Processes::BlendType + @blend_type = process[5] + when Processes::SE + pbSEPlay(process[5],process[6]) + when Processes::Name + @name = process[5] + when Processes::Origin + @origin = process[5] + when Processes::Src + @src_rect.x = process[5] + @src_rect.y = process[6] + when Processes::SrcSize + @src_rect.width = process[5] + @src_rect.height = process[6] + when Processes::CropBottom + @cropBottom = process[5] + end + # Increase frame counter + process[3] += 1 + if process[3]>process[2] + # Process has ended, erase it + callback(process[4]) if process[4] + @processes[i] = nil + procEnded = true + end + end + # Clear out empty spaces in @processes array caused by finished processes + @processes.compact! if procEnded + # Add the constant rotation speed + if @rotate_speed != 0 + @frameUpdates.push(Processes::Angle) if !@frameUpdates.include?(Processes::Angle) + @angle += @rotate_speed + while @angle<0; @angle += 360; end + @angle %= 360 + end + end +end + + + +def setPictureSprite(sprite, picture, iconSprite=false) + return if picture.frameUpdates.length==0 + for i in 0...picture.frameUpdates.length + case picture.frameUpdates[i] + when Processes::XY, Processes::DeltaXY + sprite.x = picture.x.round + sprite.y = picture.y.round + when Processes::Z + sprite.z = picture.z + when Processes::Zoom + sprite.zoom_x = picture.zoom_x / 100.0 + sprite.zoom_y = picture.zoom_y / 100.0 + when Processes::Angle + sprite.angle = picture.angle + when Processes::Tone + sprite.tone = picture.tone + when Processes::Color + sprite.color = picture.color + when Processes::Hue + # This doesn't do anything. + when Processes::BlendType + sprite.blend_type = picture.blend_type + when Processes::Opacity + sprite.opacity = picture.opacity + when Processes::Visible + sprite.visible = picture.visible + when Processes::Name + sprite.name = picture.name if iconSprite && sprite.name != picture.name + when Processes::Origin + case picture.origin + when PictureOrigin::TopLeft, PictureOrigin::Left, PictureOrigin::BottomLeft + sprite.ox = 0 + when PictureOrigin::Top, PictureOrigin::Center, PictureOrigin::Bottom + sprite.ox = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.width/2 : 0 + when PictureOrigin::TopRight, PictureOrigin::Right, PictureOrigin::BottomRight + sprite.ox = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.width : 0 + end + case picture.origin + when PictureOrigin::TopLeft, PictureOrigin::Top, PictureOrigin::TopRight + sprite.oy = 0 + when PictureOrigin::Left, PictureOrigin::Center, PictureOrigin::Right + sprite.oy = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.height/2 : 0 + when PictureOrigin::BottomLeft, PictureOrigin::Bottom, PictureOrigin::BottomRight + sprite.oy = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.height : 0 + end + when Processes::Src + next unless iconSprite && sprite.src_rect + sprite.src_rect.x = picture.src_rect.x + sprite.src_rect.y = picture.src_rect.y + when Processes::SrcSize + next unless iconSprite && sprite.src_rect + sprite.src_rect.width = picture.src_rect.width + sprite.src_rect.height = picture.src_rect.height + end + end + if iconSprite && sprite.src_rect && picture.cropBottom>=0 + spriteBottom = sprite.y-sprite.oy+sprite.src_rect.height + if spriteBottom>picture.cropBottom + sprite.src_rect.height = [picture.cropBottom-sprite.y+sprite.oy,0].max + end + end +end + +def setPictureIconSprite(sprite, picture) + setPictureSprite(sprite,picture,true) +end + + + +class PictureOrigin + TopLeft = 0 + Center = 1 + TopRight = 2 + BottomLeft = 3 + LowerLeft = 3 + BottomRight = 4 + LowerRight = 4 + Top = 5 + Bottom = 6 + Left = 7 + Right = 8 +end + + + +def pbTextBitmap(text, maxwidth=Graphics.width) + dims = [] + tmp = Bitmap.new(maxwidth,Graphics.height) + pbSetSystemFont(tmp) + drawFormattedTextEx(tmp,0,0,maxwidth,text,Color.new(248,248,248),Color.new(168,184,184)) + return tmp +end + + + +class PictureSprite < SpriteWrapper + def initialize(viewport, picture) + super(viewport) + @picture = picture + @pictureBitmap = nil + @customBitmap = nil + @customBitmapIsBitmap = true + @hue = 0 + update + end + + def dispose + @pictureBitmap.dispose if @pictureBitmap + super + end + + # Doesn't free the bitmap + def setCustomBitmap(bitmap) + @customBitmap = bitmap + @customBitmapIsBitmap = @customBitmap.is_a?(Bitmap) + end + + def update + super + @pictureBitmap.update if @pictureBitmap + # If picture file name is different from current one + if @customBitmap && @picture.name=="" + self.bitmap = (@customBitmapIsBitmap) ? @customBitmap : @customBitmap.bitmap + elsif @picture_name != @picture.name || @picture.hue.to_i != @hue.to_i + # Remember file name to instance variables + @picture_name = @picture.name + @hue = @picture.hue.to_i + # If file name is not empty + if @picture_name == "" + @pictureBitmap.dispose if @pictureBitmap + @pictureBitmap = nil + self.visible = false + return + end + # Get picture graphic + @pictureBitmap.dispose if @pictureBitmap + @pictureBitmap = AnimatedBitmap.new(@picture_name, @hue) + self.bitmap = (@pictureBitmap) ? @pictureBitmap.bitmap : nil + elsif @picture_name == "" + # Set sprite to invisible + self.visible = false + return + end + setPictureSprite(self,@picture) + end +end + + + +class EventScene + attr_accessor :onCTrigger,:onBTrigger,:onUpdate + + def initialize(viewport=nil) + @viewport = viewport + @onCTrigger = Event.new + @onBTrigger = Event.new + @onUpdate = Event.new + @pictures = [] + @picturesprites = [] + @usersprites = [] + @disposed = false + end + + def dispose + return if disposed? + for sprite in @picturesprites + sprite.dispose + end + for sprite in @usersprites + sprite.dispose + end + @onCTrigger.clear + @onBTrigger.clear + @onUpdate.clear + @pictures.clear + @picturesprites.clear + @usersprites.clear + @disposed = true + end + + def disposed? + return @disposed + end + + def addBitmap(x, y, bitmap) + # _bitmap_ can be a Bitmap or an AnimatedBitmap + # (update method isn't called if it's animated) + # EventScene doesn't take ownership of the passed-in bitmap + num = @pictures.length + picture = PictureEx.new(num) + picture.setXY(0,x,y) + picture.setVisible(0,true) + @pictures[num] = picture + @picturesprites[num] = PictureSprite.new(@viewport,picture) + @picturesprites[num].setCustomBitmap(bitmap) + return picture + end + + def addLabel(x, y, width, text) + addBitmap(x,y,pbTextBitmap(text,width)) + end + + def addImage(x, y, name) + num = @pictures.length + picture = PictureEx.new(num) + picture.name = name + picture.setXY(0,x,y) + picture.setVisible(0,true) + @pictures[num] = picture + @picturesprites[num] = PictureSprite.new(@viewport,picture) + return picture + end + + def addUserSprite(sprite) + @usersprites.push(sprite) + end + + def getPicture(num) + return @pictures[num] + end + + def wait(frames) + frames.times { update } + end + + def pictureWait(extraframes=0) + loop do + hasRunning = false + for pic in @pictures + hasRunning = true if pic.running? + end + break if !hasRunning + update + end + extraframes.times { update } + end + + def update + return if disposed? + Graphics.update + Input.update + for picture in @pictures + picture.update + end + for sprite in @picturesprites + sprite.update + end + for sprite in @usersprites + next if !sprite || sprite.disposed? || !sprite.is_a?(Sprite) + sprite.update + end + @onUpdate.trigger(self) + if Input.trigger?(Input::B) + @onBTrigger.trigger(self) + elsif Input.trigger?(Input::C) + @onCTrigger.trigger(self) + end + end + + def main + while !disposed? + update + end + end +end + + + +def pbEventScreen(cls) + pbFadeOutIn { + viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + viewport.z = 99999 + PBDebug.logonerr { + cls.new(viewport).main + } + viewport.dispose + } +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/011_Interpolators.rb b/Data/Scripts/008_Objects and windows/011_Interpolators.rb new file mode 100644 index 000000000..8e69413ab --- /dev/null +++ b/Data/Scripts/008_Objects and windows/011_Interpolators.rb @@ -0,0 +1,172 @@ +class Interpolator + ZOOM_X = 1 + ZOOM_Y = 2 + X = 3 + Y = 4 + OPACITY = 5 + COLOR = 6 + WAIT = 7 + + def initialize + @tweening = false + @tweensteps = [] + @sprite = nil + @frames = 0 + @step = 0 + end + + def tweening? + return @tweening + end + + def tween(sprite,items,frames) + @tweensteps = [] + if sprite && !sprite.disposed? && frames>0 + @frames = frames + @step = 0 + @sprite = sprite + for item in items + case item[0] + when ZOOM_X + @tweensteps[item[0]] = [sprite.zoom_x,item[1]-sprite.zoom_x] + when ZOOM_Y + @tweensteps[item[0]] = [sprite.zoom_y,item[1]-sprite.zoom_y] + when X + @tweensteps[item[0]] = [sprite.x,item[1]-sprite.x] + when Y + @tweensteps[item[0]] = [sprite.y,item[1]-sprite.y] + when OPACITY + @tweensteps[item[0]] = [sprite.opacity,item[1]-sprite.opacity] + when COLOR + @tweensteps[item[0]] = [sprite.color.clone,Color.new( + item[1].red-sprite.color.red, + item[1].green-sprite.color.green, + item[1].blue-sprite.color.blue, + item[1].alpha-sprite.color.alpha + )] + end + end + @tweening = true + end + end + + def update + if @tweening + t = (@step*1.0)/@frames + for i in 0...@tweensteps.length + item = @tweensteps[i] + next if !item + case i + when ZOOM_X + @sprite.zoom_x = item[0]+item[1]*t + when ZOOM_Y + @sprite.zoom_y = item[0]+item[1]*t + when X + @sprite.x = item[0]+item[1]*t + when Y + @sprite.y = item[0]+item[1]*t + when OPACITY + @sprite.opacity = item[0]+item[1]*t + when COLOR + @sprite.color = Color.new( + item[0].red+item[1].red*t, + item[0].green+item[1].green*t, + item[0].blue+item[1].blue*t, + item[0].alpha+item[1].alpha*t + ) + end + end + @step += 1 + if @step==@frames + @step = 0 + @frames = 0 + @tweening = false + end + end + end +end + + + +class RectInterpolator + def initialize(oldrect,newrect,frames) + restart(oldrect,newrect,frames) + end + + def restart(oldrect,newrect,frames) + @oldrect = oldrect + @newrect = newrect + @frames = [frames,1].max + @curframe = 0 + @rect = oldrect.clone + end + + def set(rect) + rect.set(@rect.x,@rect.y,@rect.width,@rect.height) + end + + def done? + @curframe>@frames + end + + def update + return if done? + t = (@curframe*1.0/@frames) + x1 = @oldrect.x + x2 = @newrect.x + x = x1+t*(x2-x1) + y1 = @oldrect.y + y2 = @newrect.y + y = y1+t*(y2-y1) + rx1 = @oldrect.x+@oldrect.width + rx2 = @newrect.x+@newrect.width + rx = rx1+t*(rx2-rx1) + ry1 = @oldrect.y+@oldrect.height + ry2 = @newrect.y+@newrect.height + ry = ry1+t*(ry2-ry1) + minx = xrx ? x : rx + miny = yry ? y : ry + @rect.set(minx,miny,maxx-minx,maxy-miny) + @curframe += 1 + end +end + + + +class PointInterpolator + attr_reader :x + attr_reader :y + + def initialize(oldx,oldy,newx,newy,frames) + restart(oldx,oldy,newx,newy,frames) + end + + def restart(oldx,oldy,newx,newy,frames) + @oldx = oldx + @oldy = oldy + @newx = newx + @newy = newy + @frames = frames + @curframe = 0 + @x = oldx + @y = oldy + end + + def done? + @curframe>@frames + end + + def update + return if done? + t = (@curframe*1.0/@frames) + rx1 = @oldx + rx2 = @newx + @x = rx1+t*(rx2-rx1) + ry1 = @oldy + ry2 = @newy + @y = ry1+t*(ry2-ry1) + @curframe += 1 + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/001_Scene_Map.rb b/Data/Scripts/009_Scenes/001_Scene_Map.rb new file mode 100644 index 000000000..52bb30121 --- /dev/null +++ b/Data/Scripts/009_Scenes/001_Scene_Map.rb @@ -0,0 +1,239 @@ +#=============================================================================== +# ** Modified Scene_Map class for Pokémon. +#------------------------------------------------------------------------------- +# +#=============================================================================== +class Scene_Map + attr_reader :spritesetGlobal + + def spriteset + for i in @spritesets.values + return i if i.map==$game_map + end + return @spritesets.values[0] + end + + def createSpritesets + @spritesetGlobal = Spriteset_Global.new + @spritesets = {} + for map in $MapFactory.maps + @spritesets[map.map_id] = Spriteset_Map.new(map) + end + $MapFactory.setSceneStarted(self) + updateSpritesets + end + + def createSingleSpriteset(map) + temp = $scene.spriteset.getAnimations + @spritesets[map] = Spriteset_Map.new($MapFactory.maps[map]) + $scene.spriteset.restoreAnimations(temp) + $MapFactory.setSceneStarted(self) + updateSpritesets + end + + def disposeSpritesets + return if !@spritesets + for i in @spritesets.keys + next if !@spritesets[i] + @spritesets[i].dispose + @spritesets[i] = nil + end + @spritesets.clear + @spritesets = {} + @spritesetGlobal.dispose + @spritesetGlobal = nil + end + + def autofade(mapid) + playingBGM = $game_system.playing_bgm + playingBGS = $game_system.playing_bgs + return if !playingBGM && !playingBGS + map = pbLoadRxData(sprintf("Data/Map%03d",mapid)) + if playingBGM && map.autoplay_bgm + if (PBDayNight.isNight? rescue false) + pbBGMFade(0.8) if playingBGM.name!=map.bgm.name && playingBGM.name!=map.bgm.name+"_n" + else + pbBGMFade(0.8) if playingBGM.name!=map.bgm.name + end + end + if playingBGS && map.autoplay_bgs + pbBGMFade(0.8) if playingBGS.name!=map.bgs.name + end + Graphics.frame_reset + end + + def transfer_player(cancelVehicles=true) + $game_temp.player_transferring = false + pbCancelVehicles($game_temp.player_new_map_id) if cancelVehicles + autofade($game_temp.player_new_map_id) + pbBridgeOff + if $game_map.map_id!=$game_temp.player_new_map_id + $MapFactory.setup($game_temp.player_new_map_id) + end + $game_player.moveto($game_temp.player_new_x, $game_temp.player_new_y) + case $game_temp.player_new_direction + when 2; $game_player.turn_down + when 4; $game_player.turn_left + when 6; $game_player.turn_right + when 8; $game_player.turn_up + end + $game_player.straighten + $game_map.update + disposeSpritesets + GC.start + createSpritesets + if $game_temp.transition_processing + $game_temp.transition_processing = false + Graphics.transition(20) + end + $game_map.autoplay + Graphics.frame_reset + Input.update + end + + def call_name + $game_temp.name_calling = false + $game_player.straighten + $game_map.update + end + + def call_menu + $game_temp.menu_calling = false + $game_temp.in_menu = true + $game_player.straighten + $game_map.update + sscene = PokemonPauseMenu_Scene.new + sscreen = PokemonPauseMenu.new(sscene) + sscreen.pbStartPokemonMenu + $game_temp.in_menu = false + end + + def call_debug + $game_temp.debug_calling = false + pbPlayDecisionSE + $game_player.straighten + pbFadeOutIn { pbDebugMenu } + end + + def miniupdate + $PokemonTemp.miniupdate = true + loop do + updateMaps + $game_player.update + $game_system.update + $game_screen.update + break unless $game_temp.player_transferring + transfer_player + break if $game_temp.transition_processing + end + updateSpritesets + $PokemonTemp.miniupdate = false + end + + def updateMaps + for map in $MapFactory.maps + map.update + end + $MapFactory.updateMaps(self) + end + + def updateSpritesets + @spritesets = {} if !@spritesets + keys = @spritesets.keys.clone + for i in keys + if !$MapFactory.hasMap?(i) + @spritesets[i].dispose if @spritesets[i] + @spritesets[i] = nil + @spritesets.delete(i) + else + @spritesets[i].update + end + end + @spritesetGlobal.update + for map in $MapFactory.maps + @spritesets[map.map_id] = Spriteset_Map.new(map) if !@spritesets[map.map_id] + end + Events.onMapUpdate.trigger(self) + end + + def update + loop do + updateMaps + pbMapInterpreter.update + $game_player.update + $game_system.update + $game_screen.update + break unless $game_temp.player_transferring + transfer_player + break if $game_temp.transition_processing + end + updateSpritesets + if $game_temp.to_title + $scene = pbCallTitle + return + end + if $game_temp.transition_processing + $game_temp.transition_processing = false + if $game_temp.transition_name == "" + Graphics.transition(20) + else + Graphics.transition(40, "Graphics/Transitions/" + $game_temp.transition_name) + end + end + return if $game_temp.message_window_showing + if !pbMapInterpreterRunning? + if Input.trigger?(Input::C) + $PokemonTemp.hiddenMoveEventCalling = true + elsif Input.trigger?(Input::B) + unless $game_system.menu_disabled or $game_player.moving? + $game_temp.menu_calling = true + $game_temp.menu_beep = true + end + elsif Input.trigger?(Input::F5) + unless $game_player.moving? + $PokemonTemp.keyItemCalling = true + end + elsif Input.trigger?(Input::A) + if $PokemonSystem.runstyle==1 + $PokemonGlobal.runtoggle = !$PokemonGlobal.runtoggle + end + elsif Input.press?(Input::F9) + $game_temp.debug_calling = true if $DEBUG + end + end + unless $game_player.moving? + if $game_temp.name_calling; call_name + elsif $game_temp.menu_calling; call_menu + elsif $game_temp.debug_calling; call_debug +# elsif $game_temp.battle_calling; call_battle +# elsif $game_temp.shop_calling; call_shop +# elsif $game_temp.save_calling; call_save + elsif $PokemonTemp.keyItemCalling + $PokemonTemp.keyItemCalling = false + $game_player.straighten + pbUseKeyItem + elsif $PokemonTemp.hiddenMoveEventCalling + $PokemonTemp.hiddenMoveEventCalling = false + $game_player.straighten + Events.onAction.trigger(self) + end + end + end + + def main + createSpritesets + Graphics.transition(20) + loop do + Graphics.update + Input.update + update + break if $scene != self + end + Graphics.freeze + disposeSpritesets + if $game_temp.to_title + Graphics.transition(20) + Graphics.freeze + end + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/002_Scene_Intro.rb b/Data/Scripts/009_Scenes/002_Scene_Intro.rb new file mode 100644 index 000000000..90e99ac56 --- /dev/null +++ b/Data/Scripts/009_Scenes/002_Scene_Intro.rb @@ -0,0 +1,134 @@ +class IntroEventScene < EventScene + TICKS_PER_PIC = 40 # 20 ticks per second, so 2 seconds + TICKS_PER_ENTER_FLASH = 40 + FADE_TICKS = 8 + + def initialize(pics,splash,viewport=nil) + super(nil) + @pics = pics + @splash = splash + @pic = addImage(0,0,"") + @pic.setOpacity(0,0) # set opacity to 0 after waiting 0 frames + @pic2 = addImage(0,0,"") # flashing "Press Enter" picture + @pic2.setOpacity(0,0) + @index = 0 + data_system = pbLoadRxData("Data/System") + pbBGMPlay(data_system.title_bgm) + openPic(self,nil) + end + + def openPic(scene,args) + onCTrigger.clear + @pic.name = "Graphics/Titles/"+@pics[@index] + # fade to opacity 255 in FADE_TICKS ticks after waiting 0 frames + @pic.moveOpacity(0,FADE_TICKS,255) + pictureWait + @timer = 0 # reset the timer + onUpdate.set(method(:picUpdate)) # call picUpdate every frame + onCTrigger.set(method(:closePic)) # call closePic when C key is pressed + end + + def closePic(scene,args) + onUpdate.clear + onCTrigger.clear + @pic.moveOpacity(0,FADE_TICKS,0) + pictureWait + @index += 1 # Move to the next picture + if @index>=@pics.length + openSplash(scene,args) + else + openPic(scene,args) + end + end + + def picUpdate(scene,args) + @timer += 1 + if @timer>TICKS_PER_PIC*Graphics.frame_rate/20 + @timer = 0 + closePic(scene,args) # Close the picture + end + end + + def openSplash(scene,args) + onUpdate.clear + onCTrigger.clear + @pic.name = "Graphics/Titles/"+@splash + @pic.moveOpacity(0,FADE_TICKS,255) + @pic2.name = "Graphics/Titles/start" + @pic2.setXY(0,0,322) + @pic2.setVisible(0,true) + @pic2.moveOpacity(0,FADE_TICKS,255) + pictureWait + onUpdate.set(method(:splashUpdate)) # call splashUpdate every frame + onCTrigger.set(method(:closeSplash)) # call closeSplash when C key is pressed + end + + def closeSplash(scene,args) + onUpdate.clear + onCTrigger.clear + # Play random cry + cry = pbCryFile(1+rand(PBSpecies.maxValue)) + pbSEPlay(cry,80,100) if cry + @pic.moveXY(0,20,0,0) + pictureWait + # Fade out + @pic.moveOpacity(0,FADE_TICKS,0) + @pic2.clearProcesses + @pic2.moveOpacity(0,FADE_TICKS,0) + pbBGMStop(1.0) + pictureWait + scene.dispose # Close the scene + sscene = PokemonLoad_Scene.new + sscreen = PokemonLoadScreen.new(sscene) + sscreen.pbStartLoadScreen + end + + def closeSplashDelete(scene,args) + onUpdate.clear + onCTrigger.clear + # Play random cry + cry = pbCryFile(1+rand(PBSpecies.maxValue)) + pbSEPlay(cry,80,100) if cry + @pic.moveXY(0,20,0,0) + pictureWait + # Fade out + @pic.moveOpacity(0,FADE_TICKS,0) + @pic2.clearProcesses + @pic2.moveOpacity(0,FADE_TICKS,0) + pbBGMStop(1.0) + pictureWait + scene.dispose # Close the scene + sscene = PokemonLoad_Scene.new + sscreen = PokemonLoadScreen.new(sscene) + sscreen.pbStartDeleteScreen + end + + def splashUpdate(scene,args) + # Flashing of "Press Enter" picture + if !@pic2.running? + @pic2.moveOpacity(TICKS_PER_ENTER_FLASH*2/10,TICKS_PER_ENTER_FLASH*4/10,0) + @pic2.moveOpacity(TICKS_PER_ENTER_FLASH*6/10,TICKS_PER_ENTER_FLASH*4/10,255) + end + if Input.press?(Input::DOWN) && + Input.press?(Input::B) && + Input.press?(Input::CTRL) + closeSplashDelete(scene,args) + end + end +end + + + +class Scene_Intro + def initialize(pics, splash = nil) + @pics = pics + @splash = splash + end + + def main + Graphics.transition(0) + @eventscene = IntroEventScene.new(@pics,@splash) + @eventscene.main + Graphics.freeze + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/003_Scene_Controls.rb b/Data/Scripts/009_Scenes/003_Scene_Controls.rb new file mode 100644 index 000000000..f323f8847 --- /dev/null +++ b/Data/Scripts/009_Scenes/003_Scene_Controls.rb @@ -0,0 +1,46 @@ +#============================================================================== +# * Scene_Controls +#------------------------------------------------------------------------------ +# Shows a help screen listing the keyboard controls. +# Display with: +# pbEventScreen(ButtonEventScene) +#============================================================================== +class ButtonEventScene < EventScene + def initialize(viewport=nil) + super + Graphics.freeze + addImage(0,0,"Graphics/Pictures/helpbg") + @labels=[ + addLabel(52*2,13*2,Graphics.width*3/4,_INTL("Moves the main character. Also used to scroll through list entries.")), + addLabel(52*2,53*2,Graphics.width*3/4,_INTL("Used to confirm a choice, check things, and talk to people.")), + addLabel(52*2,93*2,Graphics.width*3/4,_INTL("Used to exit, cancel a choice or mode, and open the pause menu.")), + addLabel(52*2,133*2,Graphics.width*3/4,_INTL("Hold down while walking to run.")), + addLabel(52*2,157*2,Graphics.width*3/4,_INTL("Press to use a registered Key Item.")) + ] + @keys=[ + addImage(26*2,18*2,"Graphics/Pictures/helpArrowKeys"), + addImage(26*2,59*2,"Graphics/Pictures/helpCkey"), + addImage(26*2,99*2,"Graphics/Pictures/helpXkey"), + addImage(26*2,130*2,"Graphics/Pictures/helpZkey"), + addImage(26*2,154*2,"Graphics/Pictures/helpFkey") + ] + for key in @keys + key.origin=PictureOrigin::Top + end + for i in 0...5 # Make everything show (almost) immediately + @keys[i].setOrigin(0,PictureOrigin::Top) + @keys[i].setOpacity(0,255) + end + pictureWait # Update event scene with the changes + Graphics.transition(20) + # Go to next screen when user presses C + onCTrigger.set(method(:pbOnScreen1)) + end + + def pbOnScreen1(scene,args) + # End scene + Graphics.freeze + scene.dispose + Graphics.transition(20) + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/004_Scene_Movie.rb b/Data/Scripts/009_Scenes/004_Scene_Movie.rb new file mode 100644 index 000000000..b9f98e69a --- /dev/null +++ b/Data/Scripts/009_Scenes/004_Scene_Movie.rb @@ -0,0 +1,52 @@ +#=============================================================================== +# ** Scene_Movie class, created by SoundSpawn, fixed by Popper. +#------------------------------------------------------------------------------- +# Instruction +# 1) Movies must be in a new folder called "Movies" in your directory. +# 2) If you call this script from an event, e.g. +# Call Script: $scene = Scene_Movie.new("INTRO") +# 3) Have fun playing movies with this script! +#=============================================================================== +class Scene_Movie + def initialize(movie) + @movie_name = RTP.getPath("Movies\\"+movie+".avi").gsub(/\//,"\\") + end + + def main + @temp = Win32API.pbFindRgssWindow.to_s + movie = Win32API.new('winmm','mciSendString','%w(p,p,l,l)','V') + x=movie.call("open \""+@movie_name+ + "\" alias FILE style 1073741824 parent " + @temp.to_s,0,0,0) + @message = Win32API.new('user32','SendMessage','%w(l,l,l,l)','V') + @detector = Win32API.new('user32','GetSystemMetrics','%w(l)','L') + @width = @detector.call(0) + if @width == 640 + #fullscreen + Graphics.update + sleep(0.1) + Graphics.update + sleep(0.1) + Graphics.update + sleep(0.1) + #fullscreen + end + status = " " * 255 + x=movie.call("play FILE",0,0,0) + loop do + sleep(0.1) + @message.call(@temp.to_i,11,0,0) + Graphics.update + @message.call(@temp.to_i,11,1,0) + Input.update + movie.call("status FILE mode",status,255,0) + true_status = status.unpack("aaaa") + break if true_status.to_s != "play" + if Input.trigger?(Input::B) + movie.call("close FILE",0,0,0) + $scene = Scene_Map.new + break + end + end + $scene = Scene_Map.new + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/005_Scene_Credits.rb b/Data/Scripts/009_Scenes/005_Scene_Credits.rb new file mode 100644 index 000000000..8773afe0d --- /dev/null +++ b/Data/Scripts/009_Scenes/005_Scene_Credits.rb @@ -0,0 +1,231 @@ +# Backgrounds to show in credits. Found in Graphics/Titles/ folder +CreditsBackgroundList = ["credits1","credits2","credits3","credits4","credits5"] +CreditsMusic = "Credits" +CreditsScrollSpeed = 2 +CreditsFrequency = 9 # Number of seconds per credits slide +CREDITS_OUTLINE = Color.new(0,0,128, 255) +CREDITS_SHADOW = Color.new(0,0,0, 100) +CREDITS_FILL = Color.new(255,255,255, 255) + +#============================================================================== +# * Scene_Credits +#------------------------------------------------------------------------------ +# Scrolls the credits you make below. Original Author unknown. +# +## Edited by MiDas Mike so it doesn't play over the Title, but runs by calling +# the following: +# $scene = Scene_Credits.new +# +## New Edit 3/6/2007 11:14 PM by AvatarMonkeyKirby. +# Ok, what I've done is changed the part of the script that was supposed to make +# the credits automatically end so that way they actually end! Yes, they will +# actually end when the credits are finished! So, that will make the people you +# should give credit to now is: Unknown, MiDas Mike, and AvatarMonkeyKirby. +# -sincerly yours, +# Your Beloved +# Oh yea, and I also added a line of code that fades out the BGM so it fades +# sooner and smoother. +# +## New Edit 24/1/2012 by Maruno. +# Added the ability to split a line into two halves with , with each half +# aligned towards the centre. Please also credit me if used. +# +## New Edit 22/2/2012 by Maruno. +# Credits now scroll properly when played with a zoom factor of 0.5. Music can +# now be defined. Credits can't be skipped during their first play. +# +## New Edit 25/3/2020 by Maruno. +# Scroll speed is now independent of frame rate. Now supports non-integer values +# for CreditsScrollSpeed. +# +## New Edit 21/8/2020 by Marin. +# Now automatically inserts the credits from the plugins that have been +# registered through the PluginManager module. +#============================================================================== + +class Scene_Credits + +# This next piece of code is the credits. +#Start Editing +CREDIT=<<_END_ + +Your credits go here. + +Your credits go here. + +Your credits go here. + +Your credits go here. + +Your credits go here. + +{INSERTS_PLUGIN_CREDITS_DO_NOT_REMOVE} +"Pokémon Essentials" was created by: +Flameguru +Poccil (Peter O.) +Maruno + +With contributions from: +AvatarMonkeyKirbyMarin +BoushyMiDas Mike +Brother1440Near Fantastica +FL.PinkMan +Genzai KawakamiPopper +help-14Rataime +IceGod64SoundSpawn +Jacob O. Wobbrockthe__end +KitsuneKoutaVenom12 +Lisa AnthonyWachunga +Luka S.J. +and everyone else who helped out + + + +"RPG Maker XP" by: +Enterbrain + +Pokémon is owned by: +The Pokémon Company +Nintendo +Affiliated with Game Freak + +This is a non-profit fan-made game. +No copyright infringements intended. +Please support the official games! + +_END_ +#Stop Editing + + def main +#------------------------------- +# Animated Background Setup +#------------------------------- + @sprite = IconSprite.new(0,0) + @backgroundList = CreditsBackgroundList + @frameCounter = 0 + # Number of game frames per background frame + @framesPerBackground = CreditsFrequency * Graphics.frame_rate + @sprite.setBitmap("Graphics/Titles/"+@backgroundList[0]) +#------------------ +# Credits text Setup +#------------------ + plugin_credits = "" + PluginManager.plugins.each do |plugin| + pcred = PluginManager.credits(plugin) + plugin_credits << "\"#{plugin}\" version #{PluginManager.version(plugin)}\n" + if pcred.size >= 5 + plugin_credits << pcred[0] + "\n" + i = 1 + until i >= pcred.size + plugin_credits << pcred[i] + "" + (pcred[i + 1] || "") + "\n" + i += 2 + end + else + pcred.each do |name| + plugin_credits << name + "\n" + end + end + plugin_credits << "\n" + end + CREDIT.gsub!(/{INSERTS_PLUGIN_CREDITS_DO_NOT_REMOVE}/, plugin_credits) + credit_lines = CREDIT.split(/\n/) + credit_bitmap = Bitmap.new(Graphics.width,32 * credit_lines.size) + credit_lines.each_index do |i| + line = credit_lines[i] + line = line.split("") + # LINE ADDED: If you use in your own game, you should remove this line + pbSetSystemFont(credit_bitmap) # <--- This line was added + x = 0 + xpos = 0 + align = 1 # Centre align + linewidth = Graphics.width + for j in 0...line.length + if line.length>1 + xpos = (j==0) ? 0 : 20 + Graphics.width/2 + align = (j==0) ? 2 : 0 # Right align : left align + linewidth = Graphics.width/2 - 20 + end + credit_bitmap.font.color = CREDITS_SHADOW + credit_bitmap.draw_text(xpos,i * 32 + 8,linewidth,32,line[j],align) + credit_bitmap.font.color = CREDITS_OUTLINE + credit_bitmap.draw_text(xpos + 2,i * 32 - 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos,i * 32 - 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos - 2,i * 32 - 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos + 2,i * 32,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos - 2,i * 32,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos + 2,i * 32 + 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos,i * 32 + 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos - 2,i * 32 + 2,linewidth,32,line[j],align) + credit_bitmap.font.color = CREDITS_FILL + credit_bitmap.draw_text(xpos,i * 32,linewidth,32,line[j],align) + end + end + @trim = Graphics.height/10 + @realOY = -(Graphics.height-@trim) # -430 + @oyChangePerFrame = CreditsScrollSpeed*20.0/Graphics.frame_rate + @credit_sprite = Sprite.new(Viewport.new(0,@trim,Graphics.width,Graphics.height-(@trim*2))) + @credit_sprite.bitmap = credit_bitmap + @credit_sprite.z = 9998 + @credit_sprite.oy = @realOY + @bg_index = 0 + @zoom_adjustment = 1.0/$ResizeFactor + @last_flag = false +#-------- +# Setup +#-------- + # Stops all audio but background music + previousBGM = $game_system.getPlayingBGM + pbMEStop + pbBGSStop + pbSEStop + pbBGMFade(2.0) + pbBGMPlay(CreditsMusic) + Graphics.transition(20) + loop do + Graphics.update + Input.update + update + break if $scene != self + end + Graphics.freeze + @sprite.dispose + @credit_sprite.dispose + $PokemonGlobal.creditsPlayed = true + pbBGMPlay(previousBGM) + end + + # Check if the credits should be cancelled + def cancel? + if Input.trigger?(Input::C) && $PokemonGlobal.creditsPlayed + $scene = Scene_Map.new + pbBGMFade(1.0) + return true + end + return false + end + + # Checks if credits bitmap has reached its ending point + def last? + if @realOY > @credit_sprite.bitmap.height + @trim + $scene = ($game_map) ? Scene_Map.new : nil + pbBGMFade(2.0) + return true + end + return false + end + + def update + @frameCounter += 1 + # Go to next slide + if @frameCounter >= @framesPerBackground + @frameCounter -= @framesPerBackground + @bg_index += 1 + @bg_index = 0 if @bg_index >= @backgroundList.length + @sprite.setBitmap("Graphics/Titles/"+@backgroundList[@bg_index]) + end + return if cancel? + return if last? + @realOY += @oyChangePerFrame + @credit_sprite.oy = @realOY + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/006_Transitions.rb b/Data/Scripts/009_Scenes/006_Transitions.rb new file mode 100644 index 000000000..6b31eb27f --- /dev/null +++ b/Data/Scripts/009_Scenes/006_Transitions.rb @@ -0,0 +1,1617 @@ +module Graphics + @@transition = nil + STOP_WHILE_TRANSITION = true + + unless defined?(transition_KGC_SpecialTransition) + class << Graphics + alias transition_KGC_SpecialTransition transition + end + + class << Graphics + alias update_KGC_SpecialTransition update + end + end + + def self.transition(duration=8,filename="",vague=20) + duration = duration.floor + if judge_special_transition(duration,filename) + duration = 0 + filename = "" + end + begin + transition_KGC_SpecialTransition(duration,filename,vague) + rescue Exception + if filename!="" + transition_KGC_SpecialTransition(duration,"",vague) + end + end + if STOP_WHILE_TRANSITION && !@_interrupt_transition + while @@transition && !@@transition.disposed? + update + end + end + end + + def self.update + update_KGC_SpecialTransition +=begin + if Graphics.frame_count%40==0 + count=0 + ObjectSpace.each_object(Object) { |o| count += 1 } + echo("Objects: #{count}\r\n") + end +=end + @@transition.update if @@transition && !@@transition.disposed? + @@transition = nil if @@transition && @@transition.disposed? + end + + def self.judge_special_transition(duration,filename) + return false if @_interrupt_transition + ret = true + if @@transition && !@@transition.disposed? + @@transition.dispose + @@transition = nil + end + dc = File.basename(filename).downcase + case dc + # Other coded transitions + when "breakingglass"; @@transition = BreakingGlass.new(duration) + when "rotatingpieces"; @@transition = ShrinkingPieces.new(duration,true) + when "shrinkingpieces"; @@transition = ShrinkingPieces.new(duration,false) + when "splash"; @@transition = SplashTransition.new(duration) + when "random_stripe_v"; @@transition = RandomStripeTransition.new(duration,0) + when "random_stripe_h"; @@transition = RandomStripeTransition.new(duration,1) + when "zoomin"; @@transition = ZoomInTransition.new(duration) + when "scrolldown"; @@transition = ScrollScreen.new(duration,2) + when "scrollleft"; @@transition = ScrollScreen.new(duration,4) + when "scrollright"; @@transition = ScrollScreen.new(duration,6) + when "scrollup"; @@transition = ScrollScreen.new(duration,8) + when "scrolldownleft"; @@transition = ScrollScreen.new(duration,1) + when "scrolldownright"; @@transition = ScrollScreen.new(duration,3) + when "scrollupleft"; @@transition = ScrollScreen.new(duration,7) + when "scrollupright"; @@transition = ScrollScreen.new(duration,9) + when "mosaic"; @@transition = MosaicTransition.new(duration) + # HGSS transitions + when "snakesquares"; @@transition = SnakeSquares.new(duration) + when "diagonalbubbletl"; @@transition = DiagonalBubble.new(duration,0) + when "diagonalbubbletr"; @@transition = DiagonalBubble.new(duration,1) + when "diagonalbubblebl"; @@transition = DiagonalBubble.new(duration,2) + when "diagonalbubblebr"; @@transition = DiagonalBubble.new(duration,3) + when "risingsplash"; @@transition = RisingSplash.new(duration) + when "twoballpass"; @@transition = TwoBallPass.new(duration) + when "spinballsplit"; @@transition = SpinBallSplit.new(duration) + when "threeballdown"; @@transition = ThreeBallDown.new(duration) + when "balldown"; @@transition = BallDown.new(duration) + when "wavythreeballup"; @@transition = WavyThreeBallUp.new(duration) + when "wavyspinball"; @@transition = WavySpinBall.new(duration) + when "fourballburst"; @@transition = FourBallBurst.new(duration) + # Graphic transitions + when ""; @@transition = FadeTransition.new(duration) + else; ret=false + end + Graphics.frame_reset if ret + return ret + end +end + + + +#=============================================================================== +# +#=============================================================================== +class BreakingGlass + def initialize(numframes) + @disposed = false + @numframes = numframes + @opacitychange = (numframes<=0) ? 255 : 255.0/numframes + cx = 6 + cy = 5 + @bitmap = Graphics.snap_to_bitmap + if !@bitmap + @disposed = true + return + end + width = @bitmap.width/cx + height = @bitmap.height/cy + @numtiles = cx*cy + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprites = [] + @offset = [] + @y = [] + for i in 0...@numtiles + @sprites[i] = Sprite.new(@viewport) + @sprites[i].bitmap = @bitmap + @sprites[i].x = width*(i%cx) + @sprites[i].y = height*(i/cx) + @sprites[i].src_rect.set(@sprites[i].x,@sprites[i].y,width,height) + @offset[i] = (rand(100)+1)*3.0/100.0 + @y[i] = @sprites[i].y + end + end + + def disposed?; @disposed; end + + def dispose + if !disposed? + @bitmap.dispose + for i in 0...@numtiles + @sprites[i].visible = false + @sprites[i].dispose + end + @sprites.clear + @viewport.dispose if @viewport + @disposed = true + end + end + + def update + return if disposed? + continue = false + for i in 0...@numtiles + @sprites[i].opacity -= @opacitychange + @y[i] += @offset[i] + @sprites[i].y = @y[i] + continue = true if @sprites[i].opacity>0 + end + self.dispose if !continue + end +end + + + +#=============================================================================== +# +#=============================================================================== +class ShrinkingPieces + def initialize(numframes,rotation) + @disposed = false + @rotation = rotation + @numframes = numframes + @opacitychange = (numframes<=0) ? 255 : 255.0/numframes + cx = 6 + cy = 5 + @bitmap = Graphics.snap_to_bitmap + if !@bitmap + @disposed = true + return + end + width = @bitmap.width/cx + height = @bitmap.height/cy + @numtiles = cx*cy + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprites = [] + for i in 0...@numtiles + @sprites[i] = Sprite.new(@viewport) + @sprites[i].bitmap = @bitmap + @sprites[i].ox = width/2 + @sprites[i].oy = height/2 + @sprites[i].x = width*(i%cx)+@sprites[i].ox + @sprites[i].y = height*(i/cx)+@sprites[i].oy + @sprites[i].src_rect.set(width*(i%cx),height*(i/cx),width,height) + end + end + + def disposed?; @disposed; end + + def dispose + if !disposed? + @bitmap.dispose + for i in 0...@numtiles + @sprites[i].visible = false + @sprites[i].dispose + end + @sprites.clear + @viewport.dispose if @viewport + @disposed = true + end + end + + def update + return if disposed? + continue = false + for i in 0...@numtiles + @sprites[i].opacity -= @opacitychange + if @rotation + @sprites[i].angle += 40 + @sprites[i].angle %= 360 + end + @sprites[i].zoom_x = @sprites[i].opacity/255.0 + @sprites[i].zoom_y = @sprites[i].opacity/255.0 + continue = true if @sprites[i].opacity>0 + end + self.dispose if !continue + end +end + + + +#=============================================================================== +# +#=============================================================================== +class SplashTransition + SPLASH_SIZE = 32 + + def initialize(numframes,vague=9.6) + @duration = numframes + @numframes = numframes + @splash_dir = [] + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = Bitmap.new(Graphics.width, Graphics.height) + size = SPLASH_SIZE + size = [size,1].max + cells = Graphics.width*Graphics.height/(size**2) + rows = Graphics.width/size + rect = Rect.new(0,0,size,size) + mag = 40.0/@numframes + cells.times { |i| + rect.x = i%rows*size + rect.y = i/rows*size + x = rect.x/size-(rows>>1) + y = rect.y/size-((cells/rows)>>1) + r = Math.sqrt(x**2+y**2)/vague + @splash_dir[i] = [] + if r!=0 + @splash_dir[i][0] = x/r + @splash_dir[i][1] = y/r + else + @splash_dir[i][0] = (x!= 0) ? x*1.5 : pmrand*vague + @splash_dir[i][1] = (y!= 0) ? y*1.5 : pmrand*vague + end + @splash_dir[i][0] += (rand-0.5)*vague + @splash_dir[i][1] += (rand-0.5)*vague + @splash_dir[i][0] *= mag + @splash_dir[i][1] *= mag + } + @sprite.bitmap.blt(0,0,@buffer,@buffer.rect) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.visible = false + @sprite.bitmap.dispose + @sprite.dispose + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + size = SPLASH_SIZE + cells = Graphics.width*Graphics.height/(size**2) + rows = Graphics.width/size + rect = Rect.new(0,0,size,size) + buffer = @buffer + sprite = @sprite + phase = @numframes-@duration + sprite.bitmap.clear + cells.times { |i| + rect.x = (i%rows)*size + rect.y = (i/rows)*size + dx = rect.x+@splash_dir[i][0]*phase + dy = rect.y+@splash_dir[i][1]*phase + sprite.bitmap.blt(dx,dy,buffer,rect) + } + sprite.opacity = 384*@duration/@numframes + @duration -= 1 + end + end + + private + + def pmrand + return (rand(2)==0) ? 1 : -1 + end +end + + + +#=============================================================================== +# +#=============================================================================== +class RandomStripeTransition + RAND_STRIPE_SIZE = 2 + + def initialize(numframes,direction) + @duration = numframes + @numframes = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = Bitmap.new(Graphics.width,Graphics.height) + ########## + @direction = direction + size = RAND_STRIPE_SIZE + bands = ((@direction==0) ? Graphics.width : Graphics.height)/size + @rand_stripe_deleted = [] + @rand_stripe_deleted_count = 0 + ary = (0...bands).to_a + @rand_stripe_index_array = ary.sort_by { rand } + ########## + @sprite.bitmap.blt(0,0,@buffer,@buffer.rect) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.visible = false + @sprite.bitmap.dispose + @sprite.dispose + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + dir = @direction + size = RAND_STRIPE_SIZE + bands = ((dir==0) ? Graphics.width : Graphics.height)/size + rect = Rect.new(0,0,(dir==0) ? size : Graphics.width,(dir==0) ? Graphics.height : size) + buffer = @buffer + sprite = @sprite + phase = @numframes-@duration + count = (bands-bands*@duration/@numframes)-@rand_stripe_deleted_count + while count > 0 + @rand_stripe_deleted[@rand_stripe_index_array.pop] = true + @rand_stripe_deleted_count += 1 + count -= 1 + end + sprite.bitmap.clear + bands.to_i.times { |i| + unless @rand_stripe_deleted[i] + if dir==0 + rect.x = i*size + sprite.bitmap.blt(rect.x,0,buffer,rect) + else + rect.y = i*size + sprite.bitmap.blt(0,rect.y,buffer,rect) + end + end + } + @duration -= 1 + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class ZoomInTransition + def initialize(numframes) + @duration = numframes + @numframes = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = @buffer + @sprite.ox = @width/2 + @sprite.oy = @height/2 + @sprite.x = @width/2 + @sprite.y = @height/2 + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.dispose if @sprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + @sprite.zoom_x += 0.2 + @sprite.zoom_y += 0.2 + @sprite.opacity = (@duration-1)*255/@numframes + @duration -= 1 + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class ScrollScreen + def initialize(numframes,direction) + @numframes = numframes + @duration = numframes + @dir = direction + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = @buffer + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.dispose if @sprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + case @dir + when 1 # down left + @sprite.y += (@height/@numframes) + @sprite.x -= (@width/@numframes) + when 2 # down + @sprite.y += (@height/@numframes) + when 3 # down right + @sprite.y += (@height/@numframes) + @sprite.x += (@width/@numframes) + when 4 # left + @sprite.x -= (@width/@numframes) + when 6 # right + @sprite.x += (@width/@numframes) + when 7 # up left + @sprite.y -= (@height/@numframes) + @sprite.x -= (@width/@numframes) + when 8 # up + @sprite.y -= (@height/@numframes) + when 9 # up right + @sprite.y -= (@height/@numframes) + @sprite.x += (@width/@numframes) + end + @duration -= 1 + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class MosaicTransition + def initialize(numframes) + @duration = numframes + @numframes = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = @buffer + @bitmapclone = @buffer.clone + @bitmapclone2 = @buffer.clone + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.dispose if @sprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + @bitmapclone2.stretch_blt( + Rect.new(0,0,@buffer.width*@duration/@numframes,@buffer.height*@duration/@numframes), + @bitmapclone, + Rect.new(0,0,@buffer.width,@buffer.height)) + @buffer.stretch_blt( + Rect.new(0,0,@buffer.width,@buffer.height), + @bitmapclone2, + Rect.new(0,0,@buffer.width*@duration/@numframes,@buffer.height*@duration/@numframes)) + @duration -= 1 + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class FadeTransition + def initialize(numframes) + @duration = numframes + @numframes = numframes + @disposed = false + if @duration<=0 + @disposed = true + return + end + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprite = BitmapSprite.new(Graphics.width,Graphics.height,@viewport) + @sprite.bitmap.fill_rect(0,0,Graphics.width,Graphics.height,Color.new(0,0,0)) + @sprite.opacity = 255 + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @sprite.dispose if @sprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + @sprite.opacity = (@duration-1)*255/@numframes + @duration -= 1 + end + end +end + + + +#=============================================================================== +# HGSS wild outdoor +#=============================================================================== +class SnakeSquares + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + @bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_square") + if !@bitmap + @disposed = true + return + end + width = @bitmap.width + height = @bitmap.height + cx = Graphics.width/width # 8 + cy = Graphics.height/height # 6 + @numtiles = cx*cy + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprites = [] + @frame = [] + @addzoom = 0.125*50/@numframes + for i in 0...cy + for j in 0...cx + k = i*cx+j + x = width*(j%cx) + x = (cx-1)*width-x if (i<3 && i%2==1) || (i>=3 && i%2==0) + @sprites[k] = Sprite.new(@viewport) + @sprites[k].x = x+width/2 + @sprites[k].y = height*i + @sprites[k].ox = width/2 + @sprites[k].visible = false + @sprites[k].bitmap = @bitmap + if k>=@numtiles/2 + @frame[k] = 2*(@numtiles-k-1)*(@numframes-1/@addzoom)/50 + else + @frame[k] = 2*k*(@numframes-1/@addzoom)/50 + end + end + end + end + + def disposed?; @disposed; end + + def dispose + if !disposed? + @bitmap.dispose + for i in 0...@numtiles + if @sprites[i] + @sprites[i].visible = false + @sprites[i].dispose + end + end + @sprites.clear + @viewport.dispose if @viewport + @disposed = true + end + end + + def update + return if disposed? + if @duration==0 + dispose + else + count = @numframes-@duration + for i in 0...@numtiles + if @frame[i]=count + @sprites[i].visible = true + @sprites[i].zoom_x = @addzoom*(count-@frame[i]) + @sprites[i].zoom_x = 1.0 if @sprites[i].zoom_x>1.0 + end + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS wild indoor day (origin=0) +# HGSS wild indoor night (origin=3) +# HGSS wild cave (origin=3) +#=============================================================================== +class DiagonalBubble + def initialize(numframes,origin=0) + @numframes = numframes + @duration = numframes + @disposed = false + @bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_square") + if !@bitmap + @disposed = true + return + end + width = @bitmap.width + height = @bitmap.height + cx = Graphics.width/width # 8 + cy = Graphics.height/height # 6 + @numtiles = cx*cy + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprites = [] + @frame = [] + # 1.2, 0.6 and 0.8 determined by trigonometry of default screen size + l = 1.2*Graphics.width/(@numtiles-8) + for i in 0...cy + for j in 0...cx + k = i*cx+j + @sprites[k] = Sprite.new(@viewport) + @sprites[k].x = width*j+width/2 + @sprites[k].y = height*i+height/2 + @sprites[k].ox = width/2 + @sprites[k].oy = height/2 + @sprites[k].visible = false + @sprites[k].bitmap = @bitmap + case origin + when 1; k = i*cx+(cx-1-j) # Top right + when 2; k = @numtiles-1-(i*cx+(cx-1-j)) # Bottom left + when 3; k = @numtiles-1-k # Bottom right + end + @frame[k] = ((0.6*j*width+0.8*i*height)*(@numframes/50)/l).floor + end + end + @addzoom = 0.125*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + if !disposed? + @bitmap.dispose + for i in 0...@numtiles + if @sprites[i] + @sprites[i].visible = false + @sprites[i].dispose + end + end + @sprites.clear + @viewport.dispose if @viewport + @disposed = true + end + end + + def update + return if disposed? + if @duration==0 + dispose + else + count = @numframes-@duration + for i in 0...@numtiles + if @frame[i]=count + @sprites[i].visible = true + @sprites[i].zoom_x = @addzoom*(count-@frame[i]) + @sprites[i].zoom_x = 1.0 if @sprites[i].zoom_x>1.0 + @sprites[i].zoom_y = @sprites[i].zoom_x + end + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS wild water +#=============================================================================== +class RisingSplash + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @bubblebitmap = BitmapCache.load_bitmap("Graphics/Transitions/water_1") + @splashbitmap = BitmapCache.load_bitmap("Graphics/Transitions/water_2") + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @buffer = Graphics.snap_to_bitmap + if !@bubblebitmap || !@splashbitmap || !@blackbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @rearsprite = Sprite.new(@viewport) + @rearsprite.z = 1 + @rearsprite.zoom_y = 2.0 + @rearsprite.bitmap = @blackbitmap + @bgsprites = [] + rect = Rect.new(0,0,@width,2) + for i in 0...@height/2 + @bgsprites[i] = Sprite.new(@viewport) + @bgsprites[i].y = i*2 + @bgsprites[i].z = 2 + @bgsprites[i].bitmap = @buffer + rect.y = i*2 + @bgsprites[i].src_rect = rect + end + @bubblesprite = Sprite.new(@viewport) + @bubblesprite.y = @height + @bubblesprite.z = 3 + @bubblesprite.bitmap = @bubblebitmap + @splashsprite = Sprite.new(@viewport) + @splashsprite.y = @height + @splashsprite.z = 4 + @splashsprite.bitmap = @splashbitmap + @blacksprite = Sprite.new(@viewport) + @blacksprite.y = @height + @blacksprite.z = 5 + @blacksprite.zoom_y = 2.0 + @blacksprite.bitmap = @blackbitmap + @bubblesuby = @height*2/@numframes + @splashsuby = @bubblesuby*2 + @blacksuby = @height/(@numframes*0.1).floor + @angmult = 2/(@numframes/50) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @bubblebitmap.dispose if @bubblebitmap + @bubblebitmap = nil + @splashbitmap.dispose if @splashbitmap + @splashbitmap = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @rearsprite.dispose if @rearsprite + for i in @bgsprites; i.dispose if i; end + @bgsprites.clear + @bubblesprite.dispose if @bubblesprite + @splashsprite.dispose if @splashsprite + @blacksprite.dispose if @blacksprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + angadd = (@numframes-@duration)*@angmult + amp = 6*angadd/8; amp = 6 if amp>6 + for i in 0...@bgsprites.length + @bgsprites[i].x = amp*Math.sin((i+angadd)*Math::PI/10) + end + @bubblesprite.x = (@width-@bubblebitmap.width)/2 + @bubblesprite.x -= 32*Math.sin((@numframes-@duration)/(@numframes/50)*3*Math::PI/60) + @bubblesprite.y -= @bubblesuby + if @duration<@numframes*0.5 + @splashsprite.y -= @splashsuby + end + if @duration<@numframes*0.1 + @blacksprite.y -= @blacksuby + @blacksprite.y = 0 if @blacksprite.y<0 + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer outdoor day +#=============================================================================== +class TwoBallPass + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @bgsprite = Sprite.new(@viewport) + @bgsprite.x = @width/2 + @bgsprite.y = @height/2 + @bgsprite.ox = @width/2 + @bgsprite.oy = @height/2 + @bgsprite.bitmap = @buffer + @blacksprites = [] + @ballsprites = [] + for i in 0...2 + @blacksprites[i] = Sprite.new(@viewport) + @blacksprites[i].x = (1-i*2)*@width + @blacksprites[i].y = i*@height/2 + @blacksprites[i].z = 1 + @blacksprites[i].bitmap = @blackbitmap + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = (1-i)*@width + (1-i*2)*@ballbitmap.width/2 + @ballsprites[i].y = @height/2 + (i*2-1)*@ballbitmap.height/2 + @ballsprites[i].z = 2 + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = @ballbitmap.height/2 + @ballsprites[i].bitmap = @ballbitmap + end + @blacksprites[2] = Sprite.new(@viewport) + @blacksprites[2].y = @height/2 + @blacksprites[2].z = 1 + @blacksprites[2].oy = @height/4 + @blacksprites[2].zoom_y = 0.0 + @blacksprites[2].bitmap = @blackbitmap + @addxmult = 2.0*@width/((@numframes*0.6)**2) + @addzoom = 0.02*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @bgsprite.dispose if @bgsprite + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.6 + for i in 0...2 + @ballsprites[i].x += (2*i-1)*((@width+@ballbitmap.width)/(0.4*@numframes)) + @ballsprites[i].angle += (2*i-1)*(360.0/(0.2*@numframes)) + end + else + addx = (@numframes*0.6-@duration)*@addxmult + for i in 0...2 + @bgsprite.zoom_x += @addzoom + @bgsprite.zoom_y += @addzoom + @blacksprites[i].x += (2*i-1)*addx + end + @blacksprites[2].zoom_y += 2*addx/@width + @blacksprites[0].x = 0 if @blacksprites[0].x<0 + @blacksprites[1].x = 0 if @blacksprites[1].x>0 + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer outdoor night +#=============================================================================== +class SpinBallSplit + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_large") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @bgsprites = [] + @blacksprites = [] + @ballsprites = [] + for i in 0...2 + @bgsprites[i] = Sprite.new(@viewport) + @bgsprites[i].x = @width/2 + @bgsprites[i].y = @height/2 + @bgsprites[i].ox = @width/2 + @bgsprites[i].oy = (1-i)*@height/2 + @bgsprites[i].bitmap = @buffer + @bgsprites[i].src_rect.set(0,i*@height/2,@width,@height/2) + @blacksprites[i] = Sprite.new(@viewport) + @blacksprites[i].x = (1-i*2)*@width + @blacksprites[i].y = i*@height/2 + @blacksprites[i].z = 1 + @blacksprites[i].bitmap = @blackbitmap + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = @width/2 + @ballsprites[i].y = @height/2 + @ballsprites[i].z = 2 + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = (1-i)*@ballbitmap.height/2 + @ballsprites[i].zoom_x = 0.0 + @ballsprites[i].zoom_y = 0.0 + @ballsprites[i].bitmap = @ballbitmap + end + @addxmult = 2.0*@width/((@numframes*0.5)**2) + @addzoom = 0.02*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + for i in @bgsprites; i.dispose if i; end + @bgsprites.clear + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.6 + if @ballsprites[0].zoom_x<1.0 + @ballsprites[0].zoom_x += (1.0/(0.4*@numframes)) + @ballsprites[0].zoom_y += (1.0/(0.4*@numframes)) + @ballsprites[0].angle -= (360.0/(0.4*@numframes)) + if @ballsprites[0].zoom_x>=1.0 + for i in 0...2 + @ballsprites[i].src_rect.set(0,i*@ballbitmap.height/2, + @ballbitmap.width,@ballbitmap.height/2) + @ballsprites[i].zoom_x = @ballsprites[i].zoom_y = 1.0 + @ballsprites[i].angle = 0.0 + end + end + end + # Gap between 0.6*@numframes and 0.5*@numframes + elsif @duration<@numframes*0.5 + addx = (@numframes*0.5-@duration)*@addxmult + for i in 0...2 + @bgsprites[i].x += (2*i-1)*addx + @bgsprites[i].zoom_x += @addzoom + @bgsprites[i].zoom_y += @addzoom + @blacksprites[i].x += (2*i-1)*addx + @ballsprites[i].x += (2*i-1)*addx + end + @blacksprites[0].x = 0 if @blacksprites[0].x<0 + @blacksprites[1].x = 0 if @blacksprites[1].x>0 + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer indoor day +#=============================================================================== +class ThreeBallDown + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_square") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + cx = Graphics.width/@blackbitmap.width # 8 + cy = Graphics.height/@blackbitmap.height # 6 + @numtiles = cx*cy + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @bgsprite = Sprite.new(@viewport) + @bgsprite.x = @width/2 + @bgsprite.y = @height/2 + @bgsprite.ox = @width/2 + @bgsprite.oy = @height/2 + @bgsprite.bitmap = @buffer + @frame = [] + @blacksprites = [] + for i in 0...cy + for j in 0...cx + k = i*cx+j + @blacksprites[k] = Sprite.new(@viewport) + @blacksprites[k].x = @blackbitmap.width*j + @blacksprites[k].y = @blackbitmap.height*i + @blacksprites[k].visible = false + @blacksprites[k].bitmap = @blackbitmap + @frame[k] = (((cy-i-1)*8+[0,4,1,6,7,2,5,3][j])*(@numframes*0.75)/@numtiles).floor + end + end + @ballsprites = [] + for i in 0...3 + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = 96+i*160 + @ballsprites[i].y = -@ballbitmap.height-[400,0,100][i] + @ballsprites[i].z = 2 + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = @ballbitmap.height/2 + @ballsprites[i].bitmap = @ballbitmap + end + @addyball = (@height+400+@ballbitmap.height*2)/(0.25*@numframes) + @addangle = 1.5*360/(0.25*@numframes) + @addzoom = 0.02*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @bgsprite.dispose if @bgsprite + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.75 + for i in 0...@ballsprites.length + @ballsprites[i].y += @addyball + @ballsprites[i].angle -= @addangle*([1,-1][(i==2) ? 1 : 0]) + end + else + count = (@numframes*0.75).floor-@duration + for i in 0...@numtiles + @blacksprites[i].visible = true if @frame[i]<=count + end + @bgsprite.zoom_x += @addzoom + @bgsprite.zoom_y += @addzoom + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer indoor night +# HGSS trainer cave +#=============================================================================== +class BallDown + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @curvebitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_curve") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@curvebitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @bgsprite = Sprite.new(@viewport) + @bgsprite.x = @width/2 + @bgsprite.y = @height/2 + @bgsprite.ox = @width/2 + @bgsprite.oy = @height/2 + @bgsprite.bitmap = @buffer + @blacksprites = [] + @blacksprites[0] = Sprite.new(@viewport) + @blacksprites[0].y = -@curvebitmap.height + @blacksprites[0].z = 1 + @blacksprites[0].oy = @blackbitmap.height + @blacksprites[0].zoom_y = 2.0 + @blacksprites[0].bitmap = @blackbitmap + @blacksprites[1] = Sprite.new(@viewport) + @blacksprites[1].y = -@curvebitmap.height + @blacksprites[1].z = 1 + @blacksprites[1].bitmap = @curvebitmap + @ballsprite = Sprite.new(@viewport) + @ballsprite.x = @width/2 + @ballsprite.y = -@ballbitmap.height/2 + @ballsprite.z = 2 + @ballsprite.ox = @ballbitmap.width/2 + @ballsprite.oy = @ballbitmap.height/2 + @ballsprite.zoom_x = 0.25 + @ballsprite.zoom_y = 0.25 + @ballsprite.bitmap = @ballbitmap + @addyball = (@height+@ballbitmap.height*2.5)/(0.5*@numframes) + @addangle = 1.5*360/(0.5*@numframes) + @addzoomball = 2.5/(0.5*@numframes) + @addy = (@height+@curvebitmap.height)/(@numframes*0.5) + @addzoom = 0.02*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @curvebitmap.dispose if @curvebitmap + @curvebitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @bgsprite.dispose if @bgsprite + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + @ballsprite.dispose + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.5 + @ballsprite.y += @addyball + @ballsprite.angle -= @addangle + @ballsprite.zoom_x += @addzoomball + @ballsprite.zoom_y += @addzoomball + else + @blacksprites[1].y += @addy + @blacksprites[0].y = @blacksprites[1].y + @bgsprite.zoom_x += @addzoom + @bgsprite.zoom_y += @addzoom + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer water day +#=============================================================================== +class WavyThreeBallUp + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @rearsprite = Sprite.new(@viewport) + @rearsprite.z = 1 + @rearsprite.zoom_y = 2.0 + @rearsprite.bitmap = @blackbitmap + @bgsprites = [] + rect = Rect.new(0,0,@width,2) + for i in 0...@height/2 + @bgsprites[i] = Sprite.new(@viewport) + @bgsprites[i].y = i*2 + @bgsprites[i].z = 2 + @bgsprites[i].bitmap = @buffer + rect.y = i*2 + @bgsprites[i].src_rect = rect + end + @blacksprites = [] + @ballsprites = [] + for i in 0...3 + @blacksprites[i] = Sprite.new(@viewport) + @blacksprites[i].x = (i-1)*@width*2/3 + @blacksprites[i].y = [@height*1.5,@height*3.25,@height*2.5][i] + @blacksprites[i].z = 3 + @blacksprites[i].zoom_y = 2.0 + @blacksprites[i].bitmap = @blackbitmap + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = (2*i+1)*@width/6 + @ballsprites[i].y = [@height*1.5,@height*3.25,@height*2.5][i] + @ballsprites[i].z = 4 + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = @ballbitmap.height/2 + @ballsprites[i].bitmap = @ballbitmap + end + @suby = (@height*3.5)/(@numframes*0.6) + @angmult = 4/(@numframes/50) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @rearsprite.dispose if @rearsprite + for i in @bgsprites; i.dispose if i; end + @bgsprites.clear + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + angadd = (@numframes-@duration)*@angmult + amp = 24*angadd/16; amp = 24 if amp>24 + for i in 0...@bgsprites.length + @bgsprites[i].x = amp*Math.sin((i+angadd)*Math::PI/48)*((i%2)*2-1) + end + if @duration<@numframes*0.6 + for i in 0...3 + @blacksprites[i].y -= @suby + @blacksprites[i].y = 0 if @blacksprites[i].y<0 + @ballsprites[i].y -= @suby + @ballsprites[i].angle += (2*(i%2)-1)*(360.0/(0.2*@numframes)) + end + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer water night +#=============================================================================== +class WavySpinBall + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_large") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @rearsprite = Sprite.new(@viewport) + @rearsprite.z = 1 + @rearsprite.zoom_y = 2.0 + @rearsprite.bitmap = @blackbitmap + @bgsprites = [] + rect = Rect.new(0,0,@width,2) + for i in 0...@height/2 + @bgsprites[i] = Sprite.new(@viewport) + @bgsprites[i].y = i*2 + @bgsprites[i].z = 2 + @bgsprites[i].bitmap = @buffer + rect.y = i*2 + @bgsprites[i].src_rect = rect + end + @ballsprite = Sprite.new(@viewport) + @ballsprite.x = @width/2 + @ballsprite.y = @height/2 + @ballsprite.z = 3 + @ballsprite.ox = @ballbitmap.width/2 + @ballsprite.oy = @ballbitmap.height/2 + @ballsprite.visible = false + @ballsprite.bitmap = @ballbitmap + @blacksprite = Sprite.new(@viewport) + @blacksprite.x = @width/2 + @blacksprite.y = @height/2 + @blacksprite.z = 4 + @blacksprite.ox = @blackbitmap.width/2 + @blacksprite.oy = @blackbitmap.height/2 + @blacksprite.visible = false + @blacksprite.bitmap = @blackbitmap + @angmult = 4/(@numframes/50) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @rearsprite.dispose if @rearsprite + for i in @bgsprites; i.dispose if i; end + @bgsprites.clear + @ballsprite.dispose if @ballsprite + @blacksprite.dispose if @blacksprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + angadd = (@numframes-@duration)*@angmult + amp = 24*angadd/16; amp = 24 if amp>24 + for i in 0...@bgsprites.length + @bgsprites[i].x = amp*Math.sin((i+angadd)*Math::PI/48)*((i%2)*2-1) + end + @ballsprite.visible = true + if @duration>=@numframes*0.6 + @ballsprite.opacity = 255*(@numframes-@duration)/(@numframes*0.4) + @ballsprite.angle = -360.0*(@numframes-@duration)/(@numframes*0.4) + elsif @duration<@numframes*0.5 + @blacksprite.visible = true + @blacksprite.zoom_x = (@numframes*0.5-@duration)/(@numframes*0.5) + @blacksprite.zoom_y = 2*(@numframes*0.5-@duration)/(@numframes*0.5) + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS double trainers +#=============================================================================== +class FourBallBurst + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @black1bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_wedge_1") + @black2bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_wedge_2") + @black3bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_wedge_3") + @black4bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_wedge_4") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + if !@black1bitmap || !@black2bitmap || !@black3bitmap || !@black4bitmap || !@ballbitmap + @disposed = true + return + end + @width = Graphics.width + @height = Graphics.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @ballsprites = [] + for i in 0...4 + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = @width/2 + @ballsprites[i].y = @height/2 + @ballsprites[i].z = [2,1,3,0][i] + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = @ballbitmap.height/2 + @ballsprites[i].bitmap = @ballbitmap + end + @blacksprites = [] + for i in 0...4 + b = [@black1bitmap,@black2bitmap,@black3bitmap,@black4bitmap][i] + @blacksprites[i] = Sprite.new(@viewport) + @blacksprites[i].x = (i==1) ? 0 : @width/2 + @blacksprites[i].y = (i==2) ? 0 : @height/2 + @blacksprites[i].ox = (i%2==0) ? b.width/2 : 0 + @blacksprites[i].oy = (i%2==0) ? 0 : b.height/2 + @blacksprites[i].zoom_x = (i%2==0) ? 0.0 : 1.0 + @blacksprites[i].zoom_y = (i%2==0) ? 1.0 : 0.0 + @blacksprites[i].visible = false + @blacksprites[i].bitmap = b + end + @addxball = (@width/2+@ballbitmap.width/2)/(@numframes*0.4) + @addyball = (@height/2+@ballbitmap.height/2)/(@numframes*0.4) + @addzoom = 1.0/(@numframes*0.6) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @black1bitmap.dispose if @black1bitmap + @black1bitmap = nil + @black2bitmap.dispose if @black2bitmap + @black2bitmap = nil + @black3bitmap.dispose if @black3bitmap + @black3bitmap = nil + @black4bitmap.dispose if @black4bitmap + @black4bitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.6 + for i in 0...@ballsprites.length + @ballsprites[i].x += (i==1) ? @addxball : (i==3) ? -@addxball : 0 + @ballsprites[i].y += (i==0) ? @addyball : (i==2) ? -@addyball : 0 + end + else + for i in 0...@blacksprites.length + @blacksprites[i].visible = true + @blacksprites[i].zoom_x += (i%2==0) ? @addzoom : 0 + @blacksprites[i].zoom_y += (i%2==0) ? 0 : @addzoom + end + end + @duration -= 1 + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/001_MiscData.rb b/Data/Scripts/010_Data/001_MiscData.rb new file mode 100644 index 000000000..71fd83680 --- /dev/null +++ b/Data/Scripts/010_Data/001_MiscData.rb @@ -0,0 +1,472 @@ +#=============================================================================== +# Phone data +#=============================================================================== +class PhoneDatabase + attr_accessor :generics + attr_accessor :greetings + attr_accessor :greetingsMorning + attr_accessor :greetingsEvening + attr_accessor :bodies1 + attr_accessor :bodies2 + attr_accessor :battleRequests + attr_accessor :trainers + + def initialize + @generics = [] + @greetings = [] + @greetingsMorning = [] + @greetingsEvening = [] + @bodies1 = [] + @bodies2 = [] + @battleRequests = [] + @trainers = [] + end +end + + + +module PhoneMsgType + Generic = 0 + Greeting = 1 + Body = 2 + BattleRequest = 3 +end + + + +#=============================================================================== +# Global and map metadata +#=============================================================================== +MetadataHome = 1 +MetadataWildBattleBGM = 2 +MetadataTrainerBattleBGM = 3 +MetadataWildVictoryME = 4 +MetadataTrainerVictoryME = 5 +MetadataWildCaptureME = 6 +MetadataSurfBGM = 7 +MetadataBicycleBGM = 8 +MetadataPlayerA = 9 +MetadataPlayerB = 10 +MetadataPlayerC = 11 +MetadataPlayerD = 12 +MetadataPlayerE = 13 +MetadataPlayerF = 14 +MetadataPlayerG = 15 +MetadataPlayerH = 16 + +MetadataOutdoor = 1 +MetadataShowArea = 2 +MetadataBicycle = 3 +MetadataBicycleAlways = 4 +MetadataHealingSpot = 5 +MetadataWeather = 6 +MetadataMapPosition = 7 +MetadataDiveMap = 8 +MetadataDarkMap = 9 +MetadataSafariMap = 10 +MetadataSnapEdges = 11 +MetadataDungeon = 12 +MetadataBattleBack = 13 +MetadataMapWildBattleBGM = 14 +MetadataMapTrainerBattleBGM = 15 +MetadataMapWildVictoryME = 16 +MetadataMapTrainerVictoryME = 17 +MetadataMapWildCaptureME = 18 +MetadataMapSize = 19 +MetadataEnvironment = 20 + + + +module PokemonMetadata + GlobalTypes = { + "Home" => [MetadataHome, "uuuu"], + "WildBattleBGM" => [MetadataWildBattleBGM, "s"], + "TrainerBattleBGM" => [MetadataTrainerBattleBGM, "s"], + "WildVictoryME" => [MetadataWildVictoryME, "s"], + "TrainerVictoryME" => [MetadataTrainerVictoryME, "s"], + "WildCaptureME" => [MetadataWildCaptureME, "s"], + "SurfBGM" => [MetadataSurfBGM, "s"], + "BicycleBGM" => [MetadataBicycleBGM, "s"], + "PlayerA" => [MetadataPlayerA, "esssssss",:PBTrainers], + "PlayerB" => [MetadataPlayerB, "esssssss",:PBTrainers], + "PlayerC" => [MetadataPlayerC, "esssssss",:PBTrainers], + "PlayerD" => [MetadataPlayerD, "esssssss",:PBTrainers], + "PlayerE" => [MetadataPlayerE, "esssssss",:PBTrainers], + "PlayerF" => [MetadataPlayerF, "esssssss",:PBTrainers], + "PlayerG" => [MetadataPlayerG, "esssssss",:PBTrainers], + "PlayerH" => [MetadataPlayerH, "esssssss",:PBTrainers] + } + NonGlobalTypes = { + "Outdoor" => [MetadataOutdoor, "b"], + "ShowArea" => [MetadataShowArea, "b"], + "Bicycle" => [MetadataBicycle, "b"], + "BicycleAlways" => [MetadataBicycleAlways, "b"], + "HealingSpot" => [MetadataHealingSpot, "uuu"], + "Weather" => [MetadataWeather, "eu",:PBFieldWeather], + "MapPosition" => [MetadataMapPosition, "uuu"], + "DiveMap" => [MetadataDiveMap, "u"], + "DarkMap" => [MetadataDarkMap, "b"], + "SafariMap" => [MetadataSafariMap, "b"], + "SnapEdges" => [MetadataSnapEdges, "b"], + "Dungeon" => [MetadataDungeon, "b"], + "BattleBack" => [MetadataBattleBack, "s"], + "WildBattleBGM" => [MetadataMapWildBattleBGM, "s"], + "TrainerBattleBGM" => [MetadataMapTrainerBattleBGM, "s"], + "WildVictoryME" => [MetadataMapWildVictoryME, "s"], + "TrainerVictoryME" => [MetadataMapTrainerVictoryME, "s"], + "WildCaptureME" => [MetadataMapWildCaptureME, "s"], + "MapSize" => [MetadataMapSize, "us"], + "Environment" => [MetadataEnvironment, "e",:PBEnvironment] + } +end + + + +#=============================================================================== +# Pokémon data +#=============================================================================== +SpeciesType1 = 0 +SpeciesType2 = 1 +SpeciesBaseStats = 2 +SpeciesGenderRate = 3 +SpeciesGrowthRate = 4 +SpeciesBaseExp = 5 +SpeciesEffortPoints = 6 +SpeciesRareness = 7 +SpeciesHappiness = 8 +SpeciesAbilities = 9 +SpeciesHiddenAbility = 10 +SpeciesCompatibility = 11 +SpeciesStepsToHatch = 12 +SpeciesHeight = 13 +SpeciesWeight = 14 +SpeciesColor = 15 +SpeciesShape = 16 +SpeciesHabitat = 17 +SpeciesWildItemCommon = 18 +SpeciesWildItemUncommon = 19 +SpeciesWildItemRare = 20 +SpeciesIncense = 21 +SpeciesPokedexForm = 22 # For alternate forms +SpeciesMegaStone = 23 # For alternate forms +SpeciesMegaMove = 24 # For alternate forms +SpeciesUnmegaForm = 25 # For alternate forms +SpeciesMegaMessage = 26 # For alternate forms + +MetricBattlerPlayerX = 0 +MetricBattlerPlayerY = 1 +MetricBattlerEnemyX = 2 +MetricBattlerEnemyY = 3 +MetricBattlerAltitude = 4 +MetricBattlerShadowX = 5 +MetricBattlerShadowSize = 6 + + + +module PokemonSpeciesData + def self.requiredValues(compilingForms=false) + ret = { + "Type1" => [SpeciesType1, "e",:PBTypes], + "BaseStats" => [SpeciesBaseStats, "vvvvvv"], + "BaseEXP" => [SpeciesBaseExp, "v"], + "EffortPoints" => [SpeciesEffortPoints, "uuuuuu"], + "Rareness" => [SpeciesRareness, "u"], + "Happiness" => [SpeciesHappiness, "u"], + "Compatibility" => [SpeciesCompatibility, "eE",:PBEggGroups,:PBEggGroups], + "StepsToHatch" => [SpeciesStepsToHatch, "v"], + "Height" => [SpeciesHeight, "f"], + "Weight" => [SpeciesWeight, "f"], + "Color" => [SpeciesColor, "e",:PBColors], + "Shape" => [SpeciesShape, "u"], + "Moves" => [0, "*ue",nil,:PBMoves], + "Kind" => [0, "s"], + "Pokedex" => [0, "q"] + } + if !compilingForms + ret["GenderRate"] = [SpeciesGenderRate, "e",:PBGenderRates] + ret["GrowthRate"] = [SpeciesGrowthRate, "e",:PBGrowthRates] + ret["Name"] = [0, "s"] + ret["InternalName"] = [0, "n"] + end + return ret + end + + def self.optionalValues(compilingForms=false) + ret = { + "Type2" => [SpeciesType2, "e",:PBTypes], + "Abilities" => [SpeciesAbilities, "eE",:PBAbilities,:PBAbilities], + "HiddenAbility" => [SpeciesHiddenAbility, "eEEE",:PBAbilities,:PBAbilities, + :PBAbilities,:PBAbilities], + "Habitat" => [SpeciesHabitat, "e",:PBHabitats], + "WildItemCommon" => [SpeciesWildItemCommon, "e",:PBItems], + "WildItemUncommon" => [SpeciesWildItemUncommon, "e",:PBItems], + "WildItemRare" => [SpeciesWildItemRare, "e",:PBItems], + "BattlerPlayerX" => [MetricBattlerPlayerX, "i"], + "BattlerPlayerY" => [MetricBattlerPlayerY, "i"], + "BattlerEnemyX" => [MetricBattlerEnemyX, "i"], + "BattlerEnemyY" => [MetricBattlerEnemyY, "i"], + "BattlerAltitude" => [MetricBattlerAltitude, "i"], + "BattlerShadowX" => [MetricBattlerShadowX, "i"], + "BattlerShadowSize" => [MetricBattlerShadowSize, "u"], + "EggMoves" => [0, "*e",:PBMoves], + "FormName" => [0, "q"], + "Evolutions" => [0, "*ses",nil,:PBEvolution,nil] + } + if compilingForms + ret["PokedexForm"] = [SpeciesPokedexForm, "u"] + ret["MegaStone"] = [SpeciesMegaStone, "e",:PBItems] + ret["MegaMove"] = [SpeciesMegaMove, "e",:PBMoves] + ret["UnmegaForm"] = [SpeciesUnmegaForm, "u"] + ret["MegaMessage"] = [SpeciesMegaMessage, "u"] + else + ret["Incense"] = [SpeciesIncense, "e",:PBItems] + ret["RegionalNumbers"] = [0, "*u"] + end + return ret + end +end + + + +#=============================================================================== +# Manipulation methods for metadata, phone data and Pokémon species data +#=============================================================================== +class PokemonTemp + attr_accessor :metadata + attr_accessor :townMapData + attr_accessor :encountersData + attr_accessor :phoneData + attr_accessor :regionalDexes + attr_accessor :speciesData + attr_accessor :speciesEggMoves + attr_accessor :speciesMetrics + attr_accessor :speciesMovesets + attr_accessor :speciesTMData + attr_accessor :speciesShadowMovesets + attr_accessor :pokemonFormToSpecies + attr_accessor :trainerTypesData + attr_accessor :trainersData + attr_accessor :moveToAnim + attr_accessor :battleAnims +end + + + +def pbLoadMetadata + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.metadata + $PokemonTemp.metadata = load_data("Data/metadata.dat") || [] + end + return $PokemonTemp.metadata +end + +def pbGetMetadata(mapid,metadataType) + meta = pbLoadMetadata + return meta[mapid][metadataType] if meta[mapid] + return nil +end + +def pbLoadTownMapData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.townMapData + $PokemonTemp.townMapData = load_data("Data/town_map.dat") + end + return $PokemonTemp.townMapData +end + +def pbLoadEncountersData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.encountersData + if pbRgssExists?("Data/encounters.dat") + $PokemonTemp.encountersData = load_data("Data/encounters.dat") + end + end + return $PokemonTemp.encountersData +end + +def pbLoadPhoneData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.phoneData + if pbRgssExists?("Data/phone.dat") + $PokemonTemp.phoneData = load_data("Data/phone.dat") + end + end + return $PokemonTemp.phoneData +end + +def pbLoadRegionalDexes + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.regionalDexes + $PokemonTemp.regionalDexes = load_data("Data/regional_dexes.dat") + end + return $PokemonTemp.regionalDexes +end + +def pbLoadSpeciesData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesData + $PokemonTemp.speciesData = load_data("Data/species.dat") || [] + end + return $PokemonTemp.speciesData +end + +def pbGetSpeciesData(species,form=0,speciesDataType=-1) + species = getID(PBSpecies,species) + s = pbGetFSpeciesFromForm(species,form) + speciesData = pbLoadSpeciesData + if speciesDataType<0 + return speciesData[s] || [] + end + return speciesData[s][speciesDataType] if speciesData[s] && speciesData[s][speciesDataType] + case speciesDataType + when SpeciesType2; return nil + when SpeciesBaseStats; return [1,1,1,1,1,1] + when SpeciesEffortPoints; return [0,0,0,0,0,0] + when SpeciesStepsToHatch, SpeciesHeight, SpeciesWeight; return 1 + end + return 0 +end + +def pbLoadEggMovesData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesEggMoves + $PokemonTemp.speciesEggMoves = load_data("Data/species_eggmoves.dat") || [] + end + return $PokemonTemp.speciesEggMoves +end + +def pbGetSpeciesEggMoves(species,form=0) + species = getID(PBSpecies,species) + s = pbGetFSpeciesFromForm(species,form) + eggMovesData = pbLoadEggMovesData + return eggMovesData[s] if eggMovesData[s] + return [] +end + +def pbLoadSpeciesMetrics + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesMetrics + $PokemonTemp.speciesMetrics = load_data("Data/species_metrics.dat") || [] + end + return $PokemonTemp.speciesMetrics +end + +def pbLoadMovesetsData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesMovesets + $PokemonTemp.speciesMovesets = load_data("Data/species_movesets.dat") || [] + end + return $PokemonTemp.speciesMovesets +end + +def pbGetSpeciesMoveset(species,form=0) + species = getID(PBSpecies,species) + s = pbGetFSpeciesFromForm(species,form) + movesetsData = pbLoadMovesetsData + return movesetsData[s] if movesetsData[s] + return [] +end + +def pbLoadSpeciesTMData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesTMData + $PokemonTemp.speciesTMData = load_data("Data/tm.dat") || [] + end + return $PokemonTemp.speciesTMData +end + +def pbLoadShadowMovesets + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesShadowMovesets + $PokemonTemp.speciesShadowMovesets = load_data("Data/shadow_movesets.dat") || [] + end + return $PokemonTemp.speciesShadowMovesets +end + +def pbLoadFormToSpecies + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.pokemonFormToSpecies + $PokemonTemp.pokemonFormToSpecies = load_data("Data/form2species.dat") + end + return $PokemonTemp.pokemonFormToSpecies +end + +def pbLoadTrainerTypesData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.trainerTypesData + $PokemonTemp.trainerTypesData = load_data("Data/trainer_types.dat") || [] + end + return $PokemonTemp.trainerTypesData +end + +def pbGetTrainerTypeData(type) + data = pbLoadTrainerTypesData + return data[type] if data + return nil +end + +def pbLoadTrainersData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.trainersData + $PokemonTemp.trainersData = load_data("Data/trainers.dat") || [] + end + return $PokemonTemp.trainersData +end + +def pbGetTrainerData(trainerID,trainerName,partyID=0) + trainersData = pbLoadTrainersData + ret = nil + for t in trainersData + next if t[0]!=trainerID || t[1]!=trainerName || t[4]!=partyID + ret = t + break + end + return ret +end + +def pbLoadMoveToAnim + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.moveToAnim + $PokemonTemp.moveToAnim = load_data("Data/move2anim.dat") || [] + end + return $PokemonTemp.moveToAnim +end + +def pbLoadBattleAnimations + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.battleAnims + if pbRgssExists?("Data/PkmnAnimations.rxdata") + $PokemonTemp.battleAnims = load_data("Data/PkmnAnimations.rxdata") + end + end + return $PokemonTemp.battleAnims +end + +def pbClearData + if $PokemonTemp + $PokemonTemp.metadata = nil + $PokemonTemp.townMapData = nil + $PokemonTemp.encountersData = nil + $PokemonTemp.phoneData = nil + $PokemonTemp.regionalDexes = nil + $PokemonTemp.speciesData = nil + $PokemonTemp.speciesEggMoves = nil + $PokemonTemp.speciesMetrics = nil + $PokemonTemp.speciesMovesets = nil + $PokemonTemp.speciesTMData = nil + $PokemonTemp.speciesShadowMovesets = nil + $PokemonTemp.pokemonFormToSpecies = nil + $PokemonTemp.trainerTypesData = nil + $PokemonTemp.trainersData = nil + $PokemonTemp.moveToAnim = nil + $PokemonTemp.battleAnims = nil + end + MapFactoryHelper.clear + $PokemonEncounters.setup($game_map.map_id) if $game_map && $PokemonEncounters + if pbRgssExists?("Data/Tilesets.rxdata") + $data_tilesets = load_data("Data/Tilesets.rxdata") + end + if pbRgssExists?("Data/Tilesets.rvdata") + $data_tilesets = load_data("Data/Tilesets.rvdata") + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/002_PBMove.rb b/Data/Scripts/010_Data/002_PBMove.rb new file mode 100644 index 000000000..24da3b49f --- /dev/null +++ b/Data/Scripts/010_Data/002_PBMove.rb @@ -0,0 +1,104 @@ +MOVE_ID = 0 +MOVE_INTERNAL_NAME = 1 +MOVE_NAME = 2 +MOVE_FUNCTION_CODE = 3 +MOVE_BASE_DAMAGE = 4 +MOVE_TYPE = 5 +MOVE_CATEGORY = 6 +MOVE_ACCURACY = 7 +MOVE_TOTAL_PP = 8 +MOVE_EFFECT_CHANCE = 9 +MOVE_TARGET = 10 +MOVE_PRIORITY = 11 +MOVE_FLAGS = 12 +MOVE_DESCRIPTION = 13 + + + +class PokemonTemp + attr_accessor :movesData +end + + + +def pbLoadMovesData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.movesData + if pbRgssExists?("Data/moves.dat") + $PokemonTemp.movesData = load_data("Data/moves.dat") + else + $PokemonTemp.movesData = [] + end + end + return $PokemonTemp.movesData +end + +def pbGetMoveData(moveID,moveDataType=-1) + meta = pbLoadMovesData + if moveDataType<0 + return meta[moveID] || [] + end + return meta[moveID][moveDataType] if meta[moveID] + return nil +end + +alias __moveData__pbClearData pbClearData +def pbClearData + $PokemonTemp.movesData = nil if $PokemonTemp + __moveData__pbClearData +end + + + +class PBMoveData + attr_reader :function,:basedamage,:type,:accuracy,:category + attr_reader :totalpp,:addlEffect,:target,:priority,:flags + + def initialize(moveid) + moveData = pbGetMoveData(moveID) + @function = moveData[MOVE_FUNCTION_CODE] + @basedamage = moveData[MOVE_BASE_DAMAGE] + @type = moveData[MOVE_TYPE] + @category = moveData[MOVE_CATEGORY] + @accuracy = moveData[MOVE_ACCURACY] + @totalpp = moveData[MOVE_TOTAL_PP] + @addlEffect = moveData[MOVE_EFFECT_CHANCE] + @target = moveData[MOVE_TARGET] + @priority = moveData[MOVE_PRIORITY] + @flags = moveData[MOVE_FLAGS] + end +end + + + +class PBMove + attr_reader(:id) # This move's ID + attr_accessor(:pp) # The amount of PP remaining for this move + attr_accessor(:ppup) # The number of PP Ups used for this move + + # Initializes this object to the specified move ID. + def initialize(moveID) + @id = moveID + @pp = pbGetMoveData(moveID,MOVE_TOTAL_PP) || 0 + @ppup = 0 + end + + # Changes this move's ID, and caps the PP amount if it is now greater than the + # new move's total PP. + def id=(value) + oldID = @id + @id = value + @pp = [@pp,self.totalpp].min if oldID>0 + end + + # Gets this move's type. + def type + return pbGetMoveData(@id,MOVE_TYPE) || 0 + end + + # Gets the maximum PP for this move. + def totalpp + maxPP = pbGetMoveData(@id,MOVE_TOTAL_PP) || 0 + return maxPP+maxPP*@ppup/5 + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/003_PBStatuses.rb b/Data/Scripts/010_Data/003_PBStatuses.rb new file mode 100644 index 000000000..17c62e7a2 --- /dev/null +++ b/Data/Scripts/010_Data/003_PBStatuses.rb @@ -0,0 +1,28 @@ +#70925035 +begin + module PBStatuses + NONE = 0 + SLEEP = 1 + POISON = 2 + BURN = 3 + PARALYSIS = 4 + FROZEN = 5 + + def self.getName(id) + id = getID(PBStatuses,id) + names = [ + _INTL("healthy"), + _INTL("asleep"), + _INTL("poisoned"), + _INTL("burned"), + _INTL("paralyzed"), + _INTL("frozen") + ] + return names[id] + end end + +rescue Exception + if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset" + raise $! + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/004_PBTypes_Extra.rb b/Data/Scripts/010_Data/004_PBTypes_Extra.rb new file mode 100644 index 000000000..8641766a9 --- /dev/null +++ b/Data/Scripts/010_Data/004_PBTypes_Extra.rb @@ -0,0 +1,90 @@ +module PBTypeEffectiveness + INEFFECTIVE = 0 + NOT_EFFECTIVE_ONE = 1 + NORMAL_EFFECTIVE_ONE = 2 + SUPER_EFFECTIVE_ONE = 4 + NORMAL_EFFECTIVE = NORMAL_EFFECTIVE_ONE ** 3 +end + + + +class PBTypes + @@TypeData = nil + + def PBTypes.loadTypeData + if !@@TypeData + @@TypeData = load_data("Data/types.dat") + @@TypeData[0].freeze + @@TypeData[1].freeze + @@TypeData[2].freeze + @@TypeData.freeze + end + return @@TypeData + end + + def PBTypes.regularTypesCount + ret = 0 + for i in 0..PBTypes.maxValue + next if PBTypes.isPseudoType?(i) || isConst?(i,PBTypes,:SHADOW) + ret += 1 + end + return ret + end + + def PBTypes.isPseudoType?(type) + return PBTypes.loadTypeData[0].include?(type) + end + + def PBTypes.isSpecialType?(type) + return PBTypes.loadTypeData[1].include?(type) + end + + def PBTypes.getEffectiveness(attackType,targetType) + return PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if !targetType || targetType<0 + return PBTypes.loadTypeData[2][attackType*(PBTypes.maxValue+1)+targetType] + end + + def PBTypes.getCombinedEffectiveness(attackType,targetType1,targetType2=nil,targetType3=nil) + mod1 = PBTypes.getEffectiveness(attackType,targetType1) + mod2 = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE + mod3 = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE + if targetType2!=nil && targetType2>=0 && targetType1!=targetType2 + mod2 = PBTypes.getEffectiveness(attackType,targetType2) + end + if targetType3!=nil && targetType3>=0 && + targetType1!=targetType3 && targetType2!=targetType3 + mod3 = PBTypes.getEffectiveness(attackType,targetType3) + end + return mod1*mod2*mod3 + end + + def PBTypes.ineffective?(attackType,targetType1=nil,targetType2=nil,targetType3=nil) + return attackType==PBTypeEffectiveness::INEFFECTIVE if !targetType1 + e = PBTypes.getCombinedEffectiveness(attackType,targetType1,targetType2,targetType3) + return e==PBTypeEffectiveness::INEFFECTIVE + end + + def PBTypes.notVeryEffective?(attackType,targetType1=nil,targetType2=nil,targetType3=nil) + return attackType>PBTypeEffectiveness::INEFFECTIVE && attackTypePBTypeEffectiveness::INEFFECTIVE && ePBTypeEffectiveness::NORMAL_EFFECTIVE if !targetType1 + e = PBTypes.getCombinedEffectiveness(attackType,targetType1,targetType2,targetType3) + return e>PBTypeEffectiveness::NORMAL_EFFECTIVE + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/005_PBNatures.rb b/Data/Scripts/010_Data/005_PBNatures.rb new file mode 100644 index 000000000..bcd501a44 --- /dev/null +++ b/Data/Scripts/010_Data/005_PBNatures.rb @@ -0,0 +1,87 @@ +module PBNatures + HARDY = 0 + LONELY = 1 + BRAVE = 2 + ADAMANT = 3 + NAUGHTY = 4 + BOLD = 5 + DOCILE = 6 + RELAXED = 7 + IMPISH = 8 + LAX = 9 + TIMID = 10 + HASTY = 11 + SERIOUS = 12 + JOLLY = 13 + NAIVE = 14 + MODEST = 15 + MILD = 16 + QUIET = 17 + BASHFUL = 18 + RASH = 19 + CALM = 20 + GENTLE = 21 + SASSY = 22 + CAREFUL = 23 + QUIRKY = 24 + + def self.maxValue; 24; end + def self.getCount; 25; end + + def self.getName(id) + id = getID(PBNatures,id) + names = [ + _INTL("Hardy"), + _INTL("Lonely"), + _INTL("Brave"), + _INTL("Adamant"), + _INTL("Naughty"), + _INTL("Bold"), + _INTL("Docile"), + _INTL("Relaxed"), + _INTL("Impish"), + _INTL("Lax"), + _INTL("Timid"), + _INTL("Hasty"), + _INTL("Serious"), + _INTL("Jolly"), + _INTL("Naive"), + _INTL("Modest"), + _INTL("Mild"), + _INTL("Quiet"), + _INTL("Bashful"), + _INTL("Rash"), + _INTL("Calm"), + _INTL("Gentle"), + _INTL("Sassy"), + _INTL("Careful"), + _INTL("Quirky") + ] + return names[id] + end + + def self.getStatRaised(id) + m = (id%25)/5 # 25 here is (number of stats)**2, not PBNatures.getCount + return [PBStats::ATTACK,PBStats::DEFENSE,PBStats::SPEED, + PBStats::SPATK,PBStats::SPDEF][m] + end + + def self.getStatLowered(id) + m = id%5 # Don't need to %25 here because 25 is a multiple of 5 + return [PBStats::ATTACK,PBStats::DEFENSE,PBStats::SPEED, + PBStats::SPATK,PBStats::SPDEF][m] + end + + def self.getStatChanges(id) + id = getID(PBNatures,id) + up = PBNatures.getStatRaised(id) + dn = PBNatures.getStatLowered(id) + ret = [] + PBStats.eachStat do |s| + ret[s] = 100 + ret[s] += 10 if s==up + ret[s] -= 10 if s==dn + end + return ret + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/006_PBGenderRates.rb b/Data/Scripts/010_Data/006_PBGenderRates.rb new file mode 100644 index 000000000..0dffe8f34 --- /dev/null +++ b/Data/Scripts/010_Data/006_PBGenderRates.rb @@ -0,0 +1,24 @@ +module PBGenderRates + Genderless = 0 + AlwaysMale = 1 + FemaleOneEighth = 2 + Female25Percent = 3 + Female50Percent = 4 + Female75Percent = 5 + FemaleSevenEighths = 6 + AlwaysFemale = 7 + + def self.genderByte(gender) + case gender + when AlwaysMale; return 0 + when FemaleOneEighth; return 32 + when Female25Percent; return 64 + when Female50Percent; return 128 + when Female75Percent; return 192 + when FemaleSevenEighths; return 224 + when AlwaysFemale; return 254 + when Genderless; return 255 + end + return 255 # Default value (genderless) + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/007_PBExperience.rb b/Data/Scripts/010_Data/007_PBExperience.rb new file mode 100644 index 000000000..cd2acb5b5 --- /dev/null +++ b/Data/Scripts/010_Data/007_PBExperience.rb @@ -0,0 +1,197 @@ +module PBGrowthRates + Medium = MediumFast = 0 + Erratic = 1 + Fluctuating = 2 + Parabolic = MediumSlow = 3 + Fast = 4 + Slow = 5 + + def self.maxValue; return 5; end +end + + + +module PBExperience + @PBExpTable = [] + @PBExpTable[PBGrowthRates::Medium] = [ + -1, 0, 8, 27, 64, 125, 216, 343, 512, 729, + 1000, 1331, 1728, 2197, 2744, 3375, 4096, 4913, 5832, 6859, + 8000, 9261, 10648, 12167, 13824, 15625, 17576, 19683, 21952, 24389, + 27000, 29791, 32768, 35937, 39304, 42875, 46656, 50653, 54872, 59319, + 64000, 68921, 74088, 79507, 85184, 91125, 97336, 103823, 110592, 117649, + 125000, 132651, 140608, 148877, 157464, 166375, 175616, 185193, 195112, 205379, + 216000, 226981, 238328, 250047, 262144, 274625, 287496, 300763, 314432, 328509, + 343000, 357911, 373248, 389017, 405224, 421875, 438976, 456533, 474552, 493039, + 512000, 531441, 551368, 571787, 592704, 614125, 636056, 658503, 681472, 704969, + 729000, 753571, 778688, 804357, 830584, 857375, 884736, 912673, 941192, 970299, + 1000000] + @PBExpTable[PBGrowthRates::Erratic] = [ + -1, 0, 15, 52, 122, 237, 406, 637, 942, 1326, + 1800, 2369, 3041, 3822, 4719, 5737, 6881, 8155, 9564, 11111, + 12800, 14632, 16610, 18737, 21012, 23437, 26012, 28737, 31610, 34632, + 37800, 41111, 44564, 48155, 51881, 55737, 59719, 63822, 68041, 72369, + 76800, 81326, 85942, 90637, 95406, 100237, 105122, 110052, 115015, 120001, + 125000, 131324, 137795, 144410, 151165, 158056, 165079, 172229, 179503, 186894, + 194400, 202013, 209728, 217540, 225443, 233431, 241496, 249633, 257834, 267406, + 276458, 286328, 296358, 305767, 316074, 326531, 336255, 346965, 357812, 367807, + 378880, 390077, 400293, 411686, 423190, 433572, 445239, 457001, 467489, 479378, + 491346, 501878, 513934, 526049, 536557, 548720, 560922, 571333, 583539, 591882, + 600000] + @PBExpTable[PBGrowthRates::Fluctuating] = [ + -1, 0, 4, 13, 32, 65, 112, 178, 276, 393, + 540, 745, 967, 1230, 1591, 1957, 2457, 3046, 3732, 4526, + 5440, 6482, 7666, 9003, 10506, 12187, 14060, 16140, 18439, 20974, + 23760, 26811, 30146, 33780, 37731, 42017, 46656, 50653, 55969, 60505, + 66560, 71677, 78533, 84277, 91998, 98415, 107069, 114205, 123863, 131766, + 142500, 151222, 163105, 172697, 185807, 196322, 210739, 222231, 238036, 250562, + 267840, 281456, 300293, 315059, 335544, 351520, 373744, 390991, 415050, 433631, + 459620, 479600, 507617, 529063, 559209, 582187, 614566, 639146, 673863, 700115, + 737280, 765275, 804997, 834809, 877201, 908905, 954084, 987754, 1035837, 1071552, + 1122660, 1160499, 1214753, 1254796, 1312322, 1354652, 1415577, 1460276, 1524731, 1571884, + 1640000] + @PBExpTable[PBGrowthRates::Parabolic] = [ + -1, 0, 9, 57, 96, 135, 179, 236, 314, 419, + 560, 742, 973, 1261, 1612, 2035, 2535, 3120, 3798, 4575, + 5460, 6458, 7577, 8825, 10208, 11735, 13411, 15244, 17242, 19411, + 21760, 24294, 27021, 29949, 33084, 36435, 40007, 43808, 47846, 52127, + 56660, 61450, 66505, 71833, 77440, 83335, 89523, 96012, 102810, 109923, + 117360, 125126, 133229, 141677, 150476, 159635, 169159, 179056, 189334, 199999, + 211060, 222522, 234393, 246681, 259392, 272535, 286115, 300140, 314618, 329555, + 344960, 360838, 377197, 394045, 411388, 429235, 447591, 466464, 485862, 505791, + 526260, 547274, 568841, 590969, 613664, 636935, 660787, 685228, 710266, 735907, + 762160, 789030, 816525, 844653, 873420, 902835, 932903, 963632, 995030, 1027103, + 1059860] + @PBExpTable[PBGrowthRates::Fast] = [ + -1, 0, 6, 21, 51, 100, 172, 274, 409, 583, + 800, 1064, 1382, 1757, 2195, 2700, 3276, 3930, 4665, 5487, + 6400, 7408, 8518, 9733, 11059, 12500, 14060, 15746, 17561, 19511, + 21600, 23832, 26214, 28749, 31443, 34300, 37324, 40522, 43897, 47455, + 51200, 55136, 59270, 63605, 68147, 72900, 77868, 83058, 88473, 94119, + 100000, 106120, 112486, 119101, 125971, 133100, 140492, 148154, 156089, 164303, + 172800, 181584, 190662, 200037, 209715, 219700, 229996, 240610, 251545, 262807, + 274400, 286328, 298598, 311213, 324179, 337500, 351180, 365226, 379641, 394431, + 409600, 425152, 441094, 457429, 474163, 491300, 508844, 526802, 545177, 563975, + 583200, 602856, 622950, 643485, 664467, 685900, 707788, 730138, 752953, 776239, + 800000] + @PBExpTable[PBGrowthRates::Slow] = [ + -1, 0, 10, 33, 80, 156, 270, 428, 640, 911, + 1250, 1663, 2160, 2746, 3430, 4218, 5120, 6141, 7290, 8573, + 10000, 11576, 13310, 15208, 17280, 19531, 21970, 24603, 27440, 30486, + 33750, 37238, 40960, 44921, 49130, 53593, 58320, 63316, 68590, 74148, + 80000, 86151, 92610, 99383, 106480, 113906, 121670, 129778, 138240, 147061, + 156250, 165813, 175760, 186096, 196830, 207968, 219520, 231491, 243890, 256723, + 270000, 283726, 297910, 312558, 327680, 343281, 359370, 375953, 393040, 410636, + 428750, 447388, 466560, 486271, 506530, 527343, 548720, 570666, 593190, 616298, + 640000, 664301, 689210, 714733, 740880, 767656, 795070, 823128, 851840, 881211, + 911250, 941963, 973360, 1005446, 1038230, 1071718, 1105920, 1140841, 1176490, 1212873, + 1250000] + + # Returns the maximum level a Pokémon can attain. If you want to make it vary, + # here's where you put your formulae. Note that this is also called by the + # Compiler, which happens before anything (e.g. Game Switches/Variables, the + # player's data) is loaded, so make sure they exist before using them, and + # make this method return the most maximum ever level if they don't. + def self.maxLevel + return MAXIMUM_LEVEL + end + + # Erratic (600000): + # For levels 0-50: n**3([100-n]/50) + # For levels 51-68: n**3([150-n]/100) + # For levels 69-98: n**3(1.274-[1/50][n/3]-p(n mod 3)) + # where p(x) = array(0.000,0.008,0.014)[x] + # For levels 99-100: n**3([160-n]/100) + # Fluctuating (1640000): + # For levels 0-15 : n**3([24+{(n+1)/3}]/50) + # For levels 16-35: n**3([14+n]/50) + # For levels 36-100: n**3([32+{n/2}]/50) + + def self.pbGetExpInternal(level,growth) + if level <= 100 + # Refer to experience table for levels 100 and less + return @PBExpTable[growth][level] + end + # Level 101+, use formulae + case growth + when PBGrowthRates::Medium # 1000000 + return level ** 3 + when PBGrowthRates::Erratic # 600000 + # Different formula that causes 600000 EXP at level 100 + return ((level ** 4) * 0.6 / 100).floor + when PBGrowthRates::Fluctuating # 1640000 + # Different formula that causes 1640000 EXP at level 100 + rate = 82 + if level > 100 + # Slow rate with increasing level + rate -= (level - 100) / 2 + rate = 40 if rate < 40 + end + return ((level ** 3) * (level * rate / 100) / 50.0).floor + when PBGrowthRates::Parabolic # 1059860 + return ((level ** 3) * 6 / 5) - 15 * (level ** 2) + 100 * level - 140 + when PBGrowthRates::Fast # 800000 + return (level ** 3) * 4 / 5 + when PBGrowthRates::Slow # 1250000 + return (level ** 3) * 5 / 4 + end + return 0 + end + + # Gets the maximum Exp Points possible for the given growth rate. + # growth -- Growth rate. + def self.pbGetMaxExperience(growth) + if growth<0 || growth>PBGrowthRates.maxValue + return ArgumentError.new("The growth rate is invalid.") + end + return pbGetExpInternal(maxLevel,growth) + end + + # Gets the number of Exp Points needed to reach the given + # level with the given growth rate. + # growth -- Growth rate. + def self.pbGetStartExperience(level,growth) + if growth<0 || growth>PBGrowthRates.maxValue + return ArgumentError.new("The growth rate is invalid.") + end + if level<0 + return ArgumentError.new("The level is invalid.") + end + mLevel = maxLevel + level = mLevel if level>mLevel + return pbGetExpInternal(level,growth) + end + + # Adds experience points ensuring that the new total doesn't + # exceed the maximum Exp. Points for the given growth rate. + # currexp -- Current Exp Points. + # expgain -- Exp. Points to add + # growth -- Growth rate. + def self.pbAddExperience(currexp,expgain,growth) + if growth<0 || growth>PBGrowthRates.maxValue + return ArgumentError.new("The growth rate is invalid.") + end + exp = currexp+expgain + maxexp = pbGetExpInternal(maxLevel,growth) + exp = maxexp if exp>maxexp + return exp + end + + # Calculates a level given the number of Exp Points and growth rate. + # growth -- Growth rate. + def self.pbGetLevelFromExperience(exp,growth) + if growth<0 || growth>PBGrowthRates.maxValue + return ArgumentError.new("The growth rate is invalid.") + end + mLevel = maxLevel + maxexp = pbGetExpInternal(mLevel,growth) + exp = maxexp if exp>maxexp + i = 0 + for j in 0..mLevel + currentExp = pbGetExpInternal(i,growth) + return i if exp==currentExp + return i-1 if exp0 + return @defense + end + + attr_writer :defense + + def spdef + return @defense if @battle.field.effects[PBEffects::WonderRoom]>0 + return @spdef + end + + attr_writer :spdef + + attr_reader :hp + + def hp=(value) + @hp = value.to_i + @pokemon.hp = value.to_i if @pokemon + end + + def fainted?; return @hp<=0; end + alias isFainted? fainted? + + attr_reader :status + + def status=(value) + @effects[PBEffects::Truant] = false if @status==PBStatuses::SLEEP && value!=PBStatuses::SLEEP + @effects[PBEffects::Toxic] = 0 if value!=PBStatuses::POISON + @status = value + @pokemon.status = value if @pokemon + self.statusCount = 0 if value!=PBStatuses::POISON && value!=PBStatuses::SLEEP + @battle.scene.pbRefreshOne(@index) + end + + attr_reader :statusCount + + def statusCount=(value) + @statusCount = value + @pokemon.statusCount = value if @pokemon + end + + #============================================================================= + # Properties from Pokémon + #============================================================================= + def happiness; return @pokemon ? @pokemon.happiness : 0; end + def nature; return @pokemon ? @pokemon.nature : 0; end + def pokerusStage; return @pokemon ? @pokemon.pokerusStage : 0; end + + #============================================================================= + # Mega Evolution, Primal Reversion, Shadow Pokémon + #============================================================================= + def hasMega? + return false if @effects[PBEffects::Transform] + return @pokemon && @pokemon.hasMegaForm? + end + + def mega?; return @pokemon && @pokemon.mega?; end + alias isMega? mega? + + def hasPrimal? + return false if @effects[PBEffects::Transform] + return @pokemon && @pokemon.hasPrimalForm? + end + + def primal?; return @pokemon && @pokemon.primal?; end + alias isPrimal? primal? + + def shadowPokemon?; return false; end + alias isShadow? shadowPokemon? + + def inHyperMode?; return false; end + + #============================================================================= + # Display-only properties + #============================================================================= + def name + return @effects[PBEffects::Illusion].name if @effects[PBEffects::Illusion] + return @name + end + + attr_writer :name + + def displayPokemon + return @effects[PBEffects::Illusion] if @effects[PBEffects::Illusion] + return self.pokemon + end + + def displaySpecies + return @effects[PBEffects::Illusion].species if @effects[PBEffects::Illusion] + return self.species + end + + def displayGender + return @effects[PBEffects::Illusion].gender if @effects[PBEffects::Illusion] + return self.gender + end + + def displayForm + return @effects[PBEffects::Illusion].form if @effects[PBEffects::Illusion] + return self.form + end + + def shiny? + return @effects[PBEffects::Illusion].shiny? if @effects[PBEffects::Illusion] + return @pokemon && @pokemon.shiny? + end + alias isShiny? shiny? + + def owned? + return false if !@battle.wildBattle? + return $Trainer.owned[displaySpecies] + end + alias owned owned? + + def abilityName; return PBAbilities.getName(@ability); end + def itemName; return PBItems.getName(@item); end + + def pbThis(lowerCase=false) + if opposes? + if @battle.trainerBattle? + return lowerCase ? _INTL("the opposing {1}",name) : _INTL("The opposing {1}",name) + else + return lowerCase ? _INTL("the wild {1}",name) : _INTL("The wild {1}",name) + end + elsif !pbOwnedByPlayer? + return lowerCase ? _INTL("the ally {1}",name) : _INTL("The ally {1}",name) + end + return name + end + + def pbTeam(lowerCase=false) + if opposes? + return lowerCase ? _INTL("the opposing team") : _INTL("The opposing team") + end + return lowerCase ? _INTL("your team") : _INTL("Your team") + end + + def pbOpposingTeam(lowerCase=false) + if opposes? + return lowerCase ? _INTL("your team") : _INTL("Your team") + end + return lowerCase ? _INTL("the opposing team") : _INTL("The opposing team") + end + + #============================================================================= + # Calculated properties + #============================================================================= + def pbSpeed + return 1 if fainted? + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + stage = @stages[PBStats::SPEED] + 6 + speed = @speed*stageMul[stage]/stageDiv[stage] + speedMult = 0x1000 + # Ability effects that alter calculated Speed + if abilityActive? + speedMult = BattleHandlers.triggerSpeedCalcAbility(@ability,self,speedMult) + end + # Item effects that alter calculated Speed + if itemActive? + speedMult = BattleHandlers.triggerSpeedCalcItem(@item,self,speedMult) + end + # Other effects + speedMult *= 2 if pbOwnSide.effects[PBEffects::Tailwind]>0 + speedMult /= 2 if pbOwnSide.effects[PBEffects::Swamp]>0 + # Paralysis + if status==PBStatuses::PARALYSIS && !hasActiveAbility?(:QUICKFEET) + speedMult /= (NEWEST_BATTLE_MECHANICS) ? 2 : 4 + end + # Badge multiplier + if @battle.internalBattle && pbOwnedByPlayer? && + @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_SPEED + speedMult *= 1.1 + end + # Calculation + return [(speed.to_f*speedMult/0x1000).round,1].max + end + + def pbWeight + ret = (@pokemon) ? @pokemon.weight : 500 + ret += @effects[PBEffects::WeightChange] + ret = 1 if ret<1 + if abilityActive? && !@battle.moldBreaker + ret = BattleHandlers.triggerWeightCalcAbility(@ability,self,ret) + end + if itemActive? + ret = BattleHandlers.triggerWeightCalcItem(@item,self,ret) + end + return [ret,1].max + end + + #============================================================================= + # Queries about what the battler has + #============================================================================= + def plainStats + ret = [] + ret[PBStats::ATTACK] = self.attack + ret[PBStats::DEFENSE] = self.defense + ret[PBStats::SPATK] = self.spatk + ret[PBStats::SPDEF] = self.spdef + ret[PBStats::SPEED] = self.speed + return ret + end + + # Returns the active types of this Pokémon. The array should not include the + # same type more than once, and should not include any invalid type numbers + # (e.g. -1). + def pbTypes(withType3=false) + ret = [@type1] + ret.push(@type2) if @type2!=@type1 + # Burn Up erases the Fire-type. + if @effects[PBEffects::BurnUp] + ret.reject! { |type| isConst?(type,PBTypes,:FIRE) } + end + # Roost erases the Flying-type. If there are no types left, adds the Normal- + # type. + if @effects[PBEffects::Roost] + ret.reject! { |type| isConst?(type,PBTypes,:FLYING) } + ret.push(getConst(PBTypes,:NORMAL) || 0) if ret.length==0 + end + # Add the third type specially. + if withType3 && @effects[PBEffects::Type3]>=0 + ret.push(@effects[PBEffects::Type3]) if !ret.include?(@effects[PBEffects::Type3]) + end + return ret + end + + def pbHasType?(type) + type = getConst(PBTypes,type) if type.is_a?(Symbol) || type.is_a?(String) + return false if !type || type<0 + activeTypes = pbTypes(true) + return activeTypes.include?(type) + end + + def pbHasOtherType?(type) + type = getConst(PBTypes,type) if type.is_a?(Symbol) || type.is_a?(String) + return false if !type || type<0 + activeTypes = pbTypes(true) + activeTypes.reject! { |t| t==type } + return activeTypes.length>0 + end + + # NOTE: Do not create any held item which affects whether a Pokémon's ability + # is active. The ability Klutz affects whether a Pokémon's item is + # active, and the code for the two combined would cause an infinite loop + # (regardless of whether any Pokémon actualy has either the ability or + # the item - the code existing is enough to cause the loop). + def abilityActive?(ignoreFainted=false) + return false if fainted? && !ignoreFainted + return false if @effects[PBEffects::GastroAcid] + return true + end + + def hasActiveAbility?(ability,ignoreFainted=false) + return false if !abilityActive?(ignoreFainted) + if ability.is_a?(Array) + ability.each do |a| + a = getID(PBAbilities,a) + return true if a!=0 && a==@ability + end + return false + end + ability = getID(PBAbilities,ability) + return ability!=0 && ability==@ability + end + alias hasWorkingAbility hasActiveAbility? + + def nonNegatableAbility? + abilityBlacklist = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be negated +# :FORECAST, # This can be negated + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + failed = false + abilityBlacklist.each do |abil| + return true if isConst?(@ability,PBAbilities,abil) + end + return false + end + + def itemActive?(ignoreFainted=false) + return false if fainted? && !ignoreFainted + return false if @effects[PBEffects::Embargo]>0 + return false if @battle.field.effects[PBEffects::MagicRoom]>0 + return false if hasActiveAbility?(:KLUTZ,ignoreFainted) + return true + end + + def hasActiveItem?(item,ignoreFainted=false) + return false if !itemActive?(ignoreFainted) + if item.is_a?(Array) + item.each do |i| + i = getID(PBItems,i) + return true if i!=0 && i==@item + end + return false + end + item = getID(PBItems,item) + return item!=0 && item==@item + end + alias hasWorkingItem hasActiveItem? + + # Returns whether the specified item will be unlosable for this Pokémon. + def unlosableItem?(item) + return false if item<=0 + return true if pbIsMail?(item) + return false if @effects[PBEffects::Transform] + # Items that change a Pokémon's form + return true if @pokemon && @pokemon.getMegaForm(true)>0 # Mega Stone + return pbIsUnlosableItem?(item,@species,@ability) + end + + def eachMove + @moves.each { |m| yield m if m && m.id!=0 } + end + + def eachMoveWithIndex + @moves.each_with_index { |m,i| yield m,i if m && m.id!=0 } + end + + def pbHasMove?(id) + id = getID(PBMoves,id) + return false if !id || id<=0 + eachMove { |m| return true if m.id==id } + return false + end + + def pbHasMoveType?(type) + type = getConst(PBTypes,type) + return false if !type || type<0 + eachMove { |m| return true if m.type==type } + return false + end + + def pbHasMoveFunction?(*arg) + return false if !code + eachMove do |m| + arg.each { |code| return true if m.function==code } + end + return false + end + + def hasMoldBreaker? + return hasActiveAbility?([:MOLDBREAKER,:TERAVOLT,:TURBOBLAZE]) + end + + def canChangeType? + return false if isConst?(@ability,PBAbilities,:MULTITYPE) || + isConst?(@ability,PBAbilities,:RKSSYSTEM) + return true + end + + def airborne? + return false if hasActiveItem?(:IRONBALL) + return false if @effects[PBEffects::Ingrain] + return false if @effects[PBEffects::SmackDown] + return false if @battle.field.effects[PBEffects::Gravity]>0 + return true if pbHasType?(:FLYING) + return true if hasActiveAbility?(:LEVITATE) && !@battle.moldBreaker + return true if hasActiveItem?(:AIRBALLOON) + return true if @effects[PBEffects::MagnetRise]>0 + return true if @effects[PBEffects::Telekinesis]>0 + return false + end + + def affectedByTerrain? + return false if airborne? + return false if semiInvulnerable? + return true + end + + def takesIndirectDamage?(showMsg=false) + return false if fainted? + if hasActiveAbility?(:MAGICGUARD) + if showMsg + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) + else + @battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return false + end + return true + end + + def takesSandstormDamage? + return false if !takesIndirectDamage? + return false if pbHasType?(:GROUND) || pbHasType?(:ROCK) || pbHasType?(:STEEL) + return false if inTwoTurnAttack?("0CA","0CB") # Dig, Dive + return false if hasActiveAbility?([:OVERCOAT,:SANDFORCE,:SANDRUSH,:SANDVEIL]) + return false if hasActiveItem?(:SAFETYGOGGLES) + return true + end + + def takesHailDamage? + return false if !takesIndirectDamage? + return false if pbHasType?(:ICE) + return false if inTwoTurnAttack?("0CA","0CB") # Dig, Dive + return false if hasActiveAbility?([:OVERCOAT,:ICEBODY,:SNOWCLOAK]) + return false if hasActiveItem?(:SAFETYGOGGLES) + return true + end + + def takesShadowSkyDamage? + return false if fainted? + return false if shadowPokemon? + return true + end + + def affectedByPowder?(showMsg=false) + return false if fainted? + return true if !NEWEST_BATTLE_MECHANICS + if pbHasType?(:GRASS) + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) if showMsg + return false + end + if hasActiveAbility?(:OVERCOAT) && !@battle.moldBreaker + if showMsg + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) + else + @battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return false + end + if hasActiveItem?(:SAFETYGOGGLES) + if showMsg + @battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!",pbThis,itemName)) + end + return false + end + return true + end + + def canHeal? + return false if fainted? || @hp>=@totalhp + return false if @effects[PBEffects::HealBlock]>0 + return true + end + + def affectedByContactEffect?(showMsg=false) + return false if fainted? + if hasActiveItem?(:PROTECTIVEPADS) + @battle.pbDisplay(_INTL("{1} protected itself with the {2}!",pbThis,itemName)) if showMsg + return false + end + return true + end + + def movedThisRound? + return @lastRoundMoved && @lastRoundMoved==@battle.turnCount + end + + def usingMultiTurnAttack? + return true if @effects[PBEffects::TwoTurnAttack]>0 + return true if @effects[PBEffects::HyperBeam]>0 + return true if @effects[PBEffects::Rollout]>0 + return true if @effects[PBEffects::Outrage]>0 + return true if @effects[PBEffects::Uproar]>0 + return true if @effects[PBEffects::Bide]>0 + return false + end + + def inTwoTurnAttack?(*arg) + return false if @effects[PBEffects::TwoTurnAttack]==0 + ttaFunction = pbGetMoveData(@effects[PBEffects::TwoTurnAttack],MOVE_FUNCTION_CODE) + arg.each { |a| return true if a==ttaFunction } + return false + end + + def semiInvulnerable? + return inTwoTurnAttack?("0C9","0CA","0CB","0CC","0CD","0CE","14D") + end + + def pbEncoredMoveIndex + return -1 if @effects[PBEffects::Encore]==0 || @effects[PBEffects::EncoreMove]==0 + ret = -1 + eachMoveWithIndex do |m,i| + next if m.id!=@effects[PBEffects::EncoreMove] + ret = i + break + end + return ret + end + + def initialItem + return @battle.initialItems[@index&1][@pokemonIndex] + end + + def setInitialItem(newItem) + @battle.initialItems[@index&1][@pokemonIndex] = newItem + end + + def recycleItem + return @battle.recycleItems[@index&1][@pokemonIndex] + end + + def setRecycleItem(newItem) + @battle.recycleItems[@index&1][@pokemonIndex] = newItem + end + + def belched? + return @battle.belch[@index&1][@pokemonIndex] + end + + def setBelched + @battle.belch[@index&1][@pokemonIndex] = true + end + + #============================================================================= + # Methods relating to this battler's position on the battlefield + #============================================================================= + # Returns whether the given position belongs to the opposing Pokémon's side. + def opposes?(i=0) + i = i.index if i.respond_to?("index") + return (@index&1)!=(i&1) + end + + # Returns whether the given position/battler is near to self. + def near?(i) + i = i.index if i.respond_to?("index") + return @battle.nearBattlers?(@index,i) + end + + # Returns whether self is owned by the player. + def pbOwnedByPlayer? + return @battle.pbOwnedByPlayer?(@index) + end + + # Returns 0 if self is on the player's side, or 1 if self is on the opposing + # side. + def idxOwnSide + return @index&1 + end + + # Returns 1 if self is on the player's side, or 0 if self is on the opposing + # side. + def idxOpposingSide + return (@index&1)^1 + end + + # Returns the data structure for this battler's side. + def pbOwnSide + return @battle.sides[idxOwnSide] + end + + # Returns the data structure for the opposing Pokémon's side. + def pbOpposingSide + return @battle.sides[idxOpposingSide] + end + + # Yields each unfainted ally Pokémon. + def eachAlly + @battle.battlers.each do |b| + yield b if b && !b.fainted? && !b.opposes?(@index) && b.index!=@index + end + end + + # Yields each unfainted opposing Pokémon. + def eachOpposing + @battle.battlers.each { |b| yield b if b && !b.fainted? && b.opposes?(@index) } + end + + # Returns the battler that is most directly opposite to self. unfaintedOnly is + # whether it should prefer to return a non-fainted battler. + def pbDirectOpposing(unfaintedOnly=false) + @battle.pbGetOpposingIndicesInOrder(@index).each do |i| + next if !@battle.battlers[i] + break if unfaintedOnly && @battle.battlers[i].fainted? + return @battle.battlers[i] + end + # Wanted an unfainted battler but couldn't find one; make do with a fainted + # battler + @battle.pbGetOpposingIndicesInOrder(@index).each do |i| + return @battle.battlers[i] if @battle.battlers[i] + end + return @battle.battlers[(@index^1)] + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/002_Battler_Initialize.rb b/Data/Scripts/011_Battle/001_Battler/002_Battler_Initialize.rb new file mode 100644 index 000000000..36ce6381c --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/002_Battler_Initialize.rb @@ -0,0 +1,326 @@ +class PokeBattle_Battler + #============================================================================= + # Creating a battler + #============================================================================= + def initialize(btl,idxBattler) + @battle = btl + @index = idxBattler + @captured = false + @dummy = false + @stages = [] + @effects = [] + @damageState = PokeBattle_DamageState.new + pbInitBlank + pbInitEffects(false) + end + + def pbInitBlank + @name = "" + @species = 0 + @form = 0 + @level = 0 + @hp = @totalhp = 0 + @type1 = @type2 = 0 + @ability = 0 + @item = 0 + @gender = 0 + @attack = @defense = @spatk = @spdef = @speed = 0 + @status = PBStatuses::NONE + @statusCount = 0 + @pokemon = nil + @pokemonIndex = -1 + @participants = [] + @moves = [] + @iv = [0,0,0,0,0,0] + end + + # Used by Future Sight only, when Future Sight's user is no longer in battle. + def pbInitDummyPokemon(pkmn,idxParty) + raise _INTL("An egg can't be an active Pokémon.") if pkmn.egg? + @name = pkmn.name + @species = pkmn.species + @form = pkmn.form + @level = pkmn.level + @hp = pkmn.hp + @totalhp = pkmn.totalhp + @type1 = pkmn.type1 + @type2 = pkmn.type2 + # ability and item intentionally not copied across here + @gender = pkmn.gender + @attack = pkmn.attack + @defense = pkmn.defense + @spatk = pkmn.spatk + @spdef = pkmn.spdef + @speed = pkmn.speed + @status = pkmn.status + @statusCount = pkmn.statusCount + @pokemon = pkmn + @pokemonIndex = idxParty + @participants = [] + # moves intentionally not copied across here + @iv = pkmn.iv.clone + @dummy = true + end + + def pbInitialize(pkmn,idxParty,batonPass=false) + pbInitPokemon(pkmn,idxParty) + pbInitEffects(batonPass) + end + + def pbInitPokemon(pkmn,idxParty) + raise _INTL("An egg can't be an active Pokémon.") if pkmn.egg? + @name = pkmn.name + @species = pkmn.species + @form = pkmn.form + @level = pkmn.level + @hp = pkmn.hp + @totalhp = pkmn.totalhp + @type1 = pkmn.type1 + @type2 = pkmn.type2 + @ability = pkmn.ability + @item = pkmn.item + @gender = pkmn.gender + @attack = pkmn.attack + @defense = pkmn.defense + @spatk = pkmn.spatk + @spdef = pkmn.spdef + @speed = pkmn.speed + @status = pkmn.status + @statusCount = pkmn.statusCount + @pokemon = pkmn + @pokemonIndex = idxParty + @participants = [] # Participants earn Exp. if this battler is defeated + @moves = [] + pkmn.moves.each_with_index do |m,i| + @moves[i] = PokeBattle_Move.pbFromPBMove(@battle,m) + end + @iv = pkmn.iv.clone + end + + def pbInitEffects(batonPass) + if batonPass + # These effects are passed on if Baton Pass is used, but they need to be + # reapplied + @effects[PBEffects::LaserFocus] = (@effects[PBEffects::LaserFocus]>0) ? 2 : 0 + @effects[PBEffects::LockOn] = (@effects[PBEffects::LockOn]>0) ? 2 : 0 + if @effects[PBEffects::PowerTrick] + @attack,@defense = @defense,@attack + end + # These effects are passed on if Baton Pass is used, but they need to be + # cancelled in certain circumstances anyway + @effects[PBEffects::Telekinesis] = 0 if isConst?(@species,PBSpecies,:GENGAR) && mega? + @effects[PBEffects::GastroAcid] = false if nonNegatableAbility? + else + # These effects are passed on if Baton Pass is used + @stages[PBStats::ATTACK] = 0 + @stages[PBStats::DEFENSE] = 0 + @stages[PBStats::SPEED] = 0 + @stages[PBStats::SPATK] = 0 + @stages[PBStats::SPDEF] = 0 + @stages[PBStats::EVASION] = 0 + @stages[PBStats::ACCURACY] = 0 + @effects[PBEffects::AquaRing] = false + @effects[PBEffects::Confusion] = 0 + @effects[PBEffects::Curse] = false + @effects[PBEffects::Embargo] = 0 + @effects[PBEffects::FocusEnergy] = 0 + @effects[PBEffects::GastroAcid] = false + @effects[PBEffects::HealBlock] = 0 + @effects[PBEffects::Ingrain] = false + @effects[PBEffects::LaserFocus] = 0 + @effects[PBEffects::LeechSeed] = -1 + @effects[PBEffects::LockOn] = 0 + @effects[PBEffects::LockOnPos] = -1 + @effects[PBEffects::MagnetRise] = 0 + @effects[PBEffects::PerishSong] = 0 + @effects[PBEffects::PerishSongUser] = -1 + @effects[PBEffects::PowerTrick] = false + @effects[PBEffects::Substitute] = 0 + @effects[PBEffects::Telekinesis] = 0 + end + @damageState.reset + @fainted = (@hp==0) + @initialHP = 0 + @lastAttacker = [] + @lastFoeAttacker = [] + @lastHPLost = 0 + @lastHPLostFromFoe = 0 + @tookDamage = false + @tookPhysicalHit = false + @lastMoveUsed = -1 + @lastMoveUsedType = -1 + @lastRegularMoveUsed = -1 + @lastRegularMoveTarget = -1 + @lastRoundMoved = -1 + @lastMoveFailed = false + @lastRoundMoveFailed = false + @movesUsed = [] + @turnCount = 0 + @effects[PBEffects::Attract] = -1 + @battle.eachBattler do |b| # Other battlers no longer attracted to self + b.effects[PBEffects::Attract] = -1 if b.effects[PBEffects::Attract]==@index + end + @effects[PBEffects::BanefulBunker] = false + @effects[PBEffects::BeakBlast] = false + @effects[PBEffects::Bide] = 0 + @effects[PBEffects::BideDamage] = 0 + @effects[PBEffects::BideTarget] = -1 + @effects[PBEffects::BurnUp] = false + @effects[PBEffects::Charge] = 0 + @effects[PBEffects::ChoiceBand] = -1 + @effects[PBEffects::Counter] = -1 + @effects[PBEffects::CounterTarget] = -1 + @effects[PBEffects::Dancer] = false + @effects[PBEffects::DefenseCurl] = false + @effects[PBEffects::DestinyBond] = false + @effects[PBEffects::DestinyBondPrevious] = false + @effects[PBEffects::DestinyBondTarget] = -1 + @effects[PBEffects::Disable] = 0 + @effects[PBEffects::DisableMove] = 0 + @effects[PBEffects::Electrify] = false + @effects[PBEffects::Encore] = 0 + @effects[PBEffects::EncoreMove] = 0 + @effects[PBEffects::Endure] = false + @effects[PBEffects::FirstPledge] = 0 + @effects[PBEffects::FlashFire] = false + @effects[PBEffects::Flinch] = false + @effects[PBEffects::FocusPunch] = false + @effects[PBEffects::FollowMe] = 0 + @effects[PBEffects::Foresight] = false + @effects[PBEffects::FuryCutter] = 0 + @effects[PBEffects::GemConsumed] = 0 + @effects[PBEffects::Grudge] = false + @effects[PBEffects::HelpingHand] = false + @effects[PBEffects::HyperBeam] = 0 + @effects[PBEffects::Illusion] = nil + if hasActiveAbility?(:ILLUSION) + idxLastParty = @battle.pbLastInTeam(@index) + if idxLastParty!=@pokemonIndex + @effects[PBEffects::Illusion] = @battle.pbParty(@index)[idxLastParty] + end + end + @effects[PBEffects::Imprison] = false + @effects[PBEffects::Instruct] = false + @effects[PBEffects::Instructed] = false + @effects[PBEffects::KingsShield] = false + @battle.eachBattler do |b| # Other battlers lose their lock-on against self + next if b.effects[PBEffects::LockOn]==0 + next if b.effects[PBEffects::LockOnPos]!=@index + b.effects[PBEffects::LockOn] = 0 + b.effects[PBEffects::LockOnPos] = -1 + end + @effects[PBEffects::MagicBounce] = false + @effects[PBEffects::MagicCoat] = false + @effects[PBEffects::MeanLook] = -1 + @battle.eachBattler do |b| # Other battlers no longer blocked by self + b.effects[PBEffects::MeanLook] = -1 if b.effects[PBEffects::MeanLook]==@index + end + @effects[PBEffects::MeFirst] = false + @effects[PBEffects::Metronome] = 0 + @effects[PBEffects::MicleBerry] = false + @effects[PBEffects::Minimize] = false + @effects[PBEffects::MiracleEye] = false + @effects[PBEffects::MirrorCoat] = -1 + @effects[PBEffects::MirrorCoatTarget] = -1 + @effects[PBEffects::MoveNext] = false + @effects[PBEffects::MudSport] = false + @effects[PBEffects::Nightmare] = false + @effects[PBEffects::Outrage] = 0 + @effects[PBEffects::ParentalBond] = 0 + @effects[PBEffects::PickupItem] = 0 + @effects[PBEffects::PickupUse] = 0 + @effects[PBEffects::Pinch] = false + @effects[PBEffects::Powder] = false + @effects[PBEffects::Prankster] = false + @effects[PBEffects::PriorityAbility] = false + @effects[PBEffects::PriorityItem] = false + @effects[PBEffects::Protect] = false + @effects[PBEffects::ProtectRate] = 1 + @effects[PBEffects::Pursuit] = false + @effects[PBEffects::Quash] = 0 + @effects[PBEffects::Rage] = false + @effects[PBEffects::RagePowder] = false + @effects[PBEffects::Revenge] = 0 + @effects[PBEffects::Rollout] = 0 + @effects[PBEffects::Roost] = false + @effects[PBEffects::SkyDrop] = -1 + @battle.eachBattler do |b| # Other battlers no longer Sky Dropped by self + b.effects[PBEffects::SkyDrop] = -1 if b.effects[PBEffects::SkyDrop]==@index + end + @effects[PBEffects::SlowStart] = 0 + @effects[PBEffects::SmackDown] = false + @effects[PBEffects::Snatch] = 0 + @effects[PBEffects::SpikyShield] = false + @effects[PBEffects::Spotlight] = 0 + @effects[PBEffects::Stockpile] = 0 + @effects[PBEffects::StockpileDef] = 0 + @effects[PBEffects::StockpileSpDef] = 0 + @effects[PBEffects::Taunt] = 0 + @effects[PBEffects::ThroatChop] = 0 + @effects[PBEffects::Torment] = false + @effects[PBEffects::Toxic] = 0 + @effects[PBEffects::Transform] = false + @effects[PBEffects::TransformSpecies] = 0 + @effects[PBEffects::Trapping] = 0 + @effects[PBEffects::TrappingMove] = 0 + @effects[PBEffects::TrappingUser] = -1 + @battle.eachBattler do |b| # Other battlers no longer trapped by self + next if b.effects[PBEffects::TrappingUser]!=@index + b.effects[PBEffects::Trapping] = 0 + b.effects[PBEffects::TrappingUser] = -1 + end + @effects[PBEffects::Truant] = false + @effects[PBEffects::TwoTurnAttack] = 0 + @effects[PBEffects::Type3] = -1 + @effects[PBEffects::Unburden] = false + @effects[PBEffects::Uproar] = 0 + @effects[PBEffects::WaterSport] = false + @effects[PBEffects::WeightChange] = 0 + @effects[PBEffects::Yawn] = 0 + end + + #============================================================================= + # Refreshing a battler's properties + #============================================================================= + def pbUpdate(fullChange=false) + return if !@pokemon + @pokemon.calcStats + @level = @pokemon.level + @hp = @pokemon.hp + @totalhp = @pokemon.totalhp + if !@effects[PBEffects::Transform] + @attack = @pokemon.attack + @defense = @pokemon.defense + @spatk = @pokemon.spatk + @spdef = @pokemon.spdef + @speed = @pokemon.speed + if fullChange + @type1 = @pokemon.type1 + @type2 = @pokemon.type2 + @ability = @pokemon.ability + end + end + end + + # Used only to erase the battler of a Shadow Pokémon that has been snagged. + def pbReset + @pokemon = nil + @pokemonIndex = -1 + @hp = 0 + pbInitEffects(false) + @participants = [] + # Reset status + @status = PBStatuses::NONE + @statusCount = 0 + # Reset choice + @battle.pbClearChoice(@index) + end + + # Update which Pokémon will gain Exp if this battler is defeated. + def pbUpdateParticipants + return if fainted? || !@battle.opposes?(@index) + eachOpposing do |b| + @participants.push(b.pokemonIndex) if !@participants.include?(b.pokemonIndex) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/003_Battler_ChangeSelf.rb b/Data/Scripts/011_Battle/001_Battler/003_Battler_ChangeSelf.rb new file mode 100644 index 000000000..2b789180a --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/003_Battler_ChangeSelf.rb @@ -0,0 +1,301 @@ +class PokeBattle_Battler + #============================================================================= + # Change HP + #============================================================================= + def pbReduceHP(amt,anim=true,registerDamage=true,anyAnim=true) + amt = amt.round + amt = @hp if amt>@hp + amt = 1 if amt<1 && !fainted? + oldHP = @hp + self.hp -= amt + PBDebug.log("[HP change] #{pbThis} lost #{amt} HP (#{oldHP}=>#{@hp})") + raise _INTL("HP less than 0") if @hp<0 + raise _INTL("HP greater than total HP") if @hp>@totalhp + @battle.scene.pbHPChanged(self,oldHP,anim) if anyAnim && amt>0 + @tookDamage = true if amt>0 && registerDamage + return amt + end + + def pbRecoverHP(amt,anim=true,anyAnim=true) + amt = amt.round + amt = @totalhp-@hp if amt>@totalhp-@hp + amt = 1 if amt<1 && @hp<@totalhp + oldHP = @hp + self.hp += amt + PBDebug.log("[HP change] #{pbThis} gained #{amt} HP (#{oldHP}=>#{@hp})") + raise _INTL("HP less than 0") if @hp<0 + raise _INTL("HP greater than total HP") if @hp>@totalhp + @battle.scene.pbHPChanged(self,oldHP,anim) if anyAnim && amt>0 + return amt + end + + def pbRecoverHPFromDrain(amt,target,msg=nil) + if target.hasActiveAbility?(:LIQUIDOOZE) + oldHP = @hp + @battle.pbShowAbilitySplash(target) + pbReduceHP(amt) + @battle.pbDisplay(_INTL("{1} sucked up the liquid ooze!",pbThis)) + @battle.pbHideAbilitySplash(target) + pbItemHPHealCheck + else + msg = _INTL("{1} had its energy drained!",target.pbThis) if !msg || msg=="" + @battle.pbDisplay(msg) + if canHeal? + amt = (amt*1.3).floor if hasActiveItem?(:BIGROOT) + pbRecoverHP(amt) + end + end + end + + def pbFaint(showMessage=true) + if !fainted? + PBDebug.log("!!!***Can't faint with HP greater than 0") + return + end + return if @fainted # Has already fainted properly + @battle.pbDisplayBrief(_INTL("{1} fainted!",pbThis)) if showMessage + PBDebug.log("[Pokémon fainted] #{pbThis} (#{@index})") if !showMessage + @battle.scene.pbFaintBattler(self) + pbInitEffects(false) + # Reset status + self.status = PBStatuses::NONE + self.statusCount = 0 + # Lose happiness + if @pokemon && @battle.internalBattle + badLoss = false + @battle.eachOtherSideBattler(@index) do |b| + badLoss = true if b.level>=self.level+30 + end + @pokemon.changeHappiness((badLoss) ? "faintbad" : "faint") + end + # Reset form + @battle.peer.pbOnLeavingBattle(@battle,@pokemon,@battle.usedInBattle[idxOwnSide][@index/2]) + @pokemon.makeUnmega if mega? + @pokemon.makeUnprimal if primal? + # Do other things + @battle.pbClearChoice(@index) # Reset choice + pbOwnSide.effects[PBEffects::LastRoundFainted] = @battle.turnCount + # Check other battlers' abilities that trigger upon a battler fainting + pbAbilitiesOnFainting + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + end + + #============================================================================= + # Move PP + #============================================================================= + def pbSetPP(move,pp) + move.pp = pp + # No need to care about @effects[PBEffects::Mimic], since Mimic can't copy + # Mimic + if move.realMove && move.id==move.realMove.id && !@effects[PBEffects::Transform] + move.realMove.pp = pp + end + end + + def pbReducePP(move) + return true if usingMultiTurnAttack? + return true if move.pp<0 # Don't reduce PP for special calls of moves + return true if move.totalpp<=0 # Infinite PP, can always be used + return false if move.pp==0 # Ran out of PP, couldn't reduce + pbSetPP(move,move.pp-1) if move.pp>0 + return true + end + + def pbReducePPOther(move) + pbSetPP(move,move.pp-1) if move.pp>0 + end + + #============================================================================= + # Change type + #============================================================================= + def pbChangeTypes(newType) + if newType.is_a?(PokeBattle_Battler) + newTypes = newType.pbTypes + newTypes.push(getConst(PBTypes,:NORMAL) || 0) if newTypes.length==0 + newType3 = newType.effects[PBEffects::Type3] + newType3 = -1 if newTypes.include?(newType3) + @type1 = newTypes[0] + @type2 = (newTypes.length==1) ? newTypes[0] : newTypes[1] + @effects[PBEffects::Type3] = newType3 + else + newType = getConst(PBTypes,newType) if newType.is_a?(Symbol) || newType.is_a?(String) + @type1 = newType + @type2 = newType + @effects[PBEffects::Type3] = -1 + end + @effects[PBEffects::BurnUp] = false + @effects[PBEffects::Roost] = false + end + + #============================================================================= + # Forms + #============================================================================= + def pbChangeForm(newForm,msg) + return if fainted? || @effects[PBEffects::Transform] || @form==newForm + oldForm = @form + oldDmg = @totalhp-@hp + self.form = newForm + pbUpdate(true) + @hp = @totalhp-oldDmg + @effects[PBEffects::WeightChange] = 0 if NEWEST_BATTLE_MECHANICS + @battle.scene.pbChangePokemon(self,@pokemon) + @battle.scene.pbRefreshOne(@index) + @battle.pbDisplay(msg) if msg && msg!="" + PBDebug.log("[Form changed] #{pbThis} changed from form #{oldForm} to form #{newForm}") + @battle.pbSetSeen(self) + end + + def pbCheckFormOnStatusChange + return if fainted? || @effects[PBEffects::Transform] + # Shaymin - reverts if frozen + if isConst?(@species,PBSpecies,:SHAYMIN) && frozen? + pbChangeForm(0,_INTL("{1} transformed!",pbThis)) + end + end + + def pbCheckFormOnMovesetChange + return if fainted? || @effects[PBEffects::Transform] + # Keldeo - knowing Secret Sword + if isConst?(@species,PBSpecies,:KELDEO) + newForm = 0 + newForm = 1 if pbHasMove?(:SECRETSWORD) + pbChangeForm(newForm,_INTL("{1} transformed!",pbThis)) + end + end + + def pbCheckFormOnWeatherChange + return if fainted? || @effects[PBEffects::Transform] + # Castform - Forecast + if isConst?(@species,PBSpecies,:CASTFORM) + if hasActiveAbility?(:FORECAST) + newForm = 0 + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun; newForm = 1 + when PBWeather::Rain, PBWeather::HeavyRain; newForm = 2 + when PBWeather::Hail; newForm = 3 + end + if @form!=newForm + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(newForm,_INTL("{1} transformed!",pbThis)) + end + else + pbChangeForm(0,_INTL("{1} transformed!",pbThis)) + end + end + # Cherrim - Flower Gift + if isConst?(@species,PBSpecies,:CHERRIM) + if hasActiveAbility?(:FLOWERGIFT) + newForm = 0 + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun; newForm = 1 + end + if @form!=newForm + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(newForm,_INTL("{1} transformed!",pbThis)) + end + else + pbChangeForm(0,_INTL("{1} transformed!",pbThis)) + end + end + end + + # Checks the Pokémon's form and updates it if necessary. Used for when a + # Pokémon enters battle (endOfRound=false) and at the end of each round + # (endOfRound=true). + def pbCheckForm(endOfRound=false) + return if fainted? || @effects[PBEffects::Transform] + # Form changes upon entering battle and when the weather changes + pbCheckFormOnWeatherChange if !endOfRound + # Darmanitan - Zen Mode + if isConst?(@species,PBSpecies,:DARMANITAN) && isConst?(@ability,PBAbilities,:ZENMODE) + if @hp<=@totalhp/2 + if @form!=1 + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(1,_INTL("{1} triggered!",abilityName)) + end + elsif @form!=0 + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(0,_INTL("{1} triggered!",abilityName)) + end + end + # Minior - Shields Down + if isConst?(@species,PBSpecies,:MINIOR) && isConst?(@ability,PBAbilities,:SHIELDSDOWN) + if @hp>@totalhp/2 # Turn into Meteor form + newForm = (@form>=7) ? @form-7 : @form + if @form!=newForm + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(newForm,_INTL("{1} deactivated!",abilityName)) + elsif !endOfRound + @battle.pbDisplay(_INTL("{1} deactivated!",abilityName)) + end + elsif @form<7 # Turn into Core form + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(@form+7,_INTL("{1} activated!",abilityName)) + end + end + # Wishiwashi - Schooling + if isConst?(@species,PBSpecies,:WISHIWASHI) && isConst?(@ability,PBAbilities,:SCHOOLING) + if @level>=20 && @hp>@totalhp/4 + if @form!=1 + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(1,_INTL("{1} formed a school!",pbThis)) + end + elsif @form!=0 + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(0,_INTL("{1} stopped schooling!",pbThis)) + end + end + # Zygarde - Power Construct + if isConst?(@species,PBSpecies,:ZYGARDE) && isConst?(@ability,PBAbilities,:POWERCONSTRUCT) && + endOfRound + if @hp<=@totalhp/2 && @form<2 # Turn into Complete Forme + newForm = @form+2 + @battle.pbDisplay(_INTL("You sense the presence of many!")) + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(newForm,_INTL("{1} transformed into its Complete Forme!",pbThis)) + end + end + end + + def pbTransform(target) + oldAbil = @ability + @effects[PBEffects::Transform] = true + @effects[PBEffects::TransformSpecies] = target.species + pbChangeTypes(target) + @ability = target.ability + @attack = target.attack + @defense = target.defense + @spatk = target.spatk + @spdef = target.spdef + @speed = target.speed + PBStats.eachBattleStat { |s| @stages[s] = target.stages[s] } + if NEWEST_BATTLE_MECHANICS + @effects[PBEffects::FocusEnergy] = target.effects[PBEffects::FocusEnergy] + @effects[PBEffects::LaserFocus] = target.effects[PBEffects::LaserFocus] + end + @moves.clear + target.moves.each_with_index do |m,i| + @moves[i] = PokeBattle_Move.pbFromPBMove(@battle,PBMove.new(m.id)) + @moves[i].pp = 5 + @moves[i].totalpp = 5 + end + @effects[PBEffects::Disable] = 0 + @effects[PBEffects::DisableMove] = 0 + @effects[PBEffects::WeightChange] = target.effects[PBEffects::WeightChange] + @battle.scene.pbRefreshOne(@index) + @battle.pbDisplay(_INTL("{1} transformed into {2}!",pbThis,target.pbThis(true))) + pbOnAbilityChanged(oldAbil) + end + + def pbHyperMode; end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/004_Battler_Statuses.rb b/Data/Scripts/011_Battle/001_Battler/004_Battler_Statuses.rb new file mode 100644 index 000000000..907b406e9 --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/004_Battler_Statuses.rb @@ -0,0 +1,573 @@ +class PokeBattle_Battler + #============================================================================= + # Generalised checks for whether a status problem can be inflicted + #============================================================================= + # NOTE: Not all "does it have this status?" checks use this method. If the + # check is leading up to curing self of that status condition, then it + # will look at the value of @status directly instead - if it is that + # PBStatuses value then it is curable. This method only checks for + # "counts as having that status", which includes Comatose which can't be + # cured. + def pbHasStatus?(checkStatus) + if BattleHandlers.triggerStatusCheckAbilityNonIgnorable(@ability,self,checkStatus) + return true + end + return @status==checkStatus + end + + def pbHasAnyStatus? + if BattleHandlers.triggerStatusCheckAbilityNonIgnorable(@ability,self,nil) + return true + end + return @status!=PBStatuses::NONE + end + + def pbCanInflictStatus?(newStatus,user,showMessages,move=nil,ignoreStatus=false) + return false if fainted? + selfInflicted = (user && user.index==@index) + # Already have that status problem + if self.status==newStatus && !ignoreStatus + if showMessages + msg = "" + case self.status + when PBStatuses::SLEEP; msg = _INTL("{1} is already asleep!",pbThis) + when PBStatuses::POISON; msg = _INTL("{1} is already poisoned!",pbThis) + when PBStatuses::BURN; msg = _INTL("{1} already has a burn!",pbThis) + when PBStatuses::PARALYSIS; msg = _INTL("{1} is already paralyzed!",pbThis) + when PBStatuses::FROZEN; msg = _INTL("{1} is already frozen solid!",pbThis) + end + @battle.pbDisplay(msg) + end + return false + end + # Trying to replace a status problem with another one + if self.status!=PBStatuses::NONE && !ignoreStatus && !selfInflicted + @battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages + return false + end + # Trying to inflict a status problem on a Pokémon behind a substitute + if @effects[PBEffects::Substitute]>0 && !(move && move.ignoresSubstitute?(user)) && + !selfInflicted + @battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages + return false + end + # Weather immunity + if newStatus==PBStatuses::FROZEN && + (@battle.pbWeather==PBWeather::Sun || @battle.pbWeather==PBWeather::HarshSun) + @battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages + return false + end + # Terrains immunity + if affectedByTerrain? + case @battle.field.terrain + when PBBattleTerrains::Electric + if newStatus==PBStatuses::SLEEP + @battle.pbDisplay(_INTL("{1} surrounds itself with electrified terrain!", + pbThis(true))) if showMessages + return false + end + when PBBattleTerrains::Misty + @battle.pbDisplay(_INTL("{1} surrounds itself with misty terrain!",pbThis(true))) if showMessages + return false + end + end + # Uproar immunity + if newStatus==PBStatuses::SLEEP && + !(hasActiveAbility?(:SOUNDPROOF) && !@battle.moldBreaker) + @battle.eachBattler do |b| + next if b.effects[PBEffects::Uproar]==0 + @battle.pbDisplay(_INTL("But the uproar kept {1} awake!",pbThis(true))) if showMessages + return false + end + end + # Type immunities + hasImmuneType = false + case newStatus + when PBStatuses::SLEEP + # No type is immune to sleep + when PBStatuses::POISON + if !(user && user.hasActiveAbility?(:CORROSION)) + hasImmuneType |= pbHasType?(:POISON) + hasImmuneType |= pbHasType?(:STEEL) + end + when PBStatuses::BURN + hasImmuneType |= pbHasType?(:FIRE) + when PBStatuses::PARALYSIS + hasImmuneType |= pbHasType?(:ELECTRIC) && NEWEST_BATTLE_MECHANICS + when PBStatuses::FROZEN + hasImmuneType |= pbHasType?(:ICE) + end + if hasImmuneType + @battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages + return false + end + # Ability immunity + immuneByAbility = false; immAlly = nil + if BattleHandlers.triggerStatusImmunityAbilityNonIgnorable(@ability,self,newStatus) + immuneByAbility = true + elsif selfInflicted || !@battle.moldBreaker + if abilityActive? && BattleHandlers.triggerStatusImmunityAbility(@ability,self,newStatus) + immuneByAbility = true + else + eachAlly do |b| + next if !b.abilityActive? + next if !BattleHandlers.triggerStatusImmunityAllyAbility(b.ability,self,newStatus) + immuneByAbility = true + immAlly = b + break + end + end + end + if immuneByAbility + if showMessages + @battle.pbShowAbilitySplash(immAlly || self) + msg = "" + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + case newStatus + when PBStatuses::SLEEP; msg = _INTL("{1} stays awake!",pbThis) + when PBStatuses::POISON; msg = _INTL("{1} cannot be poisoned!",pbThis) + when PBStatuses::BURN; msg = _INTL("{1} cannot be burned!",pbThis) + when PBStatuses::PARALYSIS; msg = _INTL("{1} cannot be paralyzed!",pbThis) + when PBStatuses::FROZEN; msg = _INTL("{1} cannot be frozen solid!",pbThis) + end + elsif immAlly + case newStatus + when PBStatuses::SLEEP + msg = _INTL("{1} stays awake because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + when PBStatuses::POISON + msg = _INTL("{1} cannot be poisoned because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + when PBStatuses::BURN + msg = _INTL("{1} cannot be burned because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + when PBStatuses::PARALYSIS + msg = _INTL("{1} cannot be paralyzed because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + when PBStatuses::FROZEN + msg = _INTL("{1} cannot be frozen solid because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + end + else + case newStatus + when PBStatuses::SLEEP; msg = _INTL("{1} stays awake because of its {2}!",pbThis,abilityName) + when PBStatuses::POISON; msg = _INTL("{1}'s {2} prevents poisoning!",pbThis,abilityName) + when PBStatuses::BURN; msg = _INTL("{1}'s {2} prevents burns!",pbThis,abilityName) + when PBStatuses::PARALYSIS; msg = _INTL("{1}'s {2} prevents paralysis!",pbThis,abilityName) + when PBStatuses::FROZEN; msg = _INTL("{1}'s {2} prevents freezing!",pbThis,abilityName) + end + end + @battle.pbDisplay(msg) + @battle.pbHideAbilitySplash(immAlly || self) + end + return false + end + # Safeguard immunity + if pbOwnSide.effects[PBEffects::Safeguard]>0 && !selfInflicted && move && + !(user && user.hasActiveAbility?(:INFILTRATOR)) + @battle.pbDisplay(_INTL("{1}'s team is protected by Safeguard!",pbThis)) if showMessages + return false + end + return true + end + + def pbCanSynchronizeStatus?(status,target) + return false if fainted? + # Trying to replace a status problem with another one + return false if self.status!=PBStatuses::NONE + # Terrain immunity + return false if @battle.field.terrain==PBBattleTerrains::Misty && affectedByTerrain? + # Type immunities + hasImmuneType = false + case self.status + when PBStatuses::POISON + # NOTE: target will have Synchronize, so it can't have Corrosion. + if !(target && target.hasActiveAbility?(:CORROSION)) + hasImmuneType |= pbHasType?(:POISON) + hasImmuneType |= pbHasType?(:STEEL) + end + when PBStatuses::BURN + hasImmuneType |= pbHasType?(:FIRE) + when PBStatuses::PARALYSIS + hasImmuneType |= pbHasType?(:ELECTRIC) && NEWEST_BATTLE_MECHANICS + end + return false if hasImmuneType + # Ability immunity + if BattleHandlers.triggerStatusImmunityAbilityNonIgnorable(@ability,self,status) + return false + end + if abilityActive? && BattleHandlers.triggerStatusImmunityAbility(@ability,self,status) + return false + end + eachAlly do |b| + next if !b.abilityActive? + next if !BattleHandlers.triggerStatusImmunityAllyAbility(b.ability,self,status) + return false + end + # Safeguard immunity + if pbOwnSide.effects[PBEffects::Safeguard]>0 && + !(user && user.hasActiveAbility?(:INFILTRATOR)) + return false + end + return true + end + + #============================================================================= + # Generalised infliction of status problem + #============================================================================= + def pbInflictStatus(newStatus,newStatusCount=0,msg=nil,user=nil) + # Inflict the new status + self.status = newStatus + self.statusCount = newStatusCount + @effects[PBEffects::Toxic] = 0 + # Record status change in debug log, generate default message, show animation + case newStatus + when PBStatuses::SLEEP + @battle.pbCommonAnimation("Sleep",self) + msg = _INTL("{1} fell asleep!",pbThis) if !msg || msg=="" + when PBStatuses::POISON + if newStatusCount>0 + @battle.pbCommonAnimation("Toxic",self) + msg = _INTL("{1} was badly poisoned!",pbThis) if !msg || msg=="" + else + @battle.pbCommonAnimation("Poison",self) + msg = _INTL("{1} was poisoned!",pbThis) if !msg || msg=="" + end + when PBStatuses::BURN + @battle.pbCommonAnimation("Burn",self) + msg = _INTL("{1} was burned!",pbThis) if !msg || msg=="" + when PBStatuses::PARALYSIS + @battle.pbCommonAnimation("Paralysis",self) + msg = _INTL("{1} is paralyzed! It may be unable to move!",pbThis) if !msg || msg=="" + when PBStatuses::FROZEN + @battle.pbCommonAnimation("Frozen",self) + msg = _INTL("{1} was frozen solid!",pbThis) if !msg || msg=="" + end + # Show message + @battle.pbDisplay(msg) if msg && msg!="" + PBDebug.log("[Status change] #{pbThis}'s sleep count is #{newStatusCount}") if newStatus==PBStatuses::SLEEP + pbCheckFormOnStatusChange + # Synchronize + if abilityActive? + BattleHandlers.triggerAbilityOnStatusInflicted(@ability,self,user,newStatus) + end + # Status cures + pbItemStatusCureCheck + pbAbilityStatusCureCheck + # Petal Dance/Outrage/Thrash get cancelled immediately by falling asleep + # NOTE: I don't know why this applies only to Outrage and only to falling + # asleep (i.e. it doesn't cancel Rollout/Uproar/other multi-turn + # moves, and it doesn't cancel any moves if self becomes frozen/ + # disabled/anything else). This behaviour was tested in Gen 5. + if @status==PBStatuses::SLEEP && @effects[PBEffects::Outrage]>0 + @effects[PBEffects::Outrage] = 0 + @currentMove = 0 + end + end + + #============================================================================= + # Sleep + #============================================================================= + def asleep? + return pbHasStatus?(PBStatuses::SLEEP) + end + + def pbCanSleep?(user,showMessages,move=nil,ignoreStatus=false) + return pbCanInflictStatus?(PBStatuses::SLEEP,user,showMessages,move,ignoreStatus) + end + + def pbCanSleepYawn? + return false if self.status!=PBStatuses::NONE + if affectedByTerrain? + return false if @battle.field.terrain==PBBattleTerrains::Electric + return false if @battle.field.terrain==PBBattleTerrains::Misty + end + if !hasActiveAbility?(:SOUNDPROOF) + @battle.eachBattler do |b| + return false if b.effects[PBEffects::Uproar]>0 + end + end + if BattleHandlers.triggerStatusImmunityAbilityNonIgnorable(@ability,self,PBStatuses::SLEEP) + return false + end + # NOTE: Bulbapedia claims that Flower Veil shouldn't prevent sleep due to + # drowsiness, but I disagree because that makes no sense. Also, the + # comparable Sweet Veil does prevent sleep due to drowsiness. + if abilityActive? && BattleHandlers.triggerStatusImmunityAbility(@ability,self,PBStatuses::SLEEP) + return false + end + eachAlly do |b| + next if !b.abilityActive? + next if !BattleHandlers.triggerStatusImmunityAllyAbility(b.ability,self,PBStatuses::SLEEP) + return false + end + # NOTE: Bulbapedia claims that Safeguard shouldn't prevent sleep due to + # drowsiness. I disagree with this too. Compare with the other sided + # effects Misty/Electric Terrain, which do prevent it. + return false if pbOwnSide.effects[PBEffects::Safeguard]>0 + return true + end + + def pbSleep(msg=nil) + pbInflictStatus(PBStatuses::SLEEP,pbSleepDuration,msg) + end + + def pbSleepSelf(msg=nil,duration=-1) + pbInflictStatus(PBStatuses::SLEEP,pbSleepDuration(duration),msg) + end + + def pbSleepDuration(duration=-1) + duration = 2+@battle.pbRandom(3) if duration<=0 + duration = (duration/2).floor if hasActiveAbility?(:EARLYBIRD) + return duration + end + + #============================================================================= + # Poison + #============================================================================= + def poisoned? + return pbHasStatus?(PBStatuses::POISON) + end + + def pbCanPoison?(user,showMessages,move=nil) + return pbCanInflictStatus?(PBStatuses::POISON,user,showMessages,move) + end + + def pbCanPoisonSynchronize?(target) + return pbCanSynchronizeStatus?(PBStatuses::POISON,target) + end + + def pbPoison(user=nil,msg=nil,toxic=false) + pbInflictStatus(PBStatuses::POISON,(toxic) ? 1 : 0,msg,user) + end + + #============================================================================= + # Burn + #============================================================================= + def burned? + return pbHasStatus?(PBStatuses::BURN) + end + + def pbCanBurn?(user,showMessages,move=nil) + return pbCanInflictStatus?(PBStatuses::BURN,user,showMessages,move) + end + + def pbCanBurnSynchronize?(target) + return pbCanSynchronizeStatus?(PBStatuses::BURN,target) + end + + def pbBurn(user=nil,msg=nil) + pbInflictStatus(PBStatuses::BURN,0,msg,user) + end + + #============================================================================= + # Paralyze + #============================================================================= + def paralyzed? + return pbHasStatus?(PBStatuses::PARALYSIS) + end + + def pbCanParalyze?(user,showMessages,move=nil) + return pbCanInflictStatus?(PBStatuses::PARALYSIS,user,showMessages,move) + end + + def pbCanParalyzeSynchronize?(target) + return pbCanSynchronizeStatus?(PBStatuses::PARALYSIS,target) + end + + def pbParalyze(user=nil,msg=nil) + pbInflictStatus(PBStatuses::PARALYSIS,0,msg,user) + end + + #============================================================================= + # Freeze + #============================================================================= + def frozen? + return pbHasStatus?(PBStatuses::FROZEN) + end + + def pbCanFreeze?(user,showMessages,move=nil) + return pbCanInflictStatus?(PBStatuses::FROZEN,user,showMessages,move) + end + + def pbFreeze(msg=nil) + pbInflictStatus(PBStatuses::FROZEN,0,msg) + end + + #============================================================================= + # Generalised status displays + #============================================================================= + def pbContinueStatus + anim = ""; msg = "" + case self.status + when PBStatuses::SLEEP + anim = "Sleep"; msg = _INTL("{1} is fast asleep.",pbThis) + when PBStatuses::POISON + anim = (@statusCount>0) ? "Toxic" : "Poison" + msg = _INTL("{1} was hurt by poison!",pbThis) + when PBStatuses::BURN + anim = "Burn"; msg = _INTL("{1} was hurt by its burn!",pbThis) + when PBStatuses::PARALYSIS + anim = "Paralysis"; msg = _INTL("{1} is paralyzed! It can't move!",pbThis) + when PBStatuses::FROZEN + anim = "Frozen"; msg = _INTL("{1} is frozen solid!",pbThis) + end + @battle.pbCommonAnimation(anim,self) if anim!="" + yield if block_given? + @battle.pbDisplay(msg) if msg!="" + PBDebug.log("[Status continues] #{pbThis}'s sleep count is #{@statusCount}") if self.status==PBStatuses::SLEEP + end + + def pbCureStatus(showMessages=true) + oldStatus = status + self.status = PBStatuses::NONE + if showMessages + case oldStatus + when PBStatuses::SLEEP; @battle.pbDisplay(_INTL("{1} woke up!",pbThis)) + when PBStatuses::POISON; @battle.pbDisplay(_INTL("{1} was cured of its poisoning.",pbThis)) + when PBStatuses::BURN; @battle.pbDisplay(_INTL("{1}'s burn was healed.",pbThis)) + when PBStatuses::PARALYSIS; @battle.pbDisplay(_INTL("{1} was cured of paralysis.",pbThis)) + when PBStatuses::FROZEN; @battle.pbDisplay(_INTL("{1} thawed out!",pbThis)) + end + end + PBDebug.log("[Status change] #{pbThis}'s status was cured") if !showMessages + end + + #============================================================================= + # Confusion + #============================================================================= + def pbCanConfuse?(user=nil,showMessages=true,move=nil,selfInflicted=false) + return false if fainted? + if @effects[PBEffects::Confusion]>0 + @battle.pbDisplay(_INTL("{1} is already confused.",pbThis)) if showMessages + return false + end + if @effects[PBEffects::Substitute]>0 && !(move && move.ignoresSubstitute?(user)) && + !selfInflicted + @battle.pbDisplay(_INTL("But it failed!")) if showMessages + return false + end + # Terrains immunity + if affectedByTerrain? && @battle.field.terrain==PBBattleTerrains::Misty + @battle.pbDisplay(_INTL("{1} surrounds itself with misty terrain!",pbThis(true))) if showMessages + return false + end + if selfInflicted || !@battle.moldBreaker + if hasActiveAbility?(:OWNTEMPO) + if showMessages + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} doesn't become confused!",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} prevents confusion!",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return false + end + end + if pbOwnSide.effects[PBEffects::Safeguard]>0 && !selfInflicted && + !(user && user.hasActiveAbility?(:INFILTRATOR)) + @battle.pbDisplay(_INTL("{1}'s team is protected by Safeguard!",pbThis)) if showMessages + return false + end + return true + end + + def pbCanConfuseSelf?(showMessages) + return pbCanConfuse?(nil,showMessages,nil,true) + end + + def pbConfuse(msg=nil) + @effects[PBEffects::Confusion] = pbConfusionDuration + @battle.pbCommonAnimation("Confusion",self) + msg = _INTL("{1} became confused!",pbThis) if !msg || msg=="" + @battle.pbDisplay(msg) + PBDebug.log("[Lingering effect] #{pbThis}'s confusion count is #{@effects[PBEffects::Confusion]}") + # Confusion cures + pbItemStatusCureCheck + pbAbilityStatusCureCheck + end + + def pbConfusionDuration(duration=-1) + duration = 2+@battle.pbRandom(4) if duration<=0 + return duration + end + + def pbCureConfusion + @effects[PBEffects::Confusion] = 0 + end + + #============================================================================= + # Attraction + #============================================================================= + def pbCanAttract?(user,showMessages=true) + return false if fainted? + return false if !user || user.fainted? + if @effects[PBEffects::Attract]>=0 + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) if showMessages + return false + end + agender = user.gender + ogender = gender + if agender==2 || ogender==2 || agender==ogender + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) if showMessages + return false + end + if !@battle.moldBreaker + if hasActiveAbility?([:AROMAVEIL,:OBLIVIOUS]) + if showMessages + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} prevents romance!",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return false + else + eachAlly do |b| + next if !b.hasActiveAbility?(:AROMAVEIL) + if showMessages + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} prevents romance!",b.pbThis,b.abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return true + end + end + end + return true + end + + def pbAttract(user,msg=nil) + @effects[PBEffects::Attract] = user.index + @battle.pbCommonAnimation("Attract",self) + msg = _INTL("{1} fell in love!",pbThis) if !msg || msg=="" + @battle.pbDisplay(msg) + # Destiny Knot + if hasActiveItem?(:DESTINYKNOT) && user.pbCanAttract?(self,false) + user.pbAttract(self,_INTL("{1} fell in love from the {2}!",user.pbThis(true),itemName)) + end + # Attraction cures + pbItemStatusCureCheck + pbAbilityStatusCureCheck + end + + def pbCureAttract + @effects[PBEffects::Attract] = -1 + end + + #============================================================================= + # Flinching + #============================================================================= + def pbFlinch(user=nil) + return if hasActiveAbility?(:INNERFOCUS) && !@battle.moldBreaker + @effects[PBEffects::Flinch] = true + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/005_Battler_StatStages.rb b/Data/Scripts/011_Battle/001_Battler/005_Battler_StatStages.rb new file mode 100644 index 000000000..761c7497a --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/005_Battler_StatStages.rb @@ -0,0 +1,310 @@ +class PokeBattle_Battler + #============================================================================= + # Increase stat stages + #============================================================================= + def statStageAtMax?(stat) + return @stages[stat]>=6 + end + + def pbCanRaiseStatStage?(stat,user=nil,move=nil,showFailMsg=false,ignoreContrary=false) + return false if fainted? + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbCanLowerStatStage?(stat,user,move,showFailMsg,true) + end + # Check the stat stage + if statStageAtMax?(stat) + @battle.pbDisplay(_INTL("{1}'s {2} won't go any higher!", + pbThis,PBStats.getName(stat))) if showFailMsg + return false + end + return true + end + + def pbRaiseStatStageBasic(stat,increment,ignoreContrary=false) + if !@battle.moldBreaker + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary + return pbLowerStatStageBasic(stat,increment,true) + end + # Simple + increment *= 2 if hasActiveAbility?(:SIMPLE) + end + # Change the stat stage + increment = [increment,6-@stages[stat]].min + if increment>0 + s = PBStats.getName(stat); new = @stages[stat]+increment + PBDebug.log("[Stat change] #{pbThis}'s #{s}: #{@stages[stat]} -> #{new} (+#{increment})") + @stages[stat] += increment + end + return increment + end + + def pbRaiseStatStage(stat,increment,user,showAnim=true,ignoreContrary=false) + return false if !PBStats.validBattleStat?(stat) + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbLowerStatStage(stat,increment,user,showAnim,true) + end + # Perform the stat stage change + increment = pbRaiseStatStageBasic(stat,increment,ignoreContrary) + return false if increment<=0 + # Stat up animation and message + @battle.pbCommonAnimation("StatUp",self) if showAnim + arrStatTexts = [ + _INTL("{1}'s {2} rose!",pbThis,PBStats.getName(stat)), + _INTL("{1}'s {2} rose sharply!",pbThis,PBStats.getName(stat)), + _INTL("{1}'s {2} rose drastically!",pbThis,PBStats.getName(stat))] + @battle.pbDisplay(arrStatTexts[[increment-1,2].min]) + # Trigger abilities upon stat gain + if abilityActive? + BattleHandlers.triggerAbilityOnStatGain(@ability,self,stat,user) + end + return true + end + + def pbRaiseStatStageByCause(stat,increment,user,cause,showAnim=true,ignoreContrary=false) + return false if !PBStats.validBattleStat?(stat) + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbLowerStatStageByCause(stat,increment,user,cause,showAnim,true) + end + # Perform the stat stage change + increment = pbRaiseStatStageBasic(stat,increment,ignoreContrary) + return false if increment<=0 + # Stat up animation and message + @battle.pbCommonAnimation("StatUp",self) if showAnim + if user.index==@index + arrStatTexts = [ + _INTL("{1}'s {2} raised its {3}!",pbThis,cause,PBStats.getName(stat)), + _INTL("{1}'s {2} sharply raised its {3}!",pbThis,cause,PBStats.getName(stat)), + _INTL("{1}'s {2} drastically raised its {3}!",pbThis,cause,PBStats.getName(stat))] + else + arrStatTexts = [ + _INTL("{1}'s {2} raised {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat)), + _INTL("{1}'s {2} sharply raised {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat)), + _INTL("{1}'s {2} drastically raised {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat))] + end + @battle.pbDisplay(arrStatTexts[[increment-1,2].min]) + # Trigger abilities upon stat gain + if abilityActive? + BattleHandlers.triggerAbilityOnStatGain(@ability,self,stat,user) + end + return true + end + + def pbRaiseStatStageByAbility(stat,increment,user,splashAnim=true) + return false if fainted? + ret = false + @battle.pbShowAbilitySplash(user) if splashAnim + if pbCanRaiseStatStage?(stat,user,nil,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + ret = pbRaiseStatStage(stat,increment,user) + else + ret = pbRaiseStatStageByCause(stat,increment,user,user.abilityName) + end + end + @battle.pbHideAbilitySplash(user) if splashAnim + return ret + end + + #============================================================================= + # Decrease stat stages + #============================================================================= + def statStageAtMin?(stat) + return @stages[stat]<=-6 + end + + def pbCanLowerStatStage?(stat,user=nil,move=nil,showFailMsg=false,ignoreContrary=false) + return false if fainted? + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbCanRaiseStatStage?(stat,user,move,showFailMsg,true) + end + if !user || user.index!=@index # Not self-inflicted + if @effects[PBEffects::Substitute]>0 && !(move && move.ignoresSubstitute?(user)) + @battle.pbDisplay(_INTL("{1} is protected by its substitute!",pbThis)) if showFailMsg + return false + end + if pbOwnSide.effects[PBEffects::Mist]>0 && + !(user && user.hasActiveAbility?(:INFILTRATOR)) + @battle.pbDisplay(_INTL("{1} is protected by Mist!",pbThis)) if showFailMsg + return false + end + if abilityActive? + return false if BattleHandlers.triggerStatLossImmunityAbility( + @ability,self,stat,@battle,showFailMsg) if !@battle.moldBreaker + return false if BattleHandlers.triggerStatLossImmunityAbilityNonIgnorable( + @ability,self,stat,@battle,showFailMsg) + end + if !@battle.moldBreaker + eachAlly do |b| + next if !b.abilityActive? + return false if BattleHandlers.triggerStatLossImmunityAllyAbility( + b.ability,b,self,stat,@battle,showFailMsg) + end + end + end + # Check the stat stage + if statStageAtMin?(stat) + @battle.pbDisplay(_INTL("{1}'s {2} won't go any lower!", + pbThis,PBStats.getName(stat))) if showFailMsg + return false + end + return true + end + + def pbLowerStatStageBasic(stat,increment,ignoreContrary=false) + if !@battle.moldBreaker + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary + return pbRaiseStatStageBasic(stat,increment,true) + end + # Simple + increment *= 2 if hasActiveAbility?(:SIMPLE) + end + # Change the stat stage + increment = [increment,6+@stages[stat]].min + if increment>0 + s = PBStats.getName(stat); new = @stages[stat]-increment + PBDebug.log("[Stat change] #{pbThis}'s #{s}: #{@stages[stat]} -> #{new} (-#{increment})") + @stages[stat] -= increment + end + return increment + end + + def pbLowerStatStage(stat,increment,user,showAnim=true,ignoreContrary=false) + return false if !PBStats.validBattleStat?(stat) + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbRaiseStatStage(stat,increment,user,showAnim,true) + end + # Perform the stat stage change + increment = pbLowerStatStageBasic(stat,increment,ignoreContrary) + return false if increment<=0 + # Stat down animation and message + @battle.pbCommonAnimation("StatDown",self) if showAnim + arrStatTexts = [ + _INTL("{1}'s {2} fell!",pbThis,PBStats.getName(stat)), + _INTL("{1}'s {2} harshly fell!",pbThis,PBStats.getName(stat)), + _INTL("{1}'s {2} severely fell!",pbThis,PBStats.getName(stat))] + @battle.pbDisplay(arrStatTexts[[increment-1,2].min]) + # Trigger abilities upon stat loss + if abilityActive? + BattleHandlers.triggerAbilityOnStatLoss(@ability,self,stat,user) + end + return true + end + + def pbLowerStatStageByCause(stat,increment,user,cause,showAnim=true,ignoreContrary=false) + return false if !PBStats.validBattleStat?(stat) + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbRaiseStatStageByCause(stat,increment,user,cause,showAnim,true) + end + # Perform the stat stage change + increment = pbLowerStatStageBasic(stat,increment,ignoreContrary) + return false if increment<=0 + # Stat down animation and message + @battle.pbCommonAnimation("StatDown",self) if showAnim + if user.index==@index + arrStatTexts = [ + _INTL("{1}'s {2} lowered its {3}!",pbThis,cause,PBStats.getName(stat)), + _INTL("{1}'s {2} harshly lowered its {3}!",pbThis,cause,PBStats.getName(stat)), + _INTL("{1}'s {2} severely lowered its {3}!",pbThis,cause,PBStats.getName(stat))] + else + arrStatTexts = [ + _INTL("{1}'s {2} lowered {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat)), + _INTL("{1}'s {2} harshly lowered {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat)), + _INTL("{1}'s {2} severely lowered {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat))] + end + @battle.pbDisplay(arrStatTexts[[increment-1,2].min]) + # Trigger abilities upon stat loss + if abilityActive? + BattleHandlers.triggerAbilityOnStatLoss(@ability,self,stat,user) + end + return true + end + + def pbLowerStatStageByAbility(stat,increment,user,splashAnim=true,checkContact=false) + ret = false + @battle.pbShowAbilitySplash(user) if splashAnim + if pbCanLowerStatStage?(stat,user,nil,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + (!checkContact || affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH)) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + ret = pbLowerStatStage(stat,increment,user) + else + ret = pbLowerStatStageByCause(stat,increment,user,user.abilityName) + end + end + @battle.pbHideAbilitySplash(user) if splashAnim + return ret + end + + def pbLowerAttackStatStageIntimidate(user) + return false if fainted? + # NOTE: Substitute intentially blocks Intimidate even if self has Contrary. + if @effects[PBEffects::Substitute]>0 + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is protected by its substitute!",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s substitute protected it from {2}'s {3}!", + pbThis,user.pbThis(true),user.abilityName)) + end + return false + end + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + return pbLowerStatStageByAbility(PBStats::ATTACK,1,user,false) + end + # NOTE: These checks exist to ensure appropriate messages are shown if + # Intimidate is blocked somehow (i.e. the messages should mention the + # Intimidate ability by name). + if !hasActiveAbility?(:CONTRARY) + if pbOwnSide.effects[PBEffects::Mist]>0 + @battle.pbDisplay(_INTL("{1} is protected from {2}'s {3} by Mist!", + pbThis,user.pbThis(true),user.abilityName)) + return false + end + if abilityActive? + if BattleHandlers.triggerStatLossImmunityAbility(@ability,self,PBStats::ATTACK,@battle,false) || + BattleHandlers.triggerStatLossImmunityAbilityNonIgnorable(@ability,self,PBStats::ATTACK,@battle,false) + @battle.pbDisplay(_INTL("{1}'s {2} prevented {3}'s {4} from working!", + pbThis,abilityName,user.pbThis(true),user.abilityName)) + return false + end + end + eachAlly do |b| + next if !b.abilityActive? + if BattleHandlers.triggerStatLossImmunityAllyAbility(b.ability,b,self,PBStats::ATTACK,@battle,false) + @battle.pbDisplay(_INTL("{1} is protected from {2}'s {3} by {4}'s {5}!", + pbThis,user.pbThis(true),user.abilityName,b.pbThis(true),b.abilityName)) + return false + end + end + end + return false if !pbCanLowerStatStage?(PBStats::ATTACK,user) + return pbLowerStatStageByCause(PBStats::ATTACK,1,user,user.abilityName) + end + + #============================================================================= + # Reset stat stages + #============================================================================= + def hasAlteredStatStages? + PBStats.eachBattleStat { |s| return true if @stages[s]!=0 } + return false + end + + def hasRaisedStatStages? + PBStats.eachBattleStat { |s| return true if @stages[s]>0 } + return false + end + + def hasLoweredStatStages? + PBStats.eachBattleStat { |s| return true if @stages[s]<0 } + return false + end + + def pbResetStatStages + PBStats.eachBattleStat { |s| @stages[s] = 0 } + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/006_Battler_AbilityAndItem.rb b/Data/Scripts/011_Battle/001_Battler/006_Battler_AbilityAndItem.rb new file mode 100644 index 000000000..ed7a0403d --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/006_Battler_AbilityAndItem.rb @@ -0,0 +1,322 @@ +class PokeBattle_Battler + #============================================================================= + # Called when a Pokémon (self) is sent into battle or its ability changes. + #============================================================================= + def pbEffectsOnSwitchIn(switchIn=false) + # Healing Wish/Lunar Dance/entry hazards + @battle.pbOnActiveOne(self) if switchIn + # Primal Revert upon entering battle + @battle.pbPrimalReversion(@index) if !fainted? + # Ending primordial weather, checking Trace + pbContinualAbilityChecks(true) + # Abilities that trigger upon switching in + if (!fainted? && nonNegatableAbility?) || abilityActive? + BattleHandlers.triggerAbilityOnSwitchIn(@ability,self,@battle) + end + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + # Items that trigger upon switching in (Air Balloon message) + if switchIn && itemActive? + BattleHandlers.triggerItemOnSwitchIn(@item,self,@battle) + end + # Berry check, status-curing ability check + pbHeldItemTriggerCheck if switchIn + pbAbilityStatusCureCheck + end + + #============================================================================= + # Ability effects + #============================================================================= + def pbAbilitiesOnSwitchOut + if abilityActive? + BattleHandlers.triggerAbilityOnSwitchOut(@ability,self,false) + end + # Reset form + @battle.peer.pbOnLeavingBattle(@battle,@pokemon,@battle.usedInBattle[idxOwnSide][@index/2]) + # Treat self as fainted + @hp = 0 + @fainted = true + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + end + + def pbAbilitiesOnFainting + # Self fainted; check all other battlers to see if their abilities trigger + @battle.pbPriority(true).each do |b| + next if !b || !b.abilityActive? + BattleHandlers.triggerAbilityChangeOnBattlerFainting(b.ability,b,self,@battle) + end + @battle.pbPriority(true).each do |b| + next if !b || !b.abilityActive? + BattleHandlers.triggerAbilityOnBattlerFainting(b.ability,b,self,@battle) + end + end + + # Used for Emergency Exit/Wimp Out. + def pbAbilitiesOnDamageTaken(oldHP,newHP=-1) + return false if !abilityActive? + newHP = @hp if newHP<0 + return false if oldHP<@totalhp/2 || newHP>=@totalhp/2 # Didn't drop below half + ret = BattleHandlers.triggerAbilityOnHPDroppedBelowHalf(@ability,self,@battle) + return ret # Whether self has switched out + end + + # Called when a Pokémon (self) enters battle, at the end of each move used, + # and at the end of each round. + def pbContinualAbilityChecks(onSwitchIn=false) + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + # Trace + if hasActiveAbility?(:TRACE) + # NOTE: In Gen 5 only, Trace only triggers upon the Trace bearer switching + # in and not at any later times, even if a traceable ability turns + # up later. Essentials ignores this, and allows Trace to trigger + # whenever it can even in the old battle mechanics. + abilityBlacklist = [ + # Replaces self with another ability + :POWEROFALCHEMY, + :RECEIVER, + :TRACE, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, + :FLOWERGIFT, + :FORECAST, + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Appearance-changing abilities + :ILLUSION, + :IMPOSTER, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + choices = [] + @battle.eachOtherSideBattler(@index) do |b| + abilityBlacklist.each do |abil| + next if !isConst?(b.ability,PBAbilities,abil) + choices.push(b) + break + end + end + if choices.length>0 + choice = choices[@battle.pbRandom(choices.length)] + @battle.pbShowAbilitySplash(self) + @ability = choice.ability + @battle.pbDisplay(_INTL("{1} traced {2}'s {3}!",pbThis,choice.pbThis(true),choice.abilityName)) + @battle.pbHideAbilitySplash(self) + if !onSwitchIn && (nonNegatableAbility? || abilityActive?) + BattleHandlers.triggerAbilityOnSwitchIn(@ability,self,@battle) + end + end + end + end + + #============================================================================= + # Ability curing + #============================================================================= + # Cures status conditions, confusion and infatuation. + def pbAbilityStatusCureCheck + if abilityActive? + BattleHandlers.triggerStatusCureAbility(@ability,self) + end + end + + #============================================================================= + # Ability change + #============================================================================= + def pbOnAbilityChanged(oldAbil) + if @effects[PBEffects::Illusion] && isConst?(oldAbil,PBAbilities,:ILLUSION) + @effects[PBEffects::Illusion] = nil + if !@effects[PBEffects::Transform] + @battle.scene.pbChangePokemon(self,@pokemon) + @battle.pbDisplay(_INTL("{1}'s {2} wore off!",pbThis,PBAbilities.getName(oldAbil))) + @battle.pbSetSeen(self) + end + end + @effects[PBEffects::GastroAcid] = false if nonNegatableAbility? + @effects[PBEffects::SlowStart] = 0 if !isConst?(@ability,PBAbilities,:SLOWSTART) + # Revert form if Flower Gift/Forecast was lost + pbCheckFormOnWeatherChange + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + end + + #============================================================================= + # Held item consuming/removing + #============================================================================= + def pbCanConsumeBerry?(item,alwaysCheckGluttony=true) + return false if @battle.pbCheckOpposingAbility(:UNNERVE,@index) + return true if @hp<=@totalhp/4 + if alwaysCheckGluttony || NEWEST_BATTLE_MECHANICS + return true if @hp<=@totalhp/2 && hasActiveAbility?(:GLUTTONY) + end + return false + end + + # permanent is whether the item is lost even after battle. Is false for Knock + # Off. + def pbRemoveItem(permanent=true) + @effects[PBEffects::ChoiceBand] = -1 + @effects[PBEffects::Unburden] = true if @item>0 + setInitialItem(0) if self.initialItem==@item && permanent + self.item = 0 + end + + def pbConsumeItem(recoverable=true,symbiosis=true,belch=true) + PBDebug.log("[Item consumed] #{pbThis} consumed its held #{PBItems.getName(@item)}") + if recoverable + setRecycleItem(@item) + @effects[PBEffects::PickupItem] = @item + @effects[PBEffects::PickupUse] = @battle.nextPickupUse + end + setBelched if belch && pbIsBerry?(@item) + pbRemoveItem + pbSymbiosis if symbiosis + end + + def pbSymbiosis + return if fainted? + return if @item!=0 + @battle.pbPriority(true).each do |b| + next if b.opposes? + next if !b.hasActiveAbility?(:SYMBIOSIS) + next if b.item==0 || b.unlosableItem?(b.item) + next if unlosableItem?(b.item) + @battle.pbShowAbilitySplash(b) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} shared its {2} with {3}!", + b.pbThis,b.itemName,pbThis(true))) + else + @battle.pbDisplay(_INTL("{1}'s {2} let it share its {3} with {4}!", + b.pbThis,b.abilityName,b.itemName,pbThis(true))) + end + self.item = b.item + b.item = 0 + b.effects[PBEffects::Unburden] = true + @battle.pbHideAbilitySplash(b) + pbHeldItemTriggerCheck + break + end + end + + def pbHeldItemTriggered(thisItem,forcedItem=0,fling=false) + # Cheek Pouch + if hasActiveAbility?(:CHEEKPOUCH) && pbIsBerry?(thisItem) && canHeal? + @battle.pbShowAbilitySplash(self) + pbRecoverHP(@totalhp/3) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1}'s HP was restored.",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + pbConsumeItem if forcedItem<=0 + pbSymbiosis if forcedItem>0 && !fling # Bug Bite/Pluck users trigger Symbiosis + end + + #============================================================================= + # Held item trigger checks + #============================================================================= + # NOTE: A Pokémon using Bug Bite/Pluck, and a Pokémon having an item thrown at + # it via Fling, will gain the effect of the item even if the Pokémon is + # affected by item-negating effects. + # If forcedItem is -1, the Pokémon's held item is forced to be consumed. If it + # is greater than 0, a different item (of that ID) is forced to be consumed + # (not the Pokémon's held one). + def pbHeldItemTriggerCheck(forcedItem=0,fling=false) + return if fainted? + return if forcedItem==0 && !itemActive? + pbItemHPHealCheck(forcedItem,fling) + pbItemStatusCureCheck(forcedItem,fling) + pbItemEndOfMoveCheck(forcedItem,fling) + # For Enigma Berry, Kee Berry and Maranga Berry, which have their effects + # when forcibly consumed by Pluck/Fling. + if forcedItem!=0 + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerTargetItemOnHitPositiveBerry(thisItem,self,@battle,true) + pbHeldItemTriggered(thisItem,forcedItem,fling) + end + end + end + + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemHPHealCheck(forcedItem=0,fling=false) + return if !canHeal? + return if forcedItem==0 && !itemActive? + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerHPHealItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + elsif forcedItem==0 + pbItemTerrainStatBoostCheck + end + end + + # Cures status conditions, confusion, infatuation and the other effects cured + # by Mental Herb. + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemStatusCureCheck(forcedItem=0,fling=false) + return if fainted? + return if forcedItem==0 && !itemActive? + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerStatusCureItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + end + end + + # Called at the end of using a move. + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemEndOfMoveCheck(forcedItem=0,fling=false) + return if fainted? + return if forcedItem==0 && !itemActive? + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerEndOfMoveItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + elsif BattleHandlers.triggerEndOfMoveStatRestoreItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + end + end + + # Used for White Herb (restore lowered stats). Only called by Moody and Sticky + # Web, as all other stat reduction happens because of/during move usage and + # this handler is also called at the end of each move's usage. + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemStatRestoreCheck(forcedItem=0,fling=false) + return if fainted? + return if forcedItem==0 && !itemActive? + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerEndOfMoveStatRestoreItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + end + end + + # Called when the battle terrain changes and when a Pokémon loses HP. + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemTerrainStatBoostCheck + return if !itemActive? + if BattleHandlers.triggerTerrainStatBoostItem(@item,self,@battle) + pbHeldItemTriggered(@item) + end + end + + # Used for Adrenaline Orb. Called when Intimidate is triggered (even if + # Intimidate has no effect on the Pokémon). + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemOnIntimidatedCheck + return if !itemActive? + if BattleHandlers.triggerItemOnIntimidated(@item,self,@battle) + pbHeldItemTriggered(@item) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/007_Battler_UseMove.rb b/Data/Scripts/011_Battle/001_Battler/007_Battler_UseMove.rb new file mode 100644 index 000000000..aa32fd603 --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/007_Battler_UseMove.rb @@ -0,0 +1,729 @@ +class PokeBattle_Battler + #============================================================================= + # Turn processing + #============================================================================= + def pbProcessTurn(choice,tryFlee=true) + return false if fainted? + # Wild roaming Pokémon always flee if possible + if tryFlee && @battle.wildBattle? && opposes? && + @battle.rules["alwaysflee"] && @battle.pbCanRun?(@index) + pbBeginTurn(choice) + @battle.pbDisplay(_INTL("{1} fled from battle!",pbThis)) { pbSEPlay("Battle flee") } + @battle.decision = 3 + pbEndTurn(choice) + return true + end + # Shift with the battler next to this one + if choice[0]==:Shift + idxOther = -1 + case @battle.pbSideSize(@index) + when 2 + idxOther = (@index+2)%4 + when 3 + if @index!=2 && @index!=3 # If not in middle spot already + idxOther = ((@index%2)==0) ? 2 : 3 + end + end + if idxOther>=0 + @battle.pbSwapBattlers(@index,idxOther) + case @battle.pbSideSize(@index) + when 2 + @battle.pbDisplay(_INTL("{1} moved across!",pbThis)) + when 3 + @battle.pbDisplay(_INTL("{1} moved to the center!",pbThis)) + end + end + pbBeginTurn(choice) + pbCancelMoves + @lastRoundMoved = @battle.turnCount # Done something this round + return true + end + # If this battler's action for this round wasn't "use a move" + if choice[0]!=:UseMove + # Clean up effects that end at battler's turn + pbBeginTurn(choice) + pbEndTurn(choice) + return false + end + # Turn is skipped if Pursuit was used during switch + if @effects[PBEffects::Pursuit] + @effects[PBEffects::Pursuit] = false + pbCancelMoves + pbEndTurn(choice) + @battle.pbJudge + return false + end + # Use the move + PBDebug.log("[Move usage] #{pbThis} started using #{choice[2].name}") + PBDebug.logonerr{ + pbUseMove(choice,choice[2]==@battle.struggle) + } + @battle.pbJudge + # Update priority order +# @battle.pbCalculatePriority if NEWEST_BATTLE_MECHANICS + return true + end + + #============================================================================= + # + #============================================================================= + def pbBeginTurn(choice) + # Cancel some lingering effects which only apply until the user next moves + @effects[PBEffects::BeakBlast] = false + @effects[PBEffects::DestinyBondPrevious] = @effects[PBEffects::DestinyBond] + @effects[PBEffects::DestinyBond] = false + @effects[PBEffects::Grudge] = false + @effects[PBEffects::MoveNext] = false + @effects[PBEffects::Quash] = 0 + @effects[PBEffects::ShellTrap] = false + # Encore's effect ends if the encored move is no longer available + if @effects[PBEffects::Encore]>0 && pbEncoredMoveIndex<0 + @effects[PBEffects::Encore] = 0 + @effects[PBEffects::EncoreMove] = 0 + end + end + + # Called when the usage of various multi-turn moves is disrupted due to + # failing pbTryUseMove, being ineffective against all targets, or because + # Pursuit was used specially to intercept a switching foe. + # Cancels the use of multi-turn moves and counters thereof. Note that Hyper + # Beam's effect is NOT cancelled. + def pbCancelMoves + # Outragers get confused anyway if they are disrupted during their final + # turn of using the move + if @effects[PBEffects::Outrage]==1 && pbCanConfuseSelf?(false) + pbConfuse(_INTL("{1} became confused due to fatigue!",pbThis)) + end + # Cancel usage of most multi-turn moves + @effects[PBEffects::TwoTurnAttack] = 0 + @effects[PBEffects::Rollout] = 0 + @effects[PBEffects::Outrage] = 0 + @effects[PBEffects::Uproar] = 0 + @effects[PBEffects::Bide] = 0 + @currentMove = 0 + # Reset counters for moves which increase them when used in succession + @effects[PBEffects::FuryCutter] = 0 + end + + def pbEndTurn(choice) + @lastRoundMoved = @battle.turnCount # Done something this round + if @effects[PBEffects::ChoiceBand]<0 && + hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF]) + if @lastMoveUsed>=0 && pbHasMove?(@lastMoveUsed) + @effects[PBEffects::ChoiceBand] = @lastMoveUsed + elsif @lastRegularMoveUsed>=0 && pbHasMove?(@lastRegularMoveUsed) + @effects[PBEffects::ChoiceBand] = @lastRegularMoveUsed + end + end + @effects[PBEffects::Charge] = 0 if @effects[PBEffects::Charge]==1 + @effects[PBEffects::GemConsumed] = 0 + @battle.eachBattler { |b| b.pbContinualAbilityChecks } # Trace, end primordial weathers + end + + def pbConfusionDamage(msg) + @damageState.reset + @damageState.initialHP = @hp + confusionMove = PokeBattle_Confusion.new(@battle,nil) + confusionMove.calcType = confusionMove.pbCalcType(self) # -1 + @damageState.typeMod = confusionMove.pbCalcTypeMod(confusionMove.calcType,self,self) # 8 + confusionMove.pbCheckDamageAbsorption(self,self) + confusionMove.pbCalcDamage(self,self) + confusionMove.pbReduceDamage(self,self) + self.hp -= @damageState.hpLost + confusionMove.pbAnimateHitAndHPLost(self,[self]) + @battle.pbDisplay(msg) # "It hurt itself in its confusion!" + confusionMove.pbRecordDamageLost(self,self) + confusionMove.pbEndureKOMessage(self) + pbFaint if fainted? + pbItemHPHealCheck + end + + #============================================================================= + # Simple "use move" method, used when a move calls another move and for Future + # Sight's attack + #============================================================================= + def pbUseMoveSimple(moveID,target=-1,idxMove=-1,specialUsage=true) + choice = [] + choice[0] = :UseMove # "Use move" + choice[1] = idxMove # Index of move to be used in user's moveset + if idxMove>=0 + choice[2] = @moves[idxMove] + else + choice[2] = PokeBattle_Move.pbFromPBMove(@battle,PBMove.new(moveID)) # PokeBattle_Move object + choice[2].pp = -1 + end + choice[3] = target # Target (-1 means no target yet) + PBDebug.log("[Move usage] #{pbThis} started using the called/simple move #{choice[2].name}") + pbUseMove(choice,specialUsage) + end + + #============================================================================= + # Master "use move" method + #============================================================================= + def pbUseMove(choice,specialUsage=false) + # NOTE: This is intentionally determined before a multi-turn attack can + # set specialUsage to true. + skipAccuracyCheck = (specialUsage && choice[2]!=@battle.struggle) + # Start using the move + pbBeginTurn(choice) + # Force the use of certain moves if they're already being used + if usingMultiTurnAttack? + choice[2] = PokeBattle_Move.pbFromPBMove(@battle,PBMove.new(@currentMove)) + specialUsage = true + elsif @effects[PBEffects::Encore]>0 && choice[1]>=0 && + @battle.pbCanShowCommands?(@index) + idxEncoredMove = pbEncoredMoveIndex + if idxEncoredMove>=0 && @battle.pbCanChooseMove?(@index,idxEncoredMove,false) + if choice[1]!=idxEncoredMove # Change move if battler was Encored mid-round + choice[1] = idxEncoredMove + choice[2] = @moves[idxEncoredMove] + choice[3] = -1 # No target chosen + end + end + end + # Labels the move being used as "move" + move = choice[2] + return if !move || move.id==0 # if move was not chosen somehow + # Try to use the move (inc. disobedience) + @lastMoveFailed = false + if !pbTryUseMove(choice,move,specialUsage,skipAccuracyCheck) + @lastMoveUsed = -1 + @lastMoveUsedType = -1 + if !specialUsage + @lastRegularMoveUsed = -1 + @lastRegularMoveTarget = -1 + end + @battle.pbGainExp # In case self is KO'd due to confusion + pbCancelMoves + pbEndTurn(choice) + return + end + move = choice[2] # In case disobedience changed the move to be used + return if !move || move.id==0 # if move was not chosen somehow + # Subtract PP + if !specialUsage + if !pbReducePP(move) + @battle.pbDisplay(_INTL("{1} used {2}!",pbThis,move.name)) + @battle.pbDisplay(_INTL("But there was no PP left for the move!")) + @lastMoveUsed = -1 + @lastMoveUsedType = -1 + @lastRegularMoveUsed = -1 + @lastRegularMoveTarget = -1 + @lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + end + # Stance Change + if isConst?(@species,PBSpecies,:AEGISLASH) && isConst?(@ability,PBAbilities,:STANCECHANGE) + if move.damagingMove? + pbChangeForm(1,_INTL("{1} changed to Blade Forme!",pbThis)) + elsif isConst?(move.id,PBMoves,:KINGSSHIELD) + pbChangeForm(0,_INTL("{1} changed to Shield Forme!",pbThis)) + end + end + # Calculate the move's type during this usage + move.calcType = move.pbCalcType(self) + # Start effect of Mold Breaker + @battle.moldBreaker = hasMoldBreaker? + # Remember that user chose a two-turn move + if move.pbIsChargingTurn?(self) + # Beginning the use of a two-turn attack + @effects[PBEffects::TwoTurnAttack] = move.id + @currentMove = move.id + else + @effects[PBEffects::TwoTurnAttack] = 0 # Cancel use of two-turn attack + end + # Add to counters for moves which increase them when used in succession + move.pbChangeUsageCounters(self,specialUsage) + # Charge up Metronome item + if hasActiveItem?(:METRONOME) && !move.callsAnotherMove? + if @lastMoveUsed==move.id && !@lastMoveFailed + @effects[PBEffects::Metronome] += 1 + else + @effects[PBEffects::Metronome] = 0 + end + end + # Record move as having been used + @lastMoveUsed = move.id + @lastMoveUsedType = move.calcType # For Conversion 2 + if !specialUsage + @lastRegularMoveUsed = move.id # For Disable, Encore, Instruct, Mimic, Mirror Move, Sketch, Spite + @lastRegularMoveTarget = choice[3] # For Instruct (remembering original target is fine) + @movesUsed.push(move.id) if !@movesUsed.include?(move.id) # For Last Resort + end + @battle.lastMoveUsed = move.id # For Copycat + @battle.lastMoveUser = @index # For "self KO" battle clause to avoid draws + @battle.successStates[@index].useState = 1 # Battle Arena - assume failure + # Find the default user (self or Snatcher) and target(s) + user = pbFindUser(choice,move) + user = pbChangeUser(choice,move,user) + targets = pbFindTargets(choice,move,user) + targets = pbChangeTargets(move,user,targets) + # Pressure + if !specialUsage + targets.each do |b| + next unless b.opposes?(user) && b.hasActiveAbility?(:PRESSURE) + PBDebug.log("[Ability triggered] #{b.pbThis}'s #{b.abilityName}") + user.pbReducePP(move) + end + if PBTargets.targetsFoeSide?(move.pbTarget(user)) + @battle.eachOtherSideBattler(user) do |b| + next unless b.hasActiveAbility?(:PRESSURE) + PBDebug.log("[Ability triggered] #{b.pbThis}'s #{b.abilityName}") + user.pbReducePP(move) + end + end + end + # Dazzling/Queenly Majesty make the move fail here + @battle.pbPriority(true).each do |b| + next if !b || !b.abilityActive? + if BattleHandlers.triggerMoveBlockingAbility(b.ability,b,user,targets,move,@battle) + @battle.pbDisplayBrief(_INTL("{1} used {2}!",user.pbThis,move.name)) + @battle.pbShowAbilitySplash(b) + @battle.pbDisplay(_INTL("{1} cannot use {2}!",user.pbThis,move.name)) + @battle.pbHideAbilitySplash(b) + user.lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + end + # "X used Y!" message + # Can be different for Bide, Fling, Focus Punch and Future Sight + # NOTE: This intentionally passes self rather than user. The user is always + # self except if Snatched, but this message should state the original + # user (self) even if the move is Snatched. + move.pbDisplayUseMessage(self) + # Snatch's message (user is the new user, self is the original user) + if move.snatched + @lastMoveFailed = true # Intentionally applies to self, not user + @battle.pbDisplay(_INTL("{1} snatched {2}'s move!",user.pbThis,pbThis(true))) + end + # "But it failed!" checks + if move.pbMoveFailed?(user,targets) + PBDebug.log(sprintf("[Move failed] In function code %s's def pbMoveFailed?",move.function)) + user.lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + # Perform set-up actions and display messages + # Messages include Magnitude's number and Pledge moves' "it's a combo!" + move.pbOnStartUse(user,targets) + # Self-thawing due to the move + if user.status==PBStatuses::FROZEN && move.thawsUser? + user.pbCureStatus(false) + @battle.pbDisplay(_INTL("{1} melted the ice!",user.pbThis)) + end + # Powder + if user.effects[PBEffects::Powder] && isConst?(move.calcType,PBTypes,:FIRE) + @battle.pbCommonAnimation("Powder",user) + @battle.pbDisplay(_INTL("When the flame touched the powder on the Pokémon, it exploded!")) + user.lastMoveFailed = true + w = @battle.pbWeather + if w!=PBWeather.RAINDANCE && w!=PBWeather.HEAVYRAIN && user.takesIndirectDamage? + oldHP = user.hp + user.pbReduceHP((user.totalhp/4.0).round,false) + user.pbFaint if user.fainted? + @battle.pbGainExp # In case user is KO'd by this + user.pbItemHPHealCheck + if user.pbAbilitiesOnDamageTaken(oldHP) + user.pbEffectsOnSwitchIn(true) + end + end + pbCancelMoves + pbEndTurn(choice) + return + end + # Primordial Sea, Desolate Land + if move.damagingMove? + case @battle.pbWeather + when PBWeather::HeavyRain + if isConst?(move.calcType,PBTypes,:FIRE) + @battle.pbDisplay(_INTL("The Fire-type attack fizzled out in the heavy rain!")) + user.lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + when PBWeather::HarshSun + if isConst?(move.calcType,PBTypes,:WATER) + @battle.pbDisplay(_INTL("The Water-type attack evaporated in the harsh sunlight!")) + user.lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + end + end + # Protean + if user.hasActiveAbility?(:PROTEAN) && !move.callsAnotherMove? && !move.snatched + if user.pbHasOtherType?(moveType) && !PBTypes.isPseudoType?(move.calcType) + @battle.pbShowAbilitySplash(user) + user.pbChangeTypes(move.calcType) + typeName = PBTypes.getName(move.calcType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",user.pbThis,typeName)) + @battle.pbHideAbilitySplash(user) + # NOTE: The GF games say that if Curse is used by a non-Ghost-type + # Pokémon which becomes Ghost-type because of Protean, it should + # target and curse itself. I think this is silly, so I'm making it + # choose a random opponent to curse instead. + if move.function=="10D" && targets.length==0 # Curse + choice[3] = -1 + targets = pbFindTargets(choice,move,user) + end + end + end + #--------------------------------------------------------------------------- + magicCoater = -1 + magicBouncer = -1 + if targets.length==0 && !PBTargets.noTargets?(move.pbTarget(user)) && + !move.worksWithNoTargets? + # def pbFindTargets should have found a target(s), but it didn't because + # they were all fainted + # All target types except: None, User, UserSide, FoeSide, BothSides + @battle.pbDisplay(_INTL("But there was no target...")) + user.lastMoveFailed = true + else # We have targets, or move doesn't use targets + # Reset whole damage state, perform various success checks (not accuracy) + user.initialHP = user.hp + targets.each do |b| + b.damageState.reset + b.damageState.initialHP = b.hp + if !pbSuccessCheckAgainstTarget(move,user,b) + b.damageState.unaffected = true + end + end + # Magic Coat/Magic Bounce checks (for moves which don't target Pokémon) + if targets.length==0 && move.canMagicCoat? + @battle.pbPriority(true).each do |b| + next if b.fainted? || !b.opposes?(user) + next if b.semiInvulnerable? + if b.effects[PBEffects::MagicCoat] + magicCoater = b.index + b.effects[PBEffects::MagicCoat] = false + break + elsif b.hasActiveAbility?(:MAGICBOUNCE) && !@battle.moldBreaker && + !b.effects[PBEffects::MagicBounce] + magicBouncer = b.index + b.effects[PBEffects::MagicBounce] = true + break + end + end + end + # Get the number of hits + numHits = move.pbNumHits(user,targets) + # Process each hit in turn + realNumHits = 0 + for i in 0...numHits + break if magicCoater>=0 || magicBouncer>=0 + success = pbProcessMoveHit(move,user,targets,i,skipAccuracyCheck) + if !success + if i==0 && targets.length>0 + hasFailed = false + targets.each do |t| + next if t.damageState.protected + hasFailed = t.damageState.unaffected + break if !t.damageState.unaffected + end + user.lastMoveFailed = hasFailed + end + break + end + realNumHits += 1 + break if user.fainted? + break if user.status==PBStatuses::SLEEP || user.status==PBStatuses::FROZEN + # NOTE: If a multi-hit move becomes disabled partway through doing those + # hits (e.g. by Cursed Body), the rest of the hits continue as + # normal. + notFainted = false + break if !targets.any? { |t| !t.fainted? } # All targets are fainted + end + # Battle Arena only - attack is successful + @battle.successStates[user.index].useState = 2 + if targets.length>0 + @battle.successStates[user.index].typeMod = 0 + targets.each do |b| + next if b.damageState.unaffected + @battle.successStates[user.index].typeMod += b.damageState.typeMod + end + end + # Effectiveness message for multi-hit moves + # NOTE: No move is both multi-hit and multi-target, and the messages below + # aren't quite right for such a hypothetical move. + if numHits>1 + if move.damagingMove? + targets.each do |b| + next if b.damageState.unaffected || b.damageState.substitute + move.pbEffectivenessMessage(user,b,targets.length) + end + end + if realNumHits==1 + @battle.pbDisplay(_INTL("Hit 1 time!")) + elsif realNumHits>1 + @battle.pbDisplay(_INTL("Hit {1} times!",realNumHits)) + end + end + # Magic Coat's bouncing back (move has targets) + targets.each do |b| + next if b.fainted? + next if !b.damageState.magicCoat && !b.damageState.magicBounce + @battle.pbShowAbilitySplash(b) if b.damageState.magicBounce + @battle.pbDisplay(_INTL("{1} bounced the {2} back!",b.pbThis,move.name)) + @battle.pbHideAbilitySplash(b) if b.damageState.magicBounce + newChoice = choice.clone + newChoice[3] = user.index + newTargets = pbFindTargets(newChoice,move,b) + newTargets = pbChangeTargets(move,b,newTargets) + success = pbProcessMoveHit(move,b,newTargets,0,false) + b.lastMoveFailed = true if !success + targets.each { |b| b.pbFaint if b && b.fainted? } + user.pbFaint if user.fainted? + end + # Magic Coat's bouncing back (move has no targets) + if magicCoater>=0 || magicBouncer>=0 + mc = @battle.battlers[(magicCoater>=0) ? magicCoater : magicBouncer] + if !mc.fainted? + user.lastMoveFailed = true + @battle.pbShowAbilitySplash(mc) if magicBouncer>=0 + @battle.pbDisplay(_INTL("{1} bounced the {2} back!",mc.pbThis,move.name)) + @battle.pbHideAbilitySplash(mc) if magicBouncer>=0 + success = pbProcessMoveHit(move,mc,[],0,false) + mc.lastMoveFailed = true if !success + targets.each { |b| b.pbFaint if b && b.fainted? } + user.pbFaint if user.fainted? + end + end + # Move-specific effects after all hits + targets.each { |b| move.pbEffectAfterAllHits(user,b) } + # Faint if 0 HP + targets.each { |b| b.pbFaint if b && b.fainted? } + user.pbFaint if user.fainted? + # External/general effects after all hits. Eject Button, Shell Bell, etc. + pbEffectsAfterMove(user,targets,move,realNumHits) + end + # End effect of Mold Breaker + @battle.moldBreaker = false + # Gain Exp + @battle.pbGainExp + # Battle Arena only - update skills + @battle.eachBattler { |b| @battle.successStates[b.index].updateSkill } + # Shadow Pokémon triggering Hyper Mode + pbHyperMode if @battle.choices[@index][0]!=:None # Not if self is replaced + # End of move usage + pbEndTurn(choice) + # Instruct + @battle.eachBattler do |b| + next if !b.effects[PBEffects::Instruct] + b.effects[PBEffects::Instruct] = false + idxMove = -1 + b.eachMoveWithIndex { |m,i| idxMove = i if m.id==b.lastMoveUsed } + next if idxMove<0 + oldLastRoundMoved = b.lastRoundMoved + @battle.pbDisplay(_INTL("{1} used the move instructed by {2}!",b.pbThis,user.pbThis(true))) + PBDebug.logonerr{ + b.effects[PBEffects::Instructed] = true + b.pbUseMoveSimple(b.lastMoveUsed,b.lastRegularMoveTarget,idxMove,false) + b.effects[PBEffects::Instructed] = false + } + b.lastRoundMoved = oldLastRoundMoved + @battle.pbJudge + return if @battle.decision>0 + end + # Dancer + if !@effects[PBEffects::Dancer] && !user.lastMoveFailed && realNumHits>0 && + !move.snatched && magicCoater<0 && @battle.pbCheckGlobalAbility(:DANCER) + dancers = [] + @battle.pbPriority(true).each do |b| + dancers.push(b) if b.index!=user.index && b.hasActiveAbility?(:DANCER) + end + while dancers.length>0 + nextUser = dancers.pop + oldLastRoundMoved = nextUser.lastRoundMoved + # NOTE: Petal Dance being used because of Dancer shouldn't lock the + # Dancer into using that move, and shouldn't contribute to its + # turn counter if it's already locked into Petal Dance. + oldOutrage = nextUser.effects[PBEffects::Outrage] + nextUser.effects[PBEffects::Outrage] += 1 if nextUser.effects[PBEffects::Outrage]>0 + oldCurrentMove = nextUser.currentMove + preTarget = choice[3] + preTarget = user.index if nextUser.opposes?(user) || !nextUser.opposes?(preTarget) + @battle.pbShowAbilitySplash(nextUser,true) + @battle.pbHideAbilitySplash(nextUser) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} kept the dance going with {2}!", + nextUser.pbThis,nextUser.abilityName)) + end + PBDebug.logonerr{ + nextUser.effects[PBEffects::Dancer] = true + nextUser.pbUseMoveSimple(move.id,preTarget) + nextUser.effects[PBEffects::Dancer] = false + } + nextUser.lastRoundMoved = oldLastRoundMoved + nextUser.effects[PBEffects::Outrage] = oldOutrage + nextUser.currentMove = oldCurrentMove + @battle.pbJudge + return if @battle.decision>0 + end + end + end + + #============================================================================= + # Attack a single target + #============================================================================= + def pbProcessMoveHit(move,user,targets,hitNum,skipAccuracyCheck) + return false if user.fainted? + # For two-turn attacks being used in a single turn + move.pbInitialEffect(user,targets,hitNum) + numTargets = 0 # Number of targets that are affected by this hit + targets.each { |b| b.damageState.resetPerHit } + # Count a hit for Parental Bond (if it applies) + user.effects[PBEffects::ParentalBond] -= 1 if user.effects[PBEffects::ParentalBond]>0 + # Accuracy check (accuracy/evasion calc) + if hitNum==0 || move.successCheckPerHit? + targets.each do |b| + next if b.damageState.unaffected + if pbSuccessCheckPerHit(move,user,b,skipAccuracyCheck) + numTargets += 1 + else + b.damageState.missed = true + b.damageState.unaffected = true + end + end + # If failed against all targets + if targets.length>0 && numTargets==0 && !move.worksWithNoTargets? + targets.each do |b| + next if !b.damageState.missed || b.damageState.magicCoat + pbMissMessage(move,user,b) + end + move.pbCrashDamage(user) + user.pbItemHPHealCheck + pbCancelMoves + return false + end + end + # If we get here, this hit will happen and do something + #--------------------------------------------------------------------------- + # Calculate damage to deal + if move.pbDamagingMove? + targets.each do |b| + next if b.damageState.unaffected + # Check whether Substitute/Disguise will absorb the damage + move.pbCheckDamageAbsorption(user,b) + # Calculate the damage against b + # pbCalcDamage shows the "eat berry" animation for SE-weakening + # berries, although the message about it comes after the additional + # effect below + move.pbCalcDamage(user,b,targets.length) # Stored in damageState.calcDamage + # Lessen damage dealt because of False Swipe/Endure/etc. + move.pbReduceDamage(user,b) # Stored in damageState.hpLost + end + end + # Show move animation (for this hit) + move.pbShowAnimation(move.id,user,targets,hitNum) + # Type-boosting Gem consume animation/message + if user.effects[PBEffects::GemConsumed]>0 && hitNum==0 + # NOTE: The consume animation and message for Gems are shown now, but the + # actual removal of the item happens in def pbEffectsAfterMove. + @battle.pbCommonAnimation("UseItem",user) + @battle.pbDisplay(_INTL("The {1} strengthened {2}'s power!", + PBItems.getName(user.effects[PBEffects::GemConsumed]),move.name)) + end + # Messages about missed target(s) (relevant for multi-target moves only) + targets.each do |b| + next if !b.damageState.missed + pbMissMessage(move,user,b) + end + # Deal the damage (to all allies first simultaneously, then all foes + # simultaneously) + if move.pbDamagingMove? + # This just changes the HP amounts and does nothing else + targets.each do |b| + next if b.damageState.unaffected + move.pbInflictHPDamage(b) + end + # Animate the hit flashing and HP bar changes + move.pbAnimateHitAndHPLost(user,targets) + end + # Self-Destruct/Explosion's damaging and fainting of user + move.pbSelfKO(user) if hitNum==0 + user.pbFaint if user.fainted? + if move.pbDamagingMove? + targets.each do |b| + next if b.damageState.unaffected + # NOTE: This method is also used for the OKHO special message. + move.pbHitEffectivenessMessages(user,b,targets.length) + # Record data about the hit for various effects' purposes + move.pbRecordDamageLost(user,b) + end + # Close Combat/Superpower's stat-lowering, Flame Burst's splash damage, + # and Incinerate's berry destruction + targets.each do |b| + next if b.damageState.unaffected + move.pbEffectWhenDealingDamage(user,b) + end + # Ability/item effects such as Static/Rocky Helmet, and Grudge, etc. + targets.each do |b| + next if b.damageState.unaffected + pbEffectsOnMakingHit(move,user,b) + end + # Disguise/Endure/Sturdy/Focus Sash/Focus Band messages + targets.each do |b| + next if b.damageState.unaffected + move.pbEndureKOMessage(b) + end + # HP-healing held items (checks all battlers rather than just targets + # because Flame Burst's splash damage affects non-targets) + @battle.pbPriority(true).each { |b| b.pbItemHPHealCheck } + # Animate battlers fainting (checks all battlers rather than just targets + # because Flame Burst's splash damage affects non-targets) + @battle.pbPriority(true).each { |b| b.pbFaint if b && b.fainted? } + end + @battle.pbJudgeCheckpoint(user,move) + # Main effect (recoil/drain, etc.) + targets.each do |b| + next if b.damageState.unaffected + move.pbEffectAgainstTarget(user,b) + end + move.pbEffectGeneral(user) + targets.each { |b| b.pbFaint if b && b.fainted? } + user.pbFaint if user.fainted? + # Additional effect + if !user.hasActiveAbility?(:SHEERFORCE) + targets.each do |b| + next if b.damageState.calcDamage==0 + chance = move.pbAdditionalEffectChance(user,b) + next if chance<=0 + if @battle.pbRandom(100)=strength + next if b.effects[PBEffects::SkyDrop]>=0 + newUser = b + strength = b.effects[PBEffects::Snatch] + end + if newUser + user = newUser + user.effects[PBEffects::Snatch] = 0 + move.snatched = true + @battle.moldBreaker = user.hasMoldBreaker? + choice[3] = -1 # Clear pre-chosen target + end + end + return user + end + + #============================================================================= + # Get move's default target(s) + #============================================================================= + def pbFindTargets(choice,move,user) + preTarget = choice[3] # A target that was already chosen + targets = [] + # Get list of targets + case move.pbTarget(user) # Curse can change its target type + when PBTargets::NearAlly + targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil + if !pbAddTarget(targets,user,targetBattler,move) + pbAddTargetRandomAlly(targets,user,move) + end + when PBTargets::UserOrNearAlly + targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil + if !pbAddTarget(targets,user,targetBattler,move,true,true) + pbAddTarget(targets,user,user,move,true,true) + end + when PBTargets::NearFoe, PBTargets::NearOther + targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil + if !pbAddTarget(targets,user,targetBattler,move) + if preTarget>=0 && !user.opposes?(preTarget) + pbAddTargetRandomAlly(targets,user,move) + else + pbAddTargetRandomFoe(targets,user,move) + end + end + when PBTargets::AllNearFoes + @battle.eachOtherSideBattler(user.index) { |b| pbAddTarget(targets,user,b,move) } + when PBTargets::RandomNearFoe + pbAddTargetRandomFoe(targets,user,move) + when PBTargets::AllNearOthers + @battle.eachBattler { |b| pbAddTarget(targets,user,b,move) } + when PBTargets::Other + targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil + if !pbAddTarget(targets,user,targetBattler,move,false) + if preTarget>=0 && !user.opposes?(preTarget) + pbAddTargetRandomAlly(targets,user,move,false) + else + pbAddTargetRandomFoe(targets,user,move,false) + end + end + when PBTargets::UserAndAllies + pbAddTarget(targets,user,user,move,true,true) + @battle.eachSameSideBattler(user.index) { |b| pbAddTarget(targets,user,b,move,false,true) } + when PBTargets::AllFoes + @battle.eachOtherSideBattler(user.index) { |b| pbAddTarget(targets,user,b,move,false) } + when PBTargets::AllBattlers + @battle.eachBattler { |b| pbAddTarget(targets,user,b,move,false,true) } + else + # Used by Counter/Mirror Coat/Metal Burst/Bide + move.pbAddTarget(targets,user) # Move-specific pbAddTarget, not the def below + end + return targets + end + + #============================================================================= + # Redirect attack to another target + #============================================================================= + def pbChangeTargets(move,user,targets) + targetType = move.pbTarget(user) + return targets if @battle.switching # For Pursuit interrupting a switch + return targets if move.cannotRedirect? + return targets if !PBTargets.canChooseOneFoeTarget?(targetType) || targets.length!=1 + priority = @battle.pbPriority(true) + nearOnly = !PBTargets.canChooseDistantTarget?(move.target) + # Spotlight (takes priority over Follow Me/Rage Powder/Lightning Rod/Storm Drain) + newTarget = nil; strength = 100 # Lower strength takes priority + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::SkyDrop]>=0 + next if b.effects[PBEffects::Spotlight]==0 || + b.effects[PBEffects::Spotlight]>=strength + next if !b.opposes?(user) + next if nearOnly && !b.near?(user) + newTarget = b + strength = b.effects[PBEffects::Spotlight] + end + if newTarget + PBDebug.log("[Move target changed] #{newTarget.pbThis}'s Spotlight made it the target") + targets = [] + pbAddTarget(targets,user,newTarget,move,nearOnly) + return targets + end + # Follow Me/Rage Powder (takes priority over Lightning Rod/Storm Drain) + newTarget = nil; strength = 100 # Lower strength takes priority + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::SkyDrop]>=0 + next if b.effects[PBEffects::RagePowder] && !user.affectedByPowder? + next if b.effects[PBEffects::FollowMe]==0 || + b.effects[PBEffects::FollowMe]>=strength + next if !b.opposes?(user) + next if nearOnly && !b.near?(user) + newTarget = b + strength = b.effects[PBEffects::FollowMe] + end + if newTarget + PBDebug.log("[Move target changed] #{newTarget.pbThis}'s Follow Me/Rage Powder made it the target") + targets = [] + pbAddTarget(targets,user,newTarget,move,nearOnly) + return targets + end + # Lightning Rod + targets = pbChangeTargetByAbility(:LIGHTNINGROD,:ELECTRIC,move,user,targets,priority,nearOnly) + # Storm Drain + targets = pbChangeTargetByAbility(:STORMDRAIN,:WATER,move,user,targets,priority,nearOnly) + return targets + end + + def pbChangeTargetByAbility(drawingAbility,drawnType,move,user,targets,priority,nearOnly) + return targets if !isConst?(move.calcType,PBTypes,drawnType) + return targets if targets[0].hasActiveAbility?(drawingAbility) + priority.each do |b| + next if b.index==user.index || b.index==targets[0].index + next if !b.hasActiveAbility?(drawingAbility) + next if nearOnly && !b.near?(user) + @battle.pbShowAbilitySplash(b) + targets.clear + pbAddTarget(targets,user,b,move,nearOnly) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} took the attack!",b.pbThis)) + else + @battle.pbDisplay(_INTL("{1} took the attack with its {2}!",b.pbThis,b.abilityName)) + end + @battle.pbHideAbilitySplash(b) + break + end + return targets + end + + #============================================================================= + # Register target + #============================================================================= + def pbAddTarget(targets,user,target,move,nearOnly=true,allowUser=false) + return false if !target || (target.fainted? && !move.cannotRedirect?) + return false if !(allowUser && user==target) && nearOnly && !user.near?(target) + targets.each { |b| return true if b.index==target.index } # Already added + targets.push(target) + return true + end + + def pbAddTargetRandomAlly(targets,user,move,nearOnly=true) + choices = [] + user.eachAlly do |b| + next if nearOnly && !user.near?(b) + pbAddTarget(choices,user,b,nearOnly) + end + if choices.length>0 + pbAddTarget(targets,user,choices[@battle.pbRandom(choices.length)],nearOnly) + end + end + + def pbAddTargetRandomFoe(targets,user,move,nearOnly=true) + choices = [] + user.eachOpposing do |b| + next if nearOnly && !user.near?(b) + pbAddTarget(choices,user,b,nearOnly) + end + if choices.length>0 + pbAddTarget(targets,user,choices[@battle.pbRandom(choices.length)],nearOnly) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/009_Battler_UseMove_SuccessChecks.rb b/Data/Scripts/011_Battle/001_Battler/009_Battler_UseMove_SuccessChecks.rb new file mode 100644 index 000000000..f842a364a --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/009_Battler_UseMove_SuccessChecks.rb @@ -0,0 +1,538 @@ +class PokeBattle_Battler + #============================================================================= + # Decide whether the trainer is allowed to tell the Pokémon to use the given + # move. Called when choosing a command for the round. + # Also called when processing the Pokémon's action, because these effects also + # prevent Pokémon action. Relevant because these effects can become active + # earlier in the same round (after choosing the command but before using the + # move) or an unusable move may be called by another move such as Metronome. + #============================================================================= + def pbCanChooseMove?(move,commandPhase,showMessages=true,specialUsage=false) + # Disable + if @effects[PBEffects::DisableMove]==move.id && !specialUsage + if showMessages + msg = _INTL("{1}'s {2} is disabled!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Heal Block + if @effects[PBEffects::HealBlock]>0 && move.healingMove? + if showMessages + msg = _INTL("{1} can't use {2} because of Heal Block!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Gravity + if @battle.field.effects[PBEffects::Gravity]>0 && move.unusableInGravity? + if showMessages + msg = _INTL("{1} can't use {2} because of gravity!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Throat Chop + if @effects[PBEffects::ThroatChop]>0 && move.soundMove? + if showMessages + msg = _INTL("{1} can't use {2} because of Throat Chop!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Choice Band + if @effects[PBEffects::ChoiceBand]>=0 + if hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF]) && + pbHasMove?(@effects[PBEffects::ChoiceBand]) + if move.id!=@effects[PBEffects::ChoiceBand] + if showMessages + msg = _INTL("{1} allows the use of only {2}!",itemName, + PBMoves.getName(@effects[PBEffects::ChoiceBand])) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + else + @effects[PBEffects::ChoiceBand] = -1 + end + end + # Taunt + if @effects[PBEffects::Taunt]>0 && move.statusMove? + if showMessages + msg = _INTL("{1} can't use {2} after the taunt!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Torment + if @effects[PBEffects::Torment] && !@effects[PBEffects::Instructed] && + move.id==@lastMoveUsed && move.id!=@battle.struggle.id + if showMessages + msg = _INTL("{1} can't use the same move twice in a row due to the torment!",pbThis) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Imprison + @battle.eachOtherSideBattler(@index) do |b| + next if !b.effects[PBEffects::Imprison] || !b.pbHasMove?(move.id) + if showMessages + msg = _INTL("{1} can't use its sealed {2}!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Assault Vest (prevents choosing status moves but doesn't prevent + # executing them) + if hasActiveItem?(:ASSAULTVEST) && move.statusMove? && commandPhase + if showMessages + msg = _INTL("The effects of the {1} prevent status moves from being used!", + itemName) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Belch + return false if !move.pbCanChooseMove?(self,commandPhase,showMessages) + return true + end + + #============================================================================= + # Obedience check + #============================================================================= + # Return true if Pokémon continues attacking (although it may have chosen to + # use a different move in disobedience), or false if attack stops. + def pbObedienceCheck?(choice) + return true if usingMultiTurnAttack? + return true if choice[0]!=:UseMove + return true if !@battle.internalBattle + return true if !@battle.pbOwnedByPlayer?(@index) + disobedient = false + # Pokémon may be disobedient; calculate if it is + badgeLevel = 10*(@battle.pbPlayer.numbadges+1) + badgeLevel = PBExperience.maxLevel if @battle.pbPlayer.numbadges>=8 + if @pokemon.foreign?(@battle.pbPlayer) && @level>badgeLevel + a = ((@level+badgeLevel)*@battle.pbRandom(256)/256).floor + disobedient |= (a>=badgeLevel) + end + disobedient |= !pbHyperModeObedience(choice[2]) + return true if !disobedient + # Pokémon is disobedient; make it do something else + return pbDisobey(choice,badgeLevel) + end + + def pbDisobey(choice,badgeLevel) + move = choice[2] + PBDebug.log("[Disobedience] #{pbThis} disobeyed") + @effects[PBEffects::Rage] = false + # Do nothing if using Snore/Sleep Talk + if @status==PBStatuses::SLEEP && move.usableWhenAsleep? + @battle.pbDisplay(_INTL("{1} ignored orders and kept sleeping!",pbThis)) + return false + end + b = ((@level+badgeLevel)*@battle.pbRandom(256)/256).floor + # Use another move + if b=0 # Intentionally no message here + PBDebug.log("[Move failed] #{pbThis} can't use #{move.name} because of being Sky Dropped") + return false + end + if @effects[PBEffects::HyperBeam]>0 # Intentionally before Truant + @battle.pbDisplay(_INTL("{1} must recharge!",pbThis)) + return false + end + if choice[1]==-2 # Battle Palace + @battle.pbDisplay(_INTL("{1} appears incapable of using its power!",pbThis)) + return false + end + # Skip checking all applied effects that could make self fail doing something + return true if skipAccuracyCheck + # Check status problems and continue their effects/cure them + case @status + when PBStatuses::SLEEP + self.statusCount -= 1 + if @statusCount<=0 + pbCureStatus + else + pbContinueStatus + if !move.usableWhenAsleep? # Snore/Sleep Talk + @lastMoveFailed = true + return false + end + end + when PBStatuses::FROZEN + if !move.thawsUser? + if @battle.pbRandom(100)<20 + pbCureStatus + else + pbContinueStatus + @lastMoveFailed = true + return false + end + end + end + # Obedience check + return false if !pbObedienceCheck?(choice) + # Truant + if hasActiveAbility?(:TRUANT) + @effects[PBEffects::Truant] = !@effects[PBEffects::Truant] + if !@effects[PBEffects::Truant] # True means loafing, but was just inverted + @battle.pbShowAbilitySplash(self) + @battle.pbDisplay(_INTL("{1} is loafing around!",pbThis)) + @lastMoveFailed = true + @battle.pbHideAbilitySplash(self) + return false + end + end + # Flinching + if @effects[PBEffects::Flinch] + @battle.pbDisplay(_INTL("{1} flinched and couldn't move!",pbThis)) + if abilityActive? + BattleHandlers.triggerAbilityOnFlinch(@ability,self,@battle) + end + @lastMoveFailed = true + return false + end + # Confusion + if @effects[PBEffects::Confusion]>0 + @effects[PBEffects::Confusion] -= 1 + if @effects[PBEffects::Confusion]<=0 + pbCureConfusion + @battle.pbDisplay(_INTL("{1} snapped out of its confusion.",pbThis)) + else + @battle.pbCommonAnimation("Confusion",self) + @battle.pbDisplay(_INTL("{1} is confused!",pbThis)) + threshold = (NEWEST_BATTLE_MECHANICS) ? 33 : 50 # % chance + if @battle.pbRandom(100)=0 + @battle.pbCommonAnimation("Attract",self) + @battle.pbDisplay(_INTL("{1} is in love with {2}!",pbThis, + @battle.battlers[@effects[PBEffects::Attract]].pbThis(true))) + if @battle.pbRandom(100)<50 + @battle.pbDisplay(_INTL("{1} is immobilized by love!",pbThis)) + @lastMoveFailed = true + return false + end + end + return true + end + + #============================================================================= + # Initial success check against the target. Done once before the first hit. + # Includes move-specific failure conditions, protections and type immunities. + #============================================================================= + def pbSuccessCheckAgainstTarget(move,user,target) + typeMod = move.pbCalcTypeMod(move.calcType,user,target) + target.damageState.typeMod = typeMod + # Two-turn attacks can't fail here in the charging turn + return true if user.effects[PBEffects::TwoTurnAttack]>0 + # Move-specific failures + return false if move.pbFailsAgainstTarget?(user,target) + # Immunity to priority moves because of Psychic Terrain + if @battle.field.terrain==PBBattleTerrains::Psychic && target.affectedByTerrain? && + target.opposes?(user) && + @battle.choices[user.index][4]>0 # Move priority saved from pbCalculatePriority + @battle.pbDisplay(_INTL("{1} surrounds itself with psychic terrain!",target.pbThis)) + return false + end + # Crafty Shield + if target.pbOwnSide.effects[PBEffects::CraftyShield] && user.index!=target.index && + move.statusMove? && move.pbTarget(user)!=PBTargets::AllBattlers + @battle.pbCommonAnimation("CraftyShield",target) + @battle.pbDisplay(_INTL("Crafty Shield protected {1}!",target.pbThis(true))) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + # Wide Guard + if target.pbOwnSide.effects[PBEffects::WideGuard] && user.index!=target.index && + PBTargets.multipleTargets?(move.pbTarget(user)) && + (NEWEST_BATTLE_MECHANICS || move.damagingMove?) + @battle.pbCommonAnimation("WideGuard",target) + @battle.pbDisplay(_INTL("Wide Guard protected {1}!",target.pbThis(true))) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + if move.canProtectAgainst? + # Quick Guard + if target.pbOwnSide.effects[PBEffects::QuickGuard] && + @battle.choices[user.index][4]>0 # Move priority saved from pbCalculatePriority + @battle.pbCommonAnimation("QuickGuard",target) + @battle.pbDisplay(_INTL("Quick Guard protected {1}!",target.pbThis(true))) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + # Protect + if target.effects[PBEffects::Protect] + @battle.pbCommonAnimation("Protect",target) + @battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + # King's Shield + if target.effects[PBEffects::KingsShield] && move.damagingMove? + @battle.pbCommonAnimation("KingsShield",target) + @battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + if move.pbContactMove?(user) && user.affectedByContactEffect? + if user.pbCanLowerStatStage?(PBStats::ATTACK) + user.pbLowerStatStage(PBStats::ATTACK,2,nil) + end + end + return false + end + # Spiky Shield + if target.effects[PBEffects::SpikyShield] + @battle.pbCommonAnimation("SpikyShield",target) + @battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + if move.pbContactMove?(user) && user.affectedByContactEffect? + @battle.scene.pbDamageAnimation(user) + user.pbReduceHP(user.totalhp/8,false) + @battle.pbDisplay(_INTL("{1} was hurt!",user.pbThis)) + user.pbItemHPHealCheck + end + return false + end + # Baneful Bunker + if target.effects[PBEffects::BanefulBunker] + @battle.pbCommonAnimation("BanefulBunker",target) + @battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + if move.pbContactMove?(user) && user.affectedByContactEffect? + user.pbPoison(target) if user.pbCanPoison?(target,false) + end + return false + end + # Mat Block + if target.pbOwnSide.effects[PBEffects::MatBlock] && move.damagingMove? + # NOTE: Confirmed no common animation for this effect. + @battle.pbDisplay(_INTL("{1} was blocked by the kicked-up mat!",move.name)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + end + # Magic Coat/Magic Bounce + if move.canMagicCoat? && !target.semiInvulnerable? && target.opposes?(user) + if target.effects[PBEffects::MagicCoat] + target.damageState.magicCoat = true + target.effects[PBEffects::MagicCoat] = false + return false + end + if target.hasActiveAbility?(:MAGICBOUNCE) && !@battle.moldBreaker && + !target.effects[PBEffects::MagicBounce] + target.damageState.magicBounce = true + target.effects[PBEffects::MagicBounce] = true + return false + end + end + # Immunity because of ability (intentionally before type immunity check) + return false if move.pbImmunityByAbility(user,target) + # Type immunity + if move.pbDamagingMove? && PBTypes.ineffective?(typeMod) + PBDebug.log("[Target immune] #{target.pbThis}'s type immunity") + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return false + end + # Dark-type immunity to moves made faster by Prankster + if NEWEST_BATTLE_MECHANICS && user.effects[PBEffects::Prankster] && + target.pbHasType?(:DARK) && target.opposes?(user) + PBDebug.log("[Target immune] #{target.pbThis} is Dark-type and immune to Prankster-boosted moves") + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return false + end + # Airborne-based immunity to Ground moves + if move.damagingMove? && isConst?(move.calcType,PBTypes,:GROUND) && + target.airborne? && !move.hitsFlyingTargets? + if target.hasActiveAbility?(:LEVITATE) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} avoided the attack with {2}!",target.pbThis,target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return false + end + if target.hasActiveItem?(:AIRBALLOON) + @battle.pbDisplay(_INTL("{1}'s {2} makes Ground moves miss!",target.pbThis,target.itemName)) + return false + end + if target.effects[PBEffects::MagnetRise]>0 + @battle.pbDisplay(_INTL("{1} makes Ground moves miss with Magnet Rise!",target.pbThis)) + return false + end + if target.effects[PBEffects::Telekinesis]>0 + @battle.pbDisplay(_INTL("{1} makes Ground moves miss with Telekinesis!",target.pbThis)) + return false + end + end + # Immunity to powder-based moves + if NEWEST_BATTLE_MECHANICS && move.powderMove? + if target.pbHasType?(:GRASS) + PBDebug.log("[Target immune] #{target.pbThis} is Grass-type and immune to powder-based moves") + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return false + end + if target.hasActiveAbility?(:OVERCOAT) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("It doesn't affect {1} because of its {2}.",target.pbThis(true),target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return false + end + if target.hasActiveItem?(:SAFETYGOGGLES) + PBDebug.log("[Item triggered] #{target.pbThis} has Safety Goggles and is immune to powder-based moves") + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return false + end + end + # Substitute + if target.effects[PBEffects::Substitute]>0 && move.statusMove? && + !move.ignoresSubstitute?(user) && user.index!=target.index + PBDebug.log("[Target immune] #{target.pbThis} is protected by its Substitute") + @battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis(true))) + return false + end + return true + end + + #============================================================================= + # Per-hit success check against the target. + # Includes semi-invulnerable move use and accuracy calculation. + #============================================================================= + def pbSuccessCheckPerHit(move,user,target,skipAccuracyCheck) + # Two-turn attacks can't fail here in the charging turn + return true if user.effects[PBEffects::TwoTurnAttack]>0 + # Lock-On + return true if user.effects[PBEffects::LockOn]>0 && + user.effects[PBEffects::LockOnPos]==target.index + # Toxic + return true if move.pbOverrideSuccessCheckPerHit(user,target) + miss = false; hitsInvul = false + # No Guard + hitsInvul = true if user.hasActiveAbility?(:NOGUARD) || + target.hasActiveAbility?(:NOGUARD) + # Future Sight + hitsInvul = true if @battle.futureSight + # Helping Hand + hitsInvul = true if move.function=="09C" + if !hitsInvul + # Semi-invulnerable moves + if target.effects[PBEffects::TwoTurnAttack]>0 + if target.inTwoTurnAttack?("0C9","0CC","0CE") # Fly, Bounce, Sky Drop + miss = true if !move.hitsFlyingTargets? + elsif target.inTwoTurnAttack?("0CA") # Dig + miss = true if !move.hitsDiggingTargets? + elsif target.inTwoTurnAttack?("0CB") # Dive + miss = true if !move.hitsDivingTargets? + elsif target.inTwoTurnAttack?("0CD","14D") # Shadow Force, Phantom Force + miss = true + end + end + if target.effects[PBEffects::SkyDrop]>=0 && + target.effects[PBEffects::SkyDrop]!=user.index + miss = true if !move.hitsFlyingTargets? + end + end + if !miss + # Called by another move + return true if skipAccuracyCheck + # Accuracy check + return true if move.pbAccuracyCheck(user,target) # Includes Counter/Mirror Coat + end + # Missed + PBDebug.log("[Move failed] Failed pbAccuracyCheck or target is semi-invulnerable") + return false + end + + #============================================================================= + # Message shown when a move fails the per-hit success check above. + #============================================================================= + def pbMissMessage(move,user,target) + tar = move.pbTarget(user) + if PBTargets.multipleTargets?(tar) + @battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis)) + elsif target.effects[PBEffects::TwoTurnAttack]>0 + @battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis)) + elsif !move.pbMissMessage(user,target) + @battle.pbDisplay(_INTL("{1}'s attack missed!",user.pbThis)) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/010_Battler_UseMove_TriggerEffects.rb b/Data/Scripts/011_Battle/001_Battler/010_Battler_UseMove_TriggerEffects.rb new file mode 100644 index 000000000..4bc3527b4 --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/010_Battler_UseMove_TriggerEffects.rb @@ -0,0 +1,188 @@ +class PokeBattle_Battler + #============================================================================= + # Effect per hit + #============================================================================= + def pbEffectsOnMakingHit(move,user,target) + if target.damageState.calcDamage>0 && !target.damageState.substitute + # Target's ability + if target.abilityActive?(true) + oldHP = user.hp + BattleHandlers.triggerTargetAbilityOnHit(target.ability,user,target,move,@battle) + user.pbItemHPHealCheck if user.hp0 && !target.damageState.substitute && move.physicalMove? + target.tookPhysicalHit = true + target.effects[PBEffects::MoveNext] = true + target.effects[PBEffects::Quash] = 0 + end + end + # Grudge + if target.effects[PBEffects::Grudge] && target.fainted? + move.pp = 0 + @battle.pbDisplay(_INTL("{1}'s {2} lost all of its PP due to the grudge!", + user.pbThis,move.name)) + end + # Destiny Bond (recording that it should apply) + if target.effects[PBEffects::DestinyBond] && target.fainted? + if user.effects[PBEffects::DestinyBondTarget]<0 + user.effects[PBEffects::DestinyBondTarget] = target.index + end + end + end + end + + #============================================================================= + # Effects after all hits (i.e. at end of move usage) + #============================================================================= + def pbEffectsAfterMove(user,targets,move,numHits) + # Defrost + if move.damagingMove? + targets.each do |b| + next if b.damageState.unaffected || b.damageState.substitute + next if b.status!=PBStatuses::FROZEN + # NOTE: Non-Fire-type moves that thaw the user will also thaw the + # target (in Gen 6+). + if isConst?(move.calcType,PBTypes,:FIRE) || + (NEWEST_BATTLE_MECHANICS && move.thawsUser?) + b.pbCureStatus + end + end + end + # Destiny Bond + # NOTE: Although Destiny Bond is similar to Grudge, they don't apply at + # the same time (although Destiny Bond does check whether it's going + # to trigger at the same time as Grudge). + if user.effects[PBEffects::DestinyBondTarget]>=0 && !user.fainted? + dbName = @battle.battlers[user.effects[PBEffects::DestinyBondTarget]].pbThis + @battle.pbDisplay(_INTL("{1} took its attacker down with it!",dbName)) + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + user.pbFaint + @battle.pbJudgeCheckpoint(user) + end + # User's ability + if user.abilityActive? + BattleHandlers.triggerUserAbilityEndOfMove(user.ability,user,targets,move,@battle) + end + # Greninja - Battle Bond + if !user.fainted? && !user.effects[PBEffects::Transform] && + isConst?(user.species,PBSpecies,:GRENINJA) && + isConst?(user.ability,PBAbilities,:BATTLEBOND) + if !@battle.pbAllFainted?(user.idxOpposingSide) && + !@battle.battleBond[user.index&1][user.pokemonIndex] + numFainted = 0 + targets.each { |b| numFainted += 1 if b.damageState.fainted } + if numFainted>0 && user.form!=1 + @battle.battleBond[user.index&1][user.pokemonIndex] = true + @battle.pbDisplay(_INTL("{1} became fully charged due to its bond with its Trainer!",user.pbThis)) + @battle.pbShowAbilitySplash(user,true) + @battle.pbHideAbilitySplash(user) + user.pbChangeForm(1,_INTL("{1} became Ash-Greninja!",user.pbThis)) + end + end + end + # Consume user's Gem + if user.effects[PBEffects::GemConsumed]>0 + # NOTE: The consume animation and message for Gems are shown immediately + # after the move's animation, but the item is only consumed now. + user.pbConsumeItem + end + # Pokémon switching caused by Roar, Whirlwind, Circle Throw, Dragon Tail + switchedBattlers = [] + move.pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers) + # Target's item, user's item, target's ability (all negated by Sheer Force) + if move.addlEffect==0 || !user.hasActiveAbility?(:SHEERFORCE) + pbEffectsAfterMove2(user,targets,move,numHits,switchedBattlers) + end + # Some move effects that need to happen here, i.e. U-turn/Volt Switch + # switching, Baton Pass switching, Parting Shot switching, Relic Song's form + # changing, Fling/Natural Gift consuming item. + if !switchedBattlers.include?(user.index) + move.pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + end + if numHits>0 + @battle.eachBattler { |b| b.pbItemEndOfMoveCheck } + end + end + + # Everything in this method is negated by Sheer Force. + def pbEffectsAfterMove2(user,targets,move,numHits,switchedBattlers) + hpNow = user.hp # Intentionally determined now, before Shell Bell + # Target's held item (Eject Button, Red Card) + switchByItem = [] + @battle.pbPriority(true).each do |b| + next if !targets.any? { |targetB| targetB.index==b.index } + next if b.damageState.unaffected || b.damageState.calcDamage==0 || + switchedBattlers.include?(b.index) + next if !b.itemActive? + BattleHandlers.triggerTargetItemAfterMoveUse(b.item,b,user,move,switchByItem,@battle) + end + @battle.moldBreaker = false if switchByItem.include?(user.index) + @battle.pbPriority(true).each do |b| + b.pbEffectsOnSwitchIn(true) if switchByItem.include?(b.index) + end + switchByItem.each { |idxB| switchedBattlers.push(idxB) } + # User's held item (Life Orb, Shell Bell) + if !switchedBattlers.include?(user.index) && user.itemActive? + BattleHandlers.triggerUserItemAfterMoveUse(user.item,user,targets,move,numHits,@battle) + end + # Target's ability (Berserk, Color Change, Emergency Exit, Pickpocket, Wimp Out) + switchWimpOut = [] + @battle.pbPriority(true).each do |b| + next if !targets.any? { |targetB| targetB.index==b.index } + next if b.damageState.unaffected || switchedBattlers.include?(b.index) + next if !b.abilityActive? + BattleHandlers.triggerTargetAbilityAfterMoveUse(b.ability,b,user,move,switchedBattlers,@battle) + if !switchedBattlers.include?(b.index) && move.damagingMove? + if b.pbAbilitiesOnDamageTaken(b.damageState.initialHP) # Emergency Exit, Wimp Out + switchWimpOut.push(b.index) + end + end + end + @battle.moldBreaker = false if switchWimpOut.include?(user.index) + @battle.pbPriority(true).each do |b| + next if b.index==user.index + b.pbEffectsOnSwitchIn(true) if switchWimpOut.include?(b.index) + end + switchWimpOut.each { |idxB| switchedBattlers.push(idxB) } + # User's ability (Emergency Exit, Wimp Out) + if !switchedBattlers.include?(user.index) && move.damagingMove? + hpNow = user.hp if user.hp0 # Usually undefined + return @realMove.totalpp if @realMove + return 0 + end + + # NOTE: This method is only ever called while using a move (and also by the + # AI), so using @calcType here is acceptable. + def physicalMove?(thisType=nil) + return (@category==0) if MOVE_CATEGORY_PER_MOVE + thisType ||= @calcType if @calcType>=0 + thisType = @type if !thisType + return !PBTypes.isSpecialType?(thisType) + end + + # NOTE: This method is only ever called while using a move (and also by the + # AI), so using @calcType here is acceptable. + def specialMove?(thisType=nil) + return (@category==1) if MOVE_CATEGORY_PER_MOVE + thisType ||= @calcType if @calcType>=0 + thisType = @type if !thisType + return PBTypes.isSpecialType?(thisType) + end + + def damagingMove?; return @category!=2; end + def statusMove?; return @category==2; end + + def usableWhenAsleep?; return false; end + def unusableInGravity?; return false; end + def healingMove?; return false; end + def recoilMove?; return false; end + def flinchingMove?; return false; end + def callsAnotherMove?; return false; end + # Whether the move can/will hit more than once in the same turn (including + # Beat Up which may instead hit just once). Not the same as pbNumHits>1. + def multiHitMove?; return false; end + def chargingTurnMove?; return false; end + def successCheckPerHit?; return false; end + def hitsFlyingTargets?; return false; end + def hitsDiggingTargets?; return false; end + def hitsDivingTargets?; return false; end + def ignoresReflect?; return false; end # For Brick Break + def cannotRedirect?; return false; end # For Future Sight/Doom Desire + def worksWithNoTargets?; return false; end # For Explosion + def damageReducedByBurn?; return true; end # For Facade + def triggersHyperMode?; return false; end + + def contactMove?; return @flags[/a/]; end + def canProtectAgainst?; return @flags[/b/]; end + def canMagicCoat?; return @flags[/c/]; end + def canSnatch?; return @flags[/d/]; end + def canMirrorMove?; return @flags[/e/]; end + def canKingsRock?; return @flags[/f/]; end + def thawsUser?; return @flags[/g/]; end + def highCriticalRate?; return @flags[/h/]; end + def bitingMove?; return @flags[/i/]; end + def punchingMove?; return @flags[/j/]; end + def soundMove?; return @flags[/k/]; end + def powderMove?; return @flags[/l/]; end + def pulseMove?; return @flags[/m/]; end + def bombMove?; return @flags[/n/]; end + def danceMove?; return @flags[/o/]; end + + # Causes perfect accuracy (param=1) and double damage (param=2). + def tramplesMinimize?(param=1); return false; end + def nonLethal?(user,target); return false; end # For False Swipe + + def ignoresSubstitute?(user) # user is the Pokémon using this move + if NEWEST_BATTLE_MECHANICS + return true if soundMove? + return true if user && user.hasActiveAbility?(:INFILTRATOR) + end + return false + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/002_Move_Usage.rb b/Data/Scripts/011_Battle/002_Move/002_Move_Usage.rb new file mode 100644 index 000000000..59238a99c --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/002_Move_Usage.rb @@ -0,0 +1,350 @@ +class PokeBattle_Move + #============================================================================= + # Effect methods per move usage + #============================================================================= + def pbCanChooseMove?(user,commandPhase,showMessages); return true; end # For Belch + def pbDisplayChargeMessage(user); end # For Focus Punch/shell Trap/Beak Blast + def pbOnStartUse(user,targets); end + def pbAddTarget(targets,user); end # For Counter, etc. and Bide + + # Reset move usage counters (child classes can increment them). + def pbChangeUsageCounters(user,specialUsage) + user.effects[PBEffects::FuryCutter] = 0 + user.effects[PBEffects::ParentalBond] = 0 + user.effects[PBEffects::ProtectRate] = 1 + @battle.field.effects[PBEffects::FusionBolt] = false + @battle.field.effects[PBEffects::FusionFlare] = false + end + + def pbDisplayUseMessage(user) + @battle.pbDisplayBrief(_INTL("{1} used {2}!",user.pbThis,@name)) + end + + def pbMissMessage(user,target); return false; end + + #============================================================================= + # + #============================================================================= + # Whether the move is currently in the "charging" turn of a two turn attack. + # Is false if Power Herb or another effect lets a two turn move charge and + # attack in the same turn. + # user.effects[PBEffects::TwoTurnAttack] is set to the move's ID during the + # charging turn, and is 0 during the attack turn. + def pbIsChargingTurn?(user); return false; end + def pbDamagingMove?; return damagingMove?; end + + def pbContactMove?(user) + return false if user.hasActiveAbility?(:LONGREACH) + return contactMove? + end + + # The maximum number of hits in a round this move will actually perform. This + # can be 1 for Beat Up, and can be 2 for any moves affected by Parental Bond. + def pbNumHits(user,targets) + if user.hasActiveAbility?(:PARENTALBOND) && pbDamagingMove? && + !chargingTurnMove? && targets.length==1 + # Record that Parental Bond applies, to weaken the second attack + user.effects[PBEffects::ParentalBond] = 3 + return 2 + end + return 1 + end + + #============================================================================= + # Effect methods per hit + #============================================================================= + def pbOverrideSuccessCheckPerHit(user,target); return false; end + def pbCrashDamage(user); end + def pbInitialEffect(user,targets,hitNum); end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if !showAnimation + if user.effects[PBEffects::ParentalBond]==1 + @battle.pbCommonAnimation("ParentalBond",user,targets) + else + @battle.pbAnimation(id,user,targets,hitNum) + end + end + + def pbSelfKO(user); end + def pbEffectWhenDealingDamage(user,target); end + def pbEffectAgainstTarget(user,target); end + def pbEffectGeneral(user); end + def pbAdditionalEffect(user,target); end + def pbEffectAfterAllHits(user,target); end # Move effects that occur after all hits + def pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers); end + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers); end + + #============================================================================= + # Check if target is immune to the move because of its ability + #============================================================================= + def pbImmunityByAbility(user,target) + return false if @battle.moldBreaker + ret = false + if target.abilityActive? + ret = BattleHandlers.triggerMoveImmunityTargetAbility(target.ability, + user,target,self,@calcType,@battle) + end + return ret + end + + #============================================================================= + # Move failure checks + #============================================================================= + # Check whether the move fails completely due to move-specific requirements. + def pbMoveFailed?(user,targets); return false; end + # Checks whether the move will be ineffective against the target. + def pbFailsAgainstTarget?(user,target); return false; end + + def pbMoveFailedLastInRound?(user) + unmoved = false + @battle.eachBattler do |b| + next if b.index==user.index + next if @battle.choices[b.index][0]!=:UseMove && @battle.choices[b.index][0]!=:Shift + next if b.movedThisRound? + unmoved = true + break + end + if !unmoved + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbMoveFailedTargetAlreadyMoved?(target) + if (@battle.choices[target.index][0]!=:UseMove && + @battle.choices[target.index][0]!=:Shift) || target.movedThisRound? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbMoveFailedAromaVeil?(user,target,showMessage=true) + return false if @battle.moldBreaker + if target.hasActiveAbility?(:AROMAVEIL) + if showMessage + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!", + target.pbThis,target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + end + return true + end + target.eachAlly do |b| + next if !b.hasActiveAbility?(:AROMAVEIL) + if showMessage + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} is unaffected because of {2}'s {3}!", + target.pbThis,b.pbThis(true),b.abilityName)) + end + @battle.pbHideAbilitySplash(target) + end + return true + end + return false + end + + #============================================================================= + # Weaken the damage dealt (doesn't actually change a battler's HP) + #============================================================================= + def pbCheckDamageAbsorption(user,target) + # Substitute will take the damage + if target.effects[PBEffects::Substitute]>0 && !ignoresSubstitute?(user) && + (!user || user.index!=target.index) + target.damageState.substitute = true + return + end + # Disguise will take the damage + if !@battle.moldBreaker && isConst?(target.species,PBSpecies,:MIMIKYU) && + target.form==0 && isConst?(target.ability,PBAbilities,:DISGUISE) + target.damageState.disguise = true + return + end + end + + def pbReduceDamage(user,target) + damage = target.damageState.calcDamage + # Substitute takes the damage + if target.damageState.substitute + damage = target.effects[PBEffects::Substitute] if damage>target.effects[PBEffects::Substitute] + target.damageState.hpLost = damage + target.damageState.totalHPLost += damage + return + end + # Disguise takes the damage + return if target.damageState.disguise + # Target takes the damage + if damage>=target.hp + damage = target.hp + # Survive a lethal hit with 1 HP effects + if nonLethal?(user,target) + damage -= 1 + elsif target.effects[PBEffects::Endure] + target.damageState.endured = true + damage -= 1 + elsif damage==target.totalhp + if target.hasActiveAbility?(:STURDY) && !@battle.moldBreaker + target.damageState.sturdy = true + damage -= 1 + elsif target.hasActiveItem?(:FOCUSSASH) && target.hp==target.totalhp + target.damageState.focusSash = true + damage -= 1 + elsif target.hasActiveItem?(:FOCUSBAND) && @battle.pbRandom(100)<10 + target.damageState.focusBand = true + damage -= 1 + end + end + end + damage = 0 if damage<0 + target.damageState.hpLost = damage + target.damageState.totalHPLost += damage + end + + #============================================================================= + # Change the target's HP by the amount calculated above + #============================================================================= + def pbInflictHPDamage(target) + if target.damageState.substitute + target.effects[PBEffects::Substitute] -= target.damageState.hpLost + else + target.hp -= target.damageState.hpLost + end + end + + #============================================================================= + # Animate the damage dealt, including lowering the HP + #============================================================================= + # Animate being damaged and losing HP (by a move) + def pbAnimateHitAndHPLost(user,targets) + # Animate allies first, then foes + animArray = [] + for side in 0...2 # side here means "allies first, then foes" + targets.each do |b| + next if b.damageState.unaffected || b.damageState.hpLost==0 + next if (side==0 && b.opposes?(user)) || (side==1 && !b.opposes?(user)) + oldHP = b.hp+b.damageState.hpLost + PBDebug.log("[Move damage] #{b.pbThis} lost #{b.damageState.hpLost} HP (#{oldHP}=>#{b.hp})") + effectiveness = 0 + if PBTypes.resistant?(b.damageState.typeMod); effectiveness = 1 + elsif PBTypes.superEffective?(b.damageState.typeMod); effectiveness = 2 + end + animArray.push([b,oldHP,effectiveness]) + end + if animArray.length>0 + @battle.scene.pbHitAndHPLossAnimation(animArray) + animArray.clear + end + end + end + + #============================================================================= + # Messages upon being hit + #============================================================================= + def pbEffectivenessMessage(user,target,numTargets=1) + return if target.damageState.disguise + if PBTypes.superEffective?(target.damageState.typeMod) + if numTargets>1 + @battle.pbDisplay(_INTL("It's super effective on {1}!",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("It's super effective!")) + end + elsif PBTypes.notVeryEffective?(target.damageState.typeMod) + if numTargets>1 + @battle.pbDisplay(_INTL("It's not very effective on {1}...",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("It's not very effective...")) + end + end + end + + def pbHitEffectivenessMessages(user,target,numTargets=1) + return if target.damageState.disguise + if target.damageState.substitute + @battle.pbDisplay(_INTL("The substitute took damage for {1}!",target.pbThis(true))) + end + if target.damageState.critical + if numTargets>1 + @battle.pbDisplay(_INTL("A critical hit on {1}!",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("A critical hit!")) + end + end + # Effectiveness message, for moves with 1 hit + if !multiHitMove? && user.effects[PBEffects::ParentalBond]==0 + pbEffectivenessMessage(user,target,numTargets) + end + if target.damageState.substitute && target.effects[PBEffects::Substitute]==0 + target.effects[PBEffects::Substitute] = 0 + @battle.pbDisplay(_INTL("{1}'s substitute faded!",target.pbThis)) + end + end + + def pbEndureKOMessage(target) + if target.damageState.disguise + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("Its disguise served it as a decoy!")) + else + @battle.pbDisplay(_INTL("{1}'s disguise served it as a decoy!",target.pbThis)) + end + @battle.pbHideAbilitySplash(target) + target.pbChangeForm(1,_INTL("{1}'s disguise was busted!",target.pbThis)) + elsif target.damageState.endured + @battle.pbDisplay(_INTL("{1} endured the hit!",target.pbThis)) + elsif target.damageState.sturdy + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} endured the hit!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} hung on with Sturdy!",target.pbThis)) + end + @battle.pbHideAbilitySplash(target) + elsif target.damageState.focusSash + @battle.pbCommonAnimation("UseItem",target) + @battle.pbDisplay(_INTL("{1} hung on using its Focus Sash!",target.pbThis)) + target.pbConsumeItem + elsif target.damageState.focusBand + @battle.pbCommonAnimation("UseItem",target) + @battle.pbDisplay(_INTL("{1} hung on using its Focus Band!",target.pbThis)) + end + end + + # Used by Counter/Mirror Coat/Metal Burst/Revenge/Focus Punch/Bide/Assurance. + def pbRecordDamageLost(user,target) + damage = target.damageState.hpLost + # NOTE: In Gen 3 where a move's category depends on its type, Hidden Power + # is for some reason countered by Counter rather than Mirror Coat, + # regardless of its calculated type. Hence the following two lines of + # code. + moveType = nil + moveType = getID(PBTypes,:NORMAL) if @function=="090" # Hidden Power + if physicalMove?(moveType) + target.effects[PBEffects::Counter] = damage + target.effects[PBEffects::CounterTarget] = user.index + elsif specialMove?(moveType) + target.effects[PBEffects::MirrorCoat] = damage + target.effects[PBEffects::MirrorCoatTarget] = user.index + end + if target.effects[PBEffects::Bide]>0 + target.effects[PBEffects::BideDamage] += damage + target.effects[PBEffects::BideTarget] = user.index + end + target.damageState.fainted = true if target.fainted? + target.lastHPLost = damage # For Focus Punch + target.tookDamage = true if damage>0 # For Assurance + target.lastAttacker.push(user.index) # For Revenge + if target.opposes?(user) + target.lastHPLostFromFoe = damage # For Metal Burst + target.lastFoeAttacker.push(user.index) # For Metal Burst + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/003_Move_Usage_Calculations.rb b/Data/Scripts/011_Battle/002_Move/003_Move_Usage_Calculations.rb new file mode 100644 index 000000000..497e7322b --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/003_Move_Usage_Calculations.rb @@ -0,0 +1,491 @@ +class PokeBattle_Move + #============================================================================= + # Move's type calculation + #============================================================================= + def pbBaseType(user) + ret = @type + return ret if !ret || ret<0 + if user.abilityActive? + ret = BattleHandlers.triggerMoveBaseTypeModifierAbility(user.ability,user,self,ret) + end + return ret + end + + def pbCalcType(user) + @powerBoost = false + ret = pbBaseType(user) + return ret if !ret || ret<0 + if hasConst?(PBTypes,:ELECTRIC) + if @battle.field.effects[PBEffects::IonDeluge] && isConst?(ret,PBTypes,:NORMAL) + ret = getConst(PBTypes,:ELECTRIC) + @powerBoost = false + end + if user.effects[PBEffects::Electrify] + ret = getConst(PBTypes,:ELECTRIC) + @powerBoost = false + end + end + return ret + end + + #============================================================================= + # Type effectiveness calculation + #============================================================================= + def pbCalcTypeModSingle(moveType,defType,user,target) + ret = PBTypes.getEffectiveness(moveType,defType) + # Ring Target + if target.hasActiveItem?(:RINGTARGET) + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if PBTypes.ineffective?(moveType,defType) + end + # Foresight + if user.hasActiveAbility?(:SCRAPPY) || target.effects[PBEffects::Foresight] + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:GHOST) && + PBTypes.ineffective?(moveType,defType) + end + # Miracle Eye + if target.effects[PBEffects::MiracleEye] + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:DARK) && + PBTypes.ineffective?(moveType,defType) + end + # Delta Stream's weather + if @battle.pbWeather==PBWeather::StrongWinds + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) && + PBTypes.superEffective?(moveType,defType) + end + # Grounded Flying-type Pokémon become susceptible to Ground moves + if !target.airborne? + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) && + isConst?(moveType,PBTypes,:GROUND) + end + return ret + end + + def pbCalcTypeMod(moveType,user,target) + return PBTypeEffectiveness::NORMAL_EFFECTIVE if moveType<0 + return PBTypeEffectiveness::NORMAL_EFFECTIVE if isConst?(moveType,PBTypes,:GROUND) && + target.pbHasType?(:FLYING) && target.hasActiveItem?(:IRONBALL) + # Determine types + tTypes = target.pbTypes(true) + # Get effectivenesses + typeMods = [PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE] * 3 # 3 types max + tTypes.each_with_index do |type,i| + typeMods[i] = pbCalcTypeModSingle(moveType,type,user,target) + end + # Multiply all effectivenesses together + ret = 1 + typeMods.each { |m| ret *= m } + return ret + end + + #============================================================================= + # Accuracy check + #============================================================================= + def pbBaseAccuracy(user,target); return @accuracy; end + + # Accuracy calculations for one-hit KO moves and "always hit" moves are + # handled elsewhere. + def pbAccuracyCheck(user,target) + # "Always hit" effects and "always hit" accuracy + return true if target.effects[PBEffects::Telekinesis]>0 + return true if target.effects[PBEffects::Minimize] && tramplesMinimize?(1) + baseAcc = pbBaseAccuracy(user,target) + return true if baseAcc==0 + # Calculate all multiplier effects + modifiers = [] + modifiers[BASE_ACC] = baseAcc + modifiers[ACC_STAGE] = user.stages[PBStats::ACCURACY] + modifiers[EVA_STAGE] = target.stages[PBStats::EVASION] + modifiers[ACC_MULT] = 0x1000 + modifiers[EVA_MULT] = 0x1000 + pbCalcAccuracyModifiers(user,target,modifiers) + # Check if move can't miss + return true if modifiers[BASE_ACC]==0 + # Calculation + accStage = [[modifiers[ACC_STAGE],-6].max,6].min + 6 + evaStage = [[modifiers[EVA_STAGE],-6].max,6].min + 6 + stageMul = [3,3,3,3,3,3, 3, 4,5,6,7,8,9] + stageDiv = [9,8,7,6,5,4, 3, 3,3,3,3,3,3] + accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage] + evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage] + accuracy = (accuracy * modifiers[ACC_MULT] / 0x1000).round + evasion = (evasion * modifiers[EVA_MULT] / 0x1000).round + evasion = 1 if evasion<1 + # Calculation + return @battle.pbRandom(100) < modifiers[BASE_ACC] * accuracy / evasion + end + + def pbCalcAccuracyModifiers(user,target,modifiers) + # Ability effects that alter accuracy calculation + if user.abilityActive? + BattleHandlers.triggerAccuracyCalcUserAbility(user.ability, + modifiers,user,target,self,@calcType) + end + user.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerAccuracyCalcUserAllyAbility(b.ability, + modifiers,user,target,self,@calcType) + end + if target.abilityActive? && !@battle.moldBreaker + BattleHandlers.triggerAccuracyCalcTargetAbility(target.ability, + modifiers,user,target,self,@calcType) + end + # Item effects that alter accuracy calculation + if user.itemActive? + BattleHandlers.triggerAccuracyCalcUserItem(user.item, + modifiers,user,target,self,@calcType) + end + if target.itemActive? + BattleHandlers.triggerAccuracyCalcTargetItem(target.item, + modifiers,user,target,self,@calcType) + end + # Other effects, inc. ones that set ACC_MULT or EVA_STAGE to specific values + if @battle.field.effects[PBEffects::Gravity]>0 + modifiers[ACC_MULT] = (modifiers[ACC_MULT]*5/3).round + end + if user.effects[PBEffects::MicleBerry] + user.effects[PBEffects::MicleBerry] = false + modifiers[ACC_MULT] = (modifiers[ACC_MULT]*1.2).round + end + modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::Foresight] && modifiers[EVA_STAGE]>0 + modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::MiracleEye] && modifiers[EVA_STAGE]>0 + end + + #============================================================================= + # Critical hit check + #============================================================================= + # Return values: + # -1: Never a critical hit. + # 0: Calculate normally. + # 1: Always a critical hit. + def pbCritialOverride(user,target); return 0; end + + # Returns whether the move will be a critical hit. + def pbIsCritical?(user,target) + return false if target.pbOwnSide.effects[PBEffects::LuckyChant]>0 + # Set up the critical hit ratios + ratios = (NEWEST_BATTLE_MECHANICS) ? [24,8,2,1] : [16,8,4,3,2] + c = 0 + # Ability effects that alter critical hit rate + if c>=0 && user.abilityActive? + c = BattleHandlers.triggerCriticalCalcUserAbility(user.ability,user,target,c) + end + if c>=0 && target.abilityActive? && !@battle.moldBreaker + c = BattleHandlers.triggerCriticalCalcTargetAbility(target.ability,user,target,c) + end + # Item effects that alter critical hit rate + if c>=0 && user.itemActive? + c = BattleHandlers.triggerCriticalCalcUserItem(user.item,user,target,c) + end + if c>=0 && target.itemActive? + c = BattleHandlers.triggerCriticalCalcTargetItem(target.item,user,target,c) + end + return false if c<0 + # Move-specific "always/never a critical hit" effects + case pbCritialOverride(user,target) + when 1; return true + when -1; return false + end + # Other effects + return true if c>50 # Merciless + return true if user.effects[PBEffects::LaserFocus]>0 + c += 1 if highCriticalRate? + c += user.effects[PBEffects::FocusEnergy] + c += 1 if user.inHyperMode? && isConst?(@type,PBTypes,:SHADOW) + c = ratios.length-1 if c>=ratios.length + # Calculation + return @battle.pbRandom(ratios[c])==0 + end + + #============================================================================= + # Damage calculation + #============================================================================= + def pbBaseDamage(baseDmg,user,target); return baseDmg; end + def pbBaseDamageMultiplier(damageMult,user,target); return damageMult; end + def pbModifyDamage(damageMult,user,target); return damageMult; end + + def pbGetAttackStats(user,target) + if specialMove? + return user.spatk, user.stages[PBStats::SPATK]+6 + end + return user.attack, user.stages[PBStats::ATTACK]+6 + end + + def pbGetDefenseStats(user,target) + if specialMove? + return target.spdef, target.stages[PBStats::SPDEF]+6 + end + return target.defense, target.stages[PBStats::DEFENSE]+6 + end + + def pbCalcDamage(user,target,numTargets=1) + return if statusMove? + if target.damageState.disguise + target.damageState.calcDamage = 1 + return + end + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + # Get the move's type + type = @calcType # -1 is treated as physical + # Calculate whether this hit deals critical damage + target.damageState.critical = pbIsCritical?(user,target) + # Calcuate base power of move + baseDmg = pbBaseDamage(@baseDamage,user,target) + # Calculate user's attack stat + atk, atkStage = pbGetAttackStats(user,target) + if !target.hasActiveAbility?(:UNAWARE) || @battle.moldBreaker + atkStage = 6 if target.damageState.critical && atkStage<6 + atk = (atk.to_f*stageMul[atkStage]/stageDiv[atkStage]).floor + end + # Calculate target's defense stat + defense, defStage = pbGetDefenseStats(user,target) + if !user.hasActiveAbility?(:UNAWARE) + defStage = 6 if target.damageState.critical && defStage>6 + defense = (defense.to_f*stageMul[defStage]/stageDiv[defStage]).floor + end + # Calculate all multiplier effects + multipliers = [0x1000,0x1000,0x1000,0x1000] + pbCalcDamageMultipliers(user,target,numTargets,type,baseDmg,multipliers) + # Main damage calculation + baseDmg = [(baseDmg * multipliers[BASE_DMG_MULT] / 0x1000).round,1].max + atk = [(atk * multipliers[ATK_MULT] / 0x1000).round,1].max + defense = [(defense * multipliers[DEF_MULT] / 0x1000).round,1].max + damage = (((2.0*user.level/5+2).floor*baseDmg*atk/defense).floor/50).floor+2 + damage = [(damage * multipliers[FINAL_DMG_MULT] / 0x1000).round,1].max + target.damageState.calcDamage = damage + end + + def pbCalcDamageMultipliers(user,target,numTargets,baseDmg,type,multipliers) + # Global abilities + if (@battle.pbCheckGlobalAbility(:DARKAURA) && isConst?(type,PBTypes,:DARK)) || + (@battle.pbCheckGlobalAbility(:FAIRYAURA) && isConst?(type,PBTypes,:FAIRY)) + if @battle.pbCheckGlobalAbility(:AURABREAK) + multipliers[BASE_DMG_MULT] *= 2/3.0 + else + multipliers[BASE_DMG_MULT] *= 4/3.0 + end + end + # Ability effects that alter damage + if user.abilityActive? + BattleHandlers.triggerDamageCalcUserAbility(user.ability, + user,target,self,multipliers,baseDmg,type) + end + if !@battle.moldBreaker + # NOTE: It's odd that the user's Mold Breaker prevents its partner's + # beneficial abilities (i.e. Flower Gift boosting Atk), but that's + # how it works. + user.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerDamageCalcUserAllyAbility(b.ability, + user,target,self,multipliers,baseDmg,type) + end + if target.abilityActive? + BattleHandlers.triggerDamageCalcTargetAbility(target.ability, + user,target,self,multipliers,baseDmg,type) if !@battle.moldBreaker + BattleHandlers.triggerDamageCalcTargetAbilityNonIgnorable(target.ability, + user,target,self,multipliers,baseDmg,type) + end + target.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerDamageCalcTargetAllyAbility(b.ability, + user,target,self,multipliers,baseDmg,type) + end + end + # Item effects that alter damage + if user.itemActive? + BattleHandlers.triggerDamageCalcUserItem(user.item, + user,target,self,multipliers,baseDmg,type) + end + if target.itemActive? + BattleHandlers.triggerDamageCalcTargetItem(target.item, + user,target,self,multipliers,baseDmg,type) + end + # Parental Bond's second attack + if user.effects[PBEffects::ParentalBond]==1 + multipliers[BASE_DMG_MULT] /= 4 + end + # Other + if user.effects[PBEffects::MeFirst] + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + if user.effects[PBEffects::HelpingHand] && !self.is_a?(PokeBattle_Confusion) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + if user.effects[PBEffects::Charge]>0 && isConst?(type,PBTypes,:ELECTRIC) + multipliers[BASE_DMG_MULT] *= 2 + end + # Mud Sport + if isConst?(type,PBTypes,:ELECTRIC) + @battle.eachBattler do |b| + next if !b.effects[PBEffects::MudSport] + multipliers[BASE_DMG_MULT] /= 3 + break + end + if @battle.field.effects[PBEffects::MudSportField]>0 + multipliers[BASE_DMG_MULT] /= 3 + end + end + # Water Sport + if isConst?(type,PBTypes,:FIRE) + @battle.eachBattler do |b| + next if !b.effects[PBEffects::WaterSport] + multipliers[BASE_DMG_MULT] /= 3 + break + end + if @battle.field.effects[PBEffects::WaterSportField]>0 + multipliers[BASE_DMG_MULT] /= 3 + end + end + # Terrain moves + if user.affectedByTerrain? + case @battle.field.terrain + when PBBattleTerrains::Electric + if isConst?(type,PBTypes,:ELECTRIC) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + when PBBattleTerrains::Grassy + if isConst?(type,PBTypes,:GRASS) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + when PBBattleTerrains::Psychic + if isConst?(type,PBTypes,:PSYCHIC) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + end + end + if @battle.field.terrain==PBBattleTerrains::Misty && target.affectedByTerrain? && + isConst?(type,PBTypes,:DRAGON) + multipliers[BASE_DMG_MULT] /= 2 + end + # Badge multipliers + if @battle.internalBattle + if user.pbOwnedByPlayer? + if physicalMove? && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_ATTACK + multipliers[ATK_MULT] = (multipliers[ATK_MULT]*1.1).round + elsif specialMove? && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_SPATK + multipliers[ATK_MULT] = (multipliers[ATK_MULT]*1.1).round + end + end + if target.pbOwnedByPlayer? + if physicalMove? && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_DEFENSE + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round + elsif specialMove? && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_SPDEF + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round + end + end + end + # Multi-targeting attacks + if numTargets>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*0.75).round + end + # Weather + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + if isConst?(type,PBTypes,:FIRE) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + elsif isConst?(type,PBTypes,:WATER) + multipliers[FINAL_DMG_MULT] /= 2 + end + when PBWeather::Rain, PBWeather::HeavyRain + if isConst?(type,PBTypes,:FIRE) + multipliers[FINAL_DMG_MULT] /= 2 + elsif isConst?(type,PBTypes,:WATER) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + end + when PBWeather::Sandstorm + if target.pbHasType?(:ROCK) && specialMove? && @function!="122" # Psyshock + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.5).round + end + end + # Critical hits + if target.damageState.critical + if NEWEST_BATTLE_MECHANICS + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + else + multipliers[FINAL_DMG_MULT] *= 2 + end + end + # Random variance + if !self.is_a?(PokeBattle_Confusion) + random = 85+@battle.pbRandom(16) + multipliers[FINAL_DMG_MULT] *= random/100.0 + end + # STAB + if type>=0 && user.pbHasType?(type) + if user.hasActiveAbility?(:ADAPTABILITY) + multipliers[FINAL_DMG_MULT] *= 2 + else + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + end + end + # Type effectiveness + multipliers[FINAL_DMG_MULT] *= target.damageState.typeMod.to_f/PBTypeEffectiveness::NORMAL_EFFECTIVE + multipliers[FINAL_DMG_MULT] = multipliers[FINAL_DMG_MULT].round + # Burn + if user.status==PBStatuses::BURN && physicalMove? && damageReducedByBurn? && + !user.hasActiveAbility?(:GUTS) + multipliers[FINAL_DMG_MULT] /= 2 + end + # Aurora Veil, Reflect, Light Screen + if !ignoresReflect? && !target.damageState.critical && + !user.hasActiveAbility?(:INFILTRATOR) + if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::Reflect]>0 && physicalMove? + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::LightScreen]>0 && specialMove? + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + end + end + # Minimize + if target.effects[PBEffects::Minimize] && tramplesMinimize?(2) + multipliers[FINAL_DMG_MULT] *= 2 + end + # Move-specific base damage modifiers + multipliers[BASE_DMG_MULT] = pbBaseDamageMultiplier(multipliers[BASE_DMG_MULT],user,target) + # Move-specific final damage modifiers + multipliers[FINAL_DMG_MULT] = pbModifyDamage(multipliers[FINAL_DMG_MULT],user,target) + end + + #============================================================================= + # Additional effect chance + #============================================================================= + def pbAdditionalEffectChance(user,target,effectChance=0) + return 0 if target.hasActiveAbility?(:SHIELDDUST) && !@battle.moldBreaker + ret = (effectChance>0) ? effectChance : @addlEffect + if NEWEST_BATTLE_MECHANICS || @function!="0A4" # Secret Power + ret *= 2 if user.hasActiveAbility?(:SERENEGRACE) || + user.pbOwnSide.effects[PBEffects::Rainbow]>0 + end + ret = 100 if $DEBUG && Input.press?(Input::CTRL) + return ret + end + + # NOTE: Flinching caused by a move's effect is applied in that move's code, + # not here. + def pbFlinchChance(user,target) + return 0 if flinchingMove? + return 0 if target.hasActiveAbility?(:SHIELDDUST) && !@battle.moldBreaker + ret = 0 + if user.hasActiveAbility?(:STENCH,true) + ret = 10 + elsif user.hasActiveItem?([:KINGSROCK,:RAZORFANG],true) + ret = 10 + end + ret *= 2 if user.hasActiveAbility?(:SERENEGRACE) || + user.pbOwnSide.effects[PBEffects::Rainbow]>0 + return ret + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/004_Move_Effects_Generic.rb b/Data/Scripts/011_Battle/002_Move/004_Move_Effects_Generic.rb new file mode 100644 index 000000000..c3c02d709 --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/004_Move_Effects_Generic.rb @@ -0,0 +1,714 @@ +#=============================================================================== +# Superclass that handles moves using a non-existent function code. +# Damaging moves just do damage with no additional effect. +# Status moves always fail. +#=============================================================================== +class PokeBattle_UnimplementedMove < PokeBattle_Move + def pbMoveFailed?(user,targets) + if statusMove? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Pseudomove for confusion damage. +#=============================================================================== +class PokeBattle_Confusion < PokeBattle_Move + def initialize(battle,move) + @battle = battle + @realMove = move + @id = 0 + @name = "" + @function = "000" + @baseDamage = 40 + @type = -1 + @category = 0 + @accuracy = 100 + @pp = -1 + @target = 0 + @priority = 0 + @flags = "" + @addlEffect = 0 + @calcType = -1 + @powerBoost = false + @snatched = false + end + + def physicalMove?(thisType=nil); return true; end + def specialMove?(thisType=nil); return false; end + def pbCritialOverride(user,target); return -1; end +end + + + +#=============================================================================== +# Implements the move Struggle. +# For cases where the real move named Struggle is not defined. +#=============================================================================== +class PokeBattle_Struggle < PokeBattle_Move + def initialize(battle,move) + @battle = battle + @realMove = nil # Not associated with a move + @id = (move) ? move.id : -1 # Doesn't work if 0 + @name = (move) ? PBMoves.getName(@id) : _INTL("Struggle") + @function = "002" + @baseDamage = 50 + @type = -1 + @category = 0 + @accuracy = 0 + @pp = -1 + @target = 0 + @priority = 0 + @flags = "" + @addlEffect = 0 + @calcType = -1 + @powerBoost = false + @snatched = false + end + + def physicalMove?(thisType=nil); return true; end + def specialMove?(thisType=nil); return false; end + + def pbEffectAfterAllHits(user,target) + return if target.damageState.unaffected + user.pbReduceHP((user.totalhp/4.0).round,false) + @battle.pbDisplay(_INTL("{1} is damaged by recoil!",user.pbThis)) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Generic status problem-inflicting classes. +#=============================================================================== +class PokeBattle_SleepMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanSleep?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbSleep + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbSleep if target.pbCanSleep?(user,false,self) + end +end + + + +class PokeBattle_PoisonMove < PokeBattle_Move + def initialize(battle,move) + super + @toxic = false + end + + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanPoison?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbPoison(user,nil,@toxic) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbPoison(user,nil,@toxic) if target.pbCanPoison?(user,false,self) + end +end + + + +class PokeBattle_ParalysisMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanParalyze?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbParalyze(user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end +end + + + +class PokeBattle_BurnMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanBurn?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbBurn(user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + end +end + + + +class PokeBattle_FreezeMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanFreeze?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbFreeze + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbFreeze if target.pbCanFreeze?(user,false,self) + end +end + + + +#=============================================================================== +# Other problem-causing classes. +#=============================================================================== +class PokeBattle_FlinchMove < PokeBattle_Move + def flinchingMove?; return true; end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbFlinch(user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbFlinch(user) + end +end + + + +class PokeBattle_ConfuseMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanConfuse?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbConfuse + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + return if !target.pbCanConfuse?(user,false,self) + target.pbConfuse + end +end + + + +#=============================================================================== +# Generic user's stat increase/decrease classes. +#=============================================================================== +class PokeBattle_StatUpMove < PokeBattle_Move + def pbMoveFailed?(user,targets) + return false if damagingMove? + return !user.pbCanRaiseStatStage?(@statUp[0],user,self,true) + end + + def pbEffectGeneral(user) + return if damagingMove? + user.pbRaiseStatStage(@statUp[0],@statUp[1],user) + end + + def pbAdditionalEffect(user,target) + if user.pbCanRaiseStatStage?(@statUp[0],user,self) + user.pbRaiseStatStage(@statUp[0],@statUp[1],user) + end + end +end + + + +class PokeBattle_MultiStatUpMove < PokeBattle_Move + def pbMoveFailed?(user,targets) + return false if damagingMove? + failed = true + for i in 0...@statUp.length/2 + next if !user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("{1}'s stats won't go any higher!",user.pbThis)) + return true + end + return false + end + + def pbEffectGeneral(user) + return if damagingMove? + showAnim = true + for i in 0...@statUp.length/2 + next if !user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + if user.pbRaiseStatStage(@statUp[i*2],@statUp[i*2+1],user,showAnim) + showAnim = false + end + end + end + + def pbAdditionalEffect(user,target) + showAnim = true + for i in 0...@statUp.length/2 + next if !user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + if user.pbRaiseStatStage(@statUp[i*2],@statUp[i*2+1],user,showAnim) + showAnim = false + end + end + end +end + + + +class PokeBattle_StatDownMove < PokeBattle_Move + def pbEffectWhenDealingDamage(user,target) + return if @battle.pbAllFainted?(target.idxOwnSide) + showAnim = true + for i in 0...@statDown.length/2 + next if !user.pbCanLowerStatStage?(@statDown[i*2],user,self) + if user.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# Generic target's stat increase/decrease classes. +#=============================================================================== +class PokeBattle_TargetStatDownMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanLowerStatStage?(@statDown[0],user,self,true) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbLowerStatStage(@statDown[0],@statDown[1],user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + return if !target.pbCanLowerStatStage?(@statDown[0],user,self) + target.pbLowerStatStage(@statDown[0],@statDown[1],user) + end +end + + + +class PokeBattle_TargetMultiStatDownMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + failed = true + for i in 0...@statDown.length/2 + next if !target.pbCanLowerStatStage?(@statDown[i*2],user,self) + failed = false + break + end + if failed + # NOTE: It's a bit of a faff to make sure the appropriate failure message + # is shown here, I know. + canLower = false + if target.hasActiveAbility?(:CONTRARY) && !@battle.moldBreaker + for i in 0...@statDown.length/2 + next if target.statStageAtMax?(@statDown[i*2]) + canLower = true + break + end + @battle.pbDisplay(_INTL("{1}'s stats won't go any higher!",user.pbThis)) if !canLower + else + for i in 0...@statDown.length/2 + next if target.statStageAtMin?(@statDown[i*2]) + canLower = true + break + end + @battle.pbDisplay(_INTL("{1}'s stats won't go any lower!",user.pbThis)) if !canLower + end + if canLower + target.pbCanLowerStatStage?(@statDown[0],user,self,true) + end + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + showAnim = true + for i in 0...@statDown.length/2 + next if !target.pbCanLowerStatStage?(@statDown[i*2],user,self) + if target.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim) + showAnim = false + end + end + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + showAnim = true + for i in 0...@statDown.length/2 + next if !target.pbCanLowerStatStage?(@statDown[i*2],user,self) + if target.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# Fixed damage-inflicting move. +#=============================================================================== +class PokeBattle_FixedDamageMove < PokeBattle_Move + def pbFixedDamage(user,target); return 1; end + + def pbCalcDamage(user,target,numTargets=1) + target.damageState.critical = false + target.damageState.calcDamage = pbFixedDamage(user,target) + target.damageState.calcDamage = 1 if target.damageState.calcDamage<1 + end +end + + + +#=============================================================================== +# Two turn move. +#=============================================================================== +class PokeBattle_TwoTurnMove < PokeBattle_Move + def chargingTurnMove?; return true; end + + # user.effects[PBEffects::TwoTurnAttack] is set to the move's ID if this + # method returns true, or 0 if false. + # Non-zero means the charging turn. 0 means the attacking turn. + def pbIsChargingTurn?(user) + @powerHerb = false + @chargingTurn = false # Assume damaging turn by default + @damagingTurn = true + # 0 at start of charging turn, move's ID at start of damaging turn + if user.effects[PBEffects::TwoTurnAttack]==0 + @powerHerb = user.hasActiveItem?(:POWERHERB) + @chargingTurn = true + @damagingTurn = @powerHerb + end + return !@damagingTurn # Deliberately not "return @chargingTurn" + end + + def pbDamagingMove? # Stops damage being dealt in the first (charging) turn + return false if !@damagingTurn + return super + end + + def pbAccuracyCheck(user,target) + return true if !@damagingTurn + return super + end + + def pbInitialEffect(user,targets,hitNum) + pbChargingTurnMessage(user,targets) if @chargingTurn + if @chargingTurn && @damagingTurn # Move only takes one turn to use + pbShowAnimation(@id,user,targets,1) # Charging anim + targets.each { |b| pbChargingTurnEffect(user,b) } + if @powerHerb + # Moves that would make the user semi-invulnerable will hide the user + # after the charging animation, so the "UseItem" animation shouldn't show + # for it + if !["0C9","0CA","0CB","0CC","0CD","0CE","14D"].include?(@function) + @battle.pbCommonAnimation("UseItem",user) + end + @battle.pbDisplay(_INTL("{1} became fully charged due to its Power Herb!",user.pbThis)) + user.pbConsumeItem + end + end + pbAttackingTurnMessage(user,targets) if @damagingTurn + end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} began charging up!",user.pbThis)) + end + + def pbAttackingTurnMessage(user,targets) + end + + def pbChargingTurnEffect(user,target) + # Skull Bash/Sky Drop are the only two-turn moves with an effect here, and + # the latter just records the target is being Sky Dropped + end + + def pbAttackingTurnEffect(user,target) + end + + def pbEffectAgainstTarget(user,target) + if @damagingTurn; pbAttackingTurnEffect(user,target) + elsif @chargingTurn; pbChargingTurnEffect(user,target) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if @chargingTurn && !@damagingTurn # Charging anim + super + end +end + + + +#=============================================================================== +# Healing move. +#=============================================================================== +class PokeBattle_HealingMove < PokeBattle_Move + def healingMove?; return true; end + def pbHealAmount(user); return 1; end + + def pbMoveFailed?(user,targets) + if user.hp==user.totalhp + @battle.pbDisplay(_INTL("{1}'s HP is full!",user.pbThis)) + return true + end + return false + end + + def pbEffectGeneral(user) + amt = pbHealAmount(user) + user.pbRecoverHP(amt) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",user.pbThis)) + end +end + + + +#=============================================================================== +# Recoil move. +#=============================================================================== +class PokeBattle_RecoilMove < PokeBattle_Move + def recoilMove?; return true; end + def pbRecoilDamage(user,target); return 1; end + + def pbEffectAfterAllHits(user,target) + return if target.damageState.unaffected + return if !user.takesIndirectDamage? + return if user.hasActiveAbility?(:ROCKHEAD) + amt = pbRecoilDamage(user,target) + amt = 1 if amt<1 + user.pbReduceHP(amt,false) + @battle.pbDisplay(_INTL("{1} is damaged by recoil!",user.pbThis)) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Protect move. +#=============================================================================== +class PokeBattle_ProtectMove < PokeBattle_Move + def initialize(battle,move) + super + @sidedEffect = false + end + + def pbChangeUsageCounters(user,specialUsage) + oldVal = user.effects[PBEffects::ProtectRate] + super + user.effects[PBEffects::ProtectRate] = oldVal + end + + def pbMoveFailed?(user,targets) + if @sidedEffect + if user.pbOwnSide.effects[@effect] + user.effects[PBEffects::ProtectRate] = 1 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + elsif user.effects[@effect] + user.effects[PBEffects::ProtectRate] = 1 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if !(@sidedEffect && NEWEST_BATTLE_MECHANICS) && + user.effects[PBEffects::ProtectRate]>1 && + @battle.pbRandom(user.effects[PBEffects::ProtectRate])!=0 + user.effects[PBEffects::ProtectRate] = 1 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if pbMoveFailedLastInRound?(user) + user.effects[PBEffects::ProtectRate] = 1 + return true + end + return false + end + + def pbEffectGeneral(user) + if @sidedEffect + user.pbOwnSide.effects[@effect] = true + else + user.effects[@effect] = true + end + user.effects[PBEffects::ProtectRate] *= (NEWEST_BATTLE_MECHANICS) ? 3 : 2 + pbProtectMessage(user) + end + + def pbProtectMessage(user) + if @sidedEffect + @battle.pbDisplay(_INTL("{1} protected {2}!",@name,user.pbTeam(true))) + else + @battle.pbDisplay(_INTL("{1} protected itself!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Weather-inducing move. +#=============================================================================== +class PokeBattle_WeatherMove < PokeBattle_Move + def initialize(battle,move) + super + @weatherType = PBWeather::None + end + def pbMoveFailed?(user,targets) + case @battle.field.weather + when PBWeather::HarshSun + @battle.pbDisplay(_INTL("The extremely harsh sunlight was not lessened at all!")) + return true + when PBWeather::HeavyRain + @battle.pbDisplay(_INTL("There is no relief from this heavy rain!")) + return true + when PBWeather::StrongWinds + @battle.pbDisplay(_INTL("The mysterious air current blows on regardless!")) + return true + when @weatherType + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartWeather(user,@weatherType,true,false) + end +end + + + +#=============================================================================== +# Pledge move. +#=============================================================================== +class PokeBattle_PledgeMove < PokeBattle_Move + def pbOnStartUse(user,targets) + @pledgeSetup = false; @pledgeCombo = false; @pledgeOtherUser = nil + @comboEffect = nil; @overrideType = nil; @overrideAnim = nil + # Check whether this is the use of a combo move + @combos.each do |i| + next if i[0]!=user.effects[PBEffects::FirstPledge] + @battle.pbDisplay(_INTL("The two moves have become one! It's a combined move!")) + @pledgeCombo = true + @comboEffect = i[1]; @overrideType = i[2]; @overrideAnim = i[3] + break + end + return if @pledgeCombo + # Check whether this is the setup of a combo move + user.eachAlly do |b| + next if @battle.choices[b.index][0]!=:UseMove || b.movedThisRound? + move = @battle.choices[b.index][2] + next if !move || move.id<=0 + @combos.each do |i| + next if i[0]!=move.function + @pledgeSetup = true + @pledgeOtherUser = b + break + end + break if @pledgeSetup + end + end + + def pbDamagingMove? + return false if @pledgeSetup + return super + end + + def pbBaseType(user) + return @overrideType if @overrideType!=nil + return super + end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if @pledgeCombo + return baseDmg + end + + def pbEffectGeneral(user) + user.effects[PBEffects::FirstPledge] = 0 + return if !@pledgeSetup + @battle.pbDisplay(_INTL("{1} is waiting for {2}'s move...", + user.pbThis,@pledgeOtherUser.pbThis(true))) + @pledgeOtherUser.effects[PBEffects::FirstPledge] = @function + @pledgeOtherUser.effects[PBEffects::MoveNext] = true + user.lastMoveFailed = true # Treated as a failure for Stomping Tantrum + end + + def pbEffectAfterAllHits(user,target) + return if !@pledgeCombo + msg = nil; animName = nil + case @comboEffect + when :SeaOfFire # Grass + Fire + if user.pbOpposingSide.effects[PBEffects::SeaOfFire]==0 + user.pbOpposingSide.effects[PBEffects::SeaOfFire] = 4 + msg = _INTL("A sea of fire enveloped {1}!",user.pbOpposingTeam(true)) + animName = (user.opposes?) ? "SeaOfFire" : "SeaOfFireOpp" + end + when :Rainbow # Fire + Water + if user.pbOwnSide.effects[PBEffects::Rainbow]==0 + user.pbOwnSide.effects[PBEffects::Rainbow] = 4 + msg = _INTL("A rainbow appeared in the sky on {1}'s side!",user.pbTeam(true)) + animName = (user.opposes?) ? "RainbowOpp" : "Rainbow" + end + when :Swamp # Water + Grass + if user.pbOpposingSide.effects[PBEffects::Swamp]==0 + user.pbOpposingSide.effects[PBEffects::Swamp] = 4 + msg = _INTL("A swamp enveloped {1}!",user.pbOpposingTeam(true)) + animName = (user.opposes?) ? "Swamp" : "SwampOpp" + end + end + @battle.pbDisplay(msg) if msg + @battle.pbCommonAnimation(animName) if animName + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if @pledgeSetup # No animation for setting up + id = @overrideAnim if @overrideAnim!=nil + return super + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/005_Move_Effects_000-07F.rb b/Data/Scripts/011_Battle/002_Move/005_Move_Effects_000-07F.rb new file mode 100644 index 000000000..e8a489828 --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/005_Move_Effects_000-07F.rb @@ -0,0 +1,2924 @@ +#=============================================================================== +# No additional effect. +#=============================================================================== +class PokeBattle_Move_000 < PokeBattle_Move +end + + + +#=============================================================================== +# Does absolutely nothing. (Splash) +#=============================================================================== +class PokeBattle_Move_001 < PokeBattle_Move + def unusableInGravity?; return true; end + + def pbEffectGeneral(user) + @battle.pbDisplay(_INTL("But nothing happened!")) + end +end + + + +#=============================================================================== +# Struggle, if defined as a move in moves.txt. Typically it won't be. +#=============================================================================== +class PokeBattle_Move_002 < PokeBattle_Struggle +end + + + +#=============================================================================== +# Puts the target to sleep. +#=============================================================================== +class PokeBattle_Move_003 < PokeBattle_SleepMove + def pbMoveFailed?(user,targets) + if NEWEST_BATTLE_MECHANICS && isConst?(@id,PBMoves,:DARKVOID) + if !isConst?(user.species,PBSpecies,:DARKRAI) && + !isConst?(user.effects[PBEffects::TransformSpecies],PBSpecies,:DARKRAI) + @battle.pbDisplay(_INTL("But {1} can't use the move!",user.pbThis)) + return true + end + end + return false + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + return if numHits==0 + return if user.fainted? || user.effects[PBEffects::Transform] + return if !isConst?(@id,PBMoves,:RELICSONG) + return if !isConst?(user.species,PBSpecies,:MELOETTA) + return if user.hasActiveAbility?(:SHEERFORCE) && @addlEffect>0 + newForm = (oldForm+1)%2 + user.pbChangeForm(newForm,_INTL("{1} transformed!",user.pbThis)) + end +end + + + +#=============================================================================== +# Makes the target drowsy; it falls asleep at the end of the next turn. (Yawn) +#=============================================================================== +class PokeBattle_Move_004 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Yawn]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if !target.pbCanSleep?(user,true,self) + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Yawn] = 2 + @battle.pbDisplay(_INTL("{1} made {2} drowsy!",user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# Poisons the target. +#=============================================================================== +class PokeBattle_Move_005 < PokeBattle_PoisonMove +end + + + +#=============================================================================== +# Badly poisons the target. (Poison Fang, Toxic) +#=============================================================================== +class PokeBattle_Move_006 < PokeBattle_PoisonMove + def initialize(battle,move) + super + @toxic = true + end + + def pbOverrideSuccessCheckPerHit(user,target) + return (NEWEST_BATTLE_MECHANICS && statusMove? && user.pbHasType?(:POISON)) + end +end + + + +#=============================================================================== +# Paralyzes the target. +# Thunder Wave: Doesn't affect target if move's type has no effect on it. +# Body Slam: Does double damage and has perfect accuracy if target is Minimized. +#=============================================================================== +class PokeBattle_Move_007 < PokeBattle_ParalysisMove + def tramplesMinimize?(param=1) + # Perfect accuracy and double damage (for Body Slam only) + return NEWEST_BATTLE_MECHANICS if isConst?(@id,PBMoves,:BODYSLAM) + return super + end + + def pbFailsAgainstTarget?(user,target) + if isConst?(@id,PBMoves,:THUNDERWAVE) && PBTypes.ineffective?(target.damageState.typeMod) + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return true + end + return super + end +end + + + +#=============================================================================== +# Paralyzes the target. Accuracy perfect in rain, 50% in sunshine. Hits some +# semi-invulnerable targets. (Thunder) +#=============================================================================== +class PokeBattle_Move_008 < PokeBattle_ParalysisMove + def hitsFlyingTargets?; return true; end + + def pbBaseAccuracy(user,target) + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + return 50 + when PBWeather::Rain, PBWeather::HeavyRain + return 0 + end + return super + end +end + + + +#=============================================================================== +# Paralyzes the target. May cause the target to flinch. (Thunder Fang) +#=============================================================================== +class PokeBattle_Move_009 < PokeBattle_Move + def flinchingMove?; return true; end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + chance = pbAdditionalEffectChance(user,target,10) + return if chance==0 + if @battle.pbRandom(100)1 || user.lastRoundMoved>=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Confuses the target. +#=============================================================================== +class PokeBattle_Move_013 < PokeBattle_ConfuseMove +end + + + +#=============================================================================== +# Confuses the target. Chance of causing confusion depends on the cry's volume. +# Confusion chance is 0% if user doesn't have a recorded cry. (Chatter) +#=============================================================================== +class PokeBattle_Move_014 < PokeBattle_ConfuseMove + def pbOnStartUse(user,targets) + @chatterChance = 0 + if user.pokemon && user.pokemon.chatter + # Intensity can be 0-127, so return value is 0-10 + @chatterChance = 10*user.pokemon.chatter.intensity/127 + end + end + + def addlEffect + return @chatterChance if !NEWEST_BATTLE_MECHANICS + return super + end +end + + + +#=============================================================================== +# Confuses the target. Accuracy perfect in rain, 50% in sunshine. Hits some +# semi-invulnerable targets. (Hurricane) +#=============================================================================== +class PokeBattle_Move_015 < PokeBattle_ConfuseMove + def hitsFlyingTargets?; return true; end + + def pbBaseAccuracy(user,target) + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + return 50 + when PBWeather::Rain, PBWeather::HeavyRain + return 0 + end + return super + end +end + + + +#=============================================================================== +# Attracts the target. (Attract) +#=============================================================================== +class PokeBattle_Move_016 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return true if !target.pbCanAttract?(user) + return true if pbMoveFailedAromaVeil?(user,target) + return false + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbAttract(user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbAttract(user) if target.pbCanAttract?(user,false) + end +end + + + +#=============================================================================== +# Burns, freezes or paralyzes the target. (Tri Attack) +#=============================================================================== +class PokeBattle_Move_017 < PokeBattle_Move + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + case @battle.pbRandom(3) + when 0; target.pbBurn(user) if target.pbCanBurn?(user,false,self) + when 1; target.pbFreeze if target.pbCanFreeze?(user,false,self) + when 2; target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end + end +end + + + +#=============================================================================== +# Cures user of burn, poison and paralysis. (Refresh) +#=============================================================================== +class PokeBattle_Move_018 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.status!=PBStatuses::BURN && + user.status!=PBStatuses::POISON && + user.status!=PBStatuses::PARALYSIS + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + t = user.status + user.pbCureStatus(false) + case t + when PBStatuses::BURN + @battle.pbDisplay(_INTL("{1} healed its burn!",user.pbThis)) + when PBStatuses::POISON + @battle.pbDisplay(_INTL("{1} cured its poisoning!",user.pbThis)) + when PBStatuses::PARALYSIS + @battle.pbDisplay(_INTL("{1} cured its paralysis!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Cures all party Pokémon of permanent status problems. (Aromatherapy, Heal Bell) +#=============================================================================== +# NOTE: In Gen 5, this move should have a target of UserSide, while in Gen 6+ it +# should have a target of UserAndAllies. This is because, in Gen 5, this +# move shouldn't call def pbSuccessCheckAgainstTarget for each Pokémon +# currently in battle that will be affected by this move (i.e. allies +# aren't protected by their substitute/ability/etc., but they are in Gen +# 6+). We achieve this by not targeting any battlers in Gen 5, since +# pbSuccessCheckAgainstTarget is only called for targeted battlers. +class PokeBattle_Move_019 < PokeBattle_Move + def worksWithNoTargets?; return true; end + + def pbMoveFailed?(user,targets) + failed = true + @battle.eachSameSideBattler(user) do |b| + next if b.status==PBStatuses::NONE + failed = false + break + end + if !failed + @battle.pbParty(user.index).each do |pkmn| + next if !pkmn || !pkmn.able? || pkmn.status==PBStatuses::NONE + failed = false + break + end + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return target.status==PBStatuses::NONE + end + + def pbAromatherapyHeal(pkmn,battler=nil) + oldStatus = (battler) ? battler.status : pkmn.status + curedName = (battler) ? battler.pbThis : pkmn.name + if battler + battler.pbCureStatus(false) + else + pkmn.status = PBStatuses::NONE + pkmn.statusCount = 0 + end + case oldStatus + when PBStatuses::SLEEP + @battle.pbDisplay(_INTL("{1} was woken from sleep.",curedName)) + when PBStatuses::POISON + @battle.pbDisplay(_INTL("{1} was cured of its poisoning.",curedName)) + when PBStatuses::BURN + @battle.pbDisplay(_INTL("{1}'s burn was healed.",curedName)) + when PBStatuses::PARALYSIS + @battle.pbDisplay(_INTL("{1} was cured of paralysis.",curedName)) + when PBStatuses::FROZEN + @battle.pbDisplay(_INTL("{1} was thawed out.",curedName)) + end + end + + def pbEffectAgainstTarget(user,target) + # Cure all Pokémon in battle on the user's side. + pbAromatherapyHeal(target.pokemon,target) + end + + def pbEffectGeneral(user) + # Cure all Pokémon in battle on the user's side. For the benefit of the Gen + # 5 version of this move, to make Pokémon out in battle get cured first. + if pbTarget(user)!=PBTargets::UserAndAllies + @battle.eachSameSideBattler(user) do |b| + next if b.status==PBStatuses::NONE + pbAromatherapyHeal(b.pokemon,b) + end + end + # Cure all Pokémon in the user's and partner trainer's party. + # NOTE: This intentionally affects the partner trainer's inactive Pokémon + # too. + @battle.pbParty(user.index).each_with_index do |pkmn,i| + next if !pkmn || !pkmn.able? || pkmn.status==PBStatuses::NONE + next if @battle.pbFindBattler(i,user) # Skip Pokémon in battle + pbAromatherapyHeal(pkmn) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + super + if isConst?(@id,PBMoves,:AROMATHERAPY) + @battle.pbDisplay(_INTL("A soothing aroma wafted through the area!")) + elsif isConst?(@id,PBMoves,:HEALBELL) + @battle.pbDisplay(_INTL("A bell chimed!")) + end + end +end + + + +#=============================================================================== +# Safeguards the user's side from being inflicted with status problems. +# (Safeguard) +#=============================================================================== +class PokeBattle_Move_01A < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::Safeguard]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Safeguard] = 5 + @battle.pbDisplay(_INTL("{1} became cloaked in a mystical veil!",user.pbTeam)) + end +end + + + +#=============================================================================== +# User passes its status problem to the target. (Psycho Shift) +#=============================================================================== +class PokeBattle_Move_01B < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.status==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if !target.pbCanInflictStatus?(user.status,user,false,self) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + msg = "" + case user.status + when PBStatuses::SLEEP + target.pbSleep + msg = _INTL("{1} woke up.",user.pbThis) + when PBStatuses::POISON + target.pbPoison(user,nil,user.statusCount!=0) + msg = _INTL("{1} was cured of its poisoning.",user.pbThis) + when PBStatuses::BURN + target.pbBurn(user) + msg = _INTL("{1}'s burn was healed.",user.pbThis) + when PBStatuses::PARALYSIS + target.pbParalyze(user) + msg = _INTL("{1} was cured of paralysis.",user.pbThis) + when PBStatuses::FROZEN + target.pbFreeze + msg = _INTL("{1} was thawed out.",user.pbThis) + end + if msg!="" + user.pbCureStatus(false) + @battle.pbDisplay(msg) + end + end +end + + + +#=============================================================================== +# Increases the user's Attack by 1 stage. +#=============================================================================== +class PokeBattle_Move_01C < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1] + end +end + + + +#=============================================================================== +# Increases the user's Defense by 1 stage. (Harden, Steel Wing, Withdraw) +#=============================================================================== +class PokeBattle_Move_01D < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Increases the user's Defense by 1 stage. User curls up. (Defense Curl) +#=============================================================================== +class PokeBattle_Move_01E < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,1] + end + + def pbEffectGeneral(user) + user.effects[PBEffects::DefenseCurl] = true + super + end +end + + + +#=============================================================================== +# Increases the user's Speed by 1 stage. (Flame Charge) +#=============================================================================== +class PokeBattle_Move_01F < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Increases the user's Special Attack by 1 stage. (Charge Beam, Fiery Dance) +#=============================================================================== +class PokeBattle_Move_020 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,1] + end +end + + + +#=============================================================================== +# Increases the user's Special Defense by 1 stage. +# Charges up user's next attack if it is Electric-type. (Charge) +#=============================================================================== +class PokeBattle_Move_021 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPDEF,1] + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Charge] = 2 + @battle.pbDisplay(_INTL("{1} began charging power!",user.pbThis)) + super + end +end + + + +#=============================================================================== +# Increases the user's evasion by 1 stage. (Double Team) +#=============================================================================== +class PokeBattle_Move_022 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::EVASION,1] + end +end + + + +#=============================================================================== +# Increases the user's critical hit rate. (Focus Energy) +#=============================================================================== +class PokeBattle_Move_023 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::FocusEnergy]>=2 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::FocusEnergy] = 2 + @battle.pbDisplay(_INTL("{1} is getting pumped!",user.pbThis)) + end +end + + + +#=============================================================================== +# Increases the user's Attack and Defense by 1 stage each. (Bulk Up) +#=============================================================================== +class PokeBattle_Move_024 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack, Defense and accuracy by 1 stage each. (Coil) +#=============================================================================== +class PokeBattle_Move_025 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::DEFENSE,1,PBStats::ACCURACY,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack and Speed by 1 stage each. (Dragon Dance) +#=============================================================================== +class PokeBattle_Move_026 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack and Special Attack by 1 stage each. (Work Up) +#=============================================================================== +class PokeBattle_Move_027 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::SPATK,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack and Sp. Attack by 1 stage each. +# In sunny weather, increases are 2 stages each instead. (Growth) +#=============================================================================== +class PokeBattle_Move_028 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::SPATK,1] + end + + def pbOnStartUse(user,targets) + increment = 1 + if @battle.pbWeather==PBWeather::Sun || + @battle.pbWeather==PBWeather::HarshSun + increment = 2 + end + @statUp[1] = @statUp[3] = increment + end +end + + + +#=============================================================================== +# Increases the user's Attack and accuracy by 1 stage each. (Hone Claws) +#=============================================================================== +class PokeBattle_Move_029 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::ACCURACY,1] + end +end + + + +#=============================================================================== +# Increases the user's Defense and Special Defense by 1 stage each. +# (Cosmic Power, Defend Order) +#=============================================================================== +class PokeBattle_Move_02A < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,1,PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Increases the user's Sp. Attack, Sp. Defense and Speed by 1 stage each. +# (Quiver Dance) +#=============================================================================== +class PokeBattle_Move_02B < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,1,PBStats::SPDEF,1,PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Increases the user's Sp. Attack and Sp. Defense by 1 stage each. (Calm Mind) +#=============================================================================== +class PokeBattle_Move_02C < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,1,PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack, Defense, Speed, Special Attack and Special Defense +# by 1 stage each. (Ancient Power, Ominous Wind, Silver Wind) +#=============================================================================== +class PokeBattle_Move_02D < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::DEFENSE,1, + PBStats::SPATK,1,PBStats::SPDEF,1, + PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack by 2 stages. (Swords Dance) +#=============================================================================== +class PokeBattle_Move_02E < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,2] + end +end + + + +#=============================================================================== +# Increases the user's Defense by 2 stages. (Acid Armor, Barrier, Iron Defense) +#=============================================================================== +class PokeBattle_Move_02F < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,2] + end +end + + + +#=============================================================================== +# Increases the user's Speed by 2 stages. (Agility, Rock Polish) +#=============================================================================== +class PokeBattle_Move_030 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPEED,2] + end +end + + + +#=============================================================================== +# Increases the user's Speed by 2 stages. Lowers user's weight by 100kg. +# (Autotomize) +#=============================================================================== +class PokeBattle_Move_031 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPEED,2] + end + + def pbEffectGeneral(user) + if user.pbWeight+user.effects[PBEffects::WeightChange]>1 + user.effects[PBEffects::WeightChange] -= 1000 + @battle.pbDisplay(_INTL("{1} became nimble!",user.pbThis)) + end + super + end +end + + + +#=============================================================================== +# Increases the user's Special Attack by 2 stages. (Nasty Plot) +#=============================================================================== +class PokeBattle_Move_032 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,2] + end +end + + + +#=============================================================================== +# Increases the user's Special Defense by 2 stages. (Amnesia) +#=============================================================================== +class PokeBattle_Move_033 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPDEF,2] + end +end + + + +#=============================================================================== +# Increases the user's evasion by 2 stages. Minimizes the user. (Minimize) +#=============================================================================== +class PokeBattle_Move_034 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::EVASION,2] + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Minimize] = true + super + end +end + + + +#=============================================================================== +# Decreases the user's Defense and Special Defense by 1 stage each. +# Increases the user's Attack, Speed and Special Attack by 2 stages each. +# (Shell Smash) +#=============================================================================== +class PokeBattle_Move_035 < PokeBattle_Move + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,2,PBStats::SPATK,2,PBStats::SPEED,2] + @statDown = [PBStats::DEFENSE,1,PBStats::SPDEF,1] + end + + def pbMoveFailed?(user,targets) + failed = true + for i in 0...@statUp.length/2 + if user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + failed = false; break + end + end + for i in 0...@statDown.length/2 + if user.pbCanLowerStatStage?(@statDown[i*2],user,self) + failed = false; break + end + end + if failed + @battle.pbDisplay(_INTL("{1}'s stats can't be changed further!",user.pbThis)) + return true + end + return false + end + + def pbEffectGeneral(user) + showAnim = true + for i in 0...@statDown.length/2 + next if !user.pbCanLowerStatStage?(@statDown[i*2],user,self) + if user.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim) + showAnim = false + end + end + showAnim = true + for i in 0...@statUp.length/2 + next if !user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + if user.pbRaiseStatStage(@statUp[i*2],@statUp[i*2+1],user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# Increases the user's Speed by 2 stages, and its Attack by 1 stage. (Shift Gear) +#=============================================================================== +class PokeBattle_Move_036 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPEED,2,PBStats::ATTACK,1] + end +end + + + +#=============================================================================== +# Increases one random stat of the target by 2 stages (except HP). (Acupressure) +#=============================================================================== +class PokeBattle_Move_037 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + @statArray = [] + PBStats.eachBattleStat do |s| + @statArray.push(s) if target.pbCanRaiseStatStage?(s,user,self) + end + if @statArray.length==0 + @battle.pbDisplay(_INTL("{1}'s stats won't go any higher!",target.pbThis)) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + stat = @statArray[@battle.pbRandom(@statArray.length)] + target.pbRaiseStatStage(stat,2,user) + end +end + + + +#=============================================================================== +# Increases the user's Defense by 3 stages. (Cotton Guard) +#=============================================================================== +class PokeBattle_Move_038 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,3] + end +end + + + +#=============================================================================== +# Increases the user's Special Attack by 3 stages. (Tail Glow) +#=============================================================================== +class PokeBattle_Move_039 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,3] + end +end + + + +#=============================================================================== +# Reduces the user's HP by half of max, and sets its Attack to maximum. +# (Belly Drum) +#=============================================================================== +class PokeBattle_Move_03A < PokeBattle_Move + def pbMoveFailed?(user,targets) + hpLoss = [user.totalhp/2,1].max + if user.hp<=hpLoss + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if !user.pbCanRaiseStatStage?(PBStats::ATTACK,user,self,true) + return false + end + + def pbEffectGeneral(user) + hpLoss = [user.totalhp/2,1].max + user.pbReduceHP(hpLoss,false) + if user.hasActiveAbility?(:CONTRARY) + user.stages[PBStats::ATTACK] = -6 + @battle.pbCommonAnimation("StatDown",user) + @battle.pbDisplay(_INTL("{1} cut its own HP and minimized its Attack!",user.pbThis)) + else + user.stages[PBStats::ATTACK] = 6 + @battle.pbCommonAnimation("StatUp",user) + @battle.pbDisplay(_INTL("{1} cut its own HP and maximized its Attack!",user.pbThis)) + end + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Decreases the user's Attack and Defense by 1 stage each. (Superpower) +#=============================================================================== +class PokeBattle_Move_03B < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1,PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Decreases the user's Defense and Special Defense by 1 stage each. +# (Close Combat, Dragon Ascent) +#=============================================================================== +class PokeBattle_Move_03C < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,1,PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Decreases the user's Defense, Special Defense and Speed by 1 stage each. +# (V-create) +#=============================================================================== +class PokeBattle_Move_03D < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPEED,1,PBStats::DEFENSE,1,PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Decreases the user's Speed by 1 stage. (Hammer Arm, Ice Hammer) +#=============================================================================== +class PokeBattle_Move_03E < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Decreases the user's Special Attack by 2 stages. +#=============================================================================== +class PokeBattle_Move_03F < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,2] + end +end + + + +#=============================================================================== +# Increases the target's Special Attack by 1 stage. Confuses the target. (Flatter) +#=============================================================================== +class PokeBattle_Move_040 < PokeBattle_Move + def pbMoveFailed?(user,targets) + failed = true + targets.each do |b| + next if !b.pbCanRaiseStatStage?(PBStats::SPATK,user,self) && + !b.pbCanConfuse?(user,false,self) + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + if target.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + target.pbRaiseStatStage(PBStats::SPATK,1,user) + end + target.pbConfuse if target.pbCanConfuse?(user,false,self) + end +end + + + +#=============================================================================== +# Increases the target's Attack by 2 stages. Confuses the target. (Swagger) +#=============================================================================== +class PokeBattle_Move_041 < PokeBattle_Move + def pbMoveFailed?(user,targets) + failed = true + targets.each do |b| + next if !b.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) && + !b.pbCanConfuse?(user,false,self) + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + if target.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + target.pbRaiseStatStage(PBStats::ATTACK,2,user) + end + target.pbConfuse if target.pbCanConfuse?(user,false,self) + end +end + + + +#=============================================================================== +# Decreases the target's Attack by 1 stage. +#=============================================================================== +class PokeBattle_Move_042 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1] + end +end + + +#=============================================================================== +# Decreases the target's Defense by 1 stage. +#=============================================================================== +class PokeBattle_Move_043 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Decreases the target's Speed by 1 stage. +#=============================================================================== +class PokeBattle_Move_044 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPEED,1] + end + + def pbBaseDamage(baseDmg,user,target) + if isConst?(@id,PBMoves,:BULLDOZE) && @battle.field.terrain==PBBattleTerrains::Grassy + baseDmg = (baseDmg/2.0).round + end + return baseDmg + end +end + + + +#=============================================================================== +# Decreases the target's Special Attack by 1 stage. +#=============================================================================== +class PokeBattle_Move_045 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,1] + end +end + + + +#=============================================================================== +# Decreases the target's Special Defense by 1 stage. +#=============================================================================== +class PokeBattle_Move_046 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Decreases the target's accuracy by 1 stage. +#=============================================================================== +class PokeBattle_Move_047 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ACCURACY,1] + end +end + + + +#=============================================================================== +# Decreases the target's evasion by 1 stage OR 2 stages. (Sweet Scent) +#=============================================================================== +class PokeBattle_Move_048 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::EVASION,(NEWEST_BATTLE_MECHANICS) ? 2 : 1] + end +end + + + +#=============================================================================== +# Decreases the target's evasion by 1 stage. Ends all barriers and entry +# hazards for the target's side OR on both sides. (Defog) +#=============================================================================== +class PokeBattle_Move_049 < PokeBattle_TargetStatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::EVASION,1] + end + + def pbFailsAgainstTarget?(user,target) + targetSide = target.pbOwnSide + targetOpposingSide = target.pbOpposingSide + return false if targetSide.effects[PBEffects::AuroraVeil]>0 || + targetSide.effects[PBEffects::LightScreen]>0 || + targetSide.effects[PBEffects::Reflect]>0 || + targetSide.effects[PBEffects::Mist]>0 || + targetSide.effects[PBEffects::Safeguard]>0 + return false if targetSide.effects[PBEffects::StealthRock] || + targetSide.effects[PBEffects::Spikes]>0 || + targetSide.effects[PBEffects::ToxicSpikes]>0 || + targetSide.effects[PBEffects::StickyWeb] + return false if NEWEST_BATTLE_MECHANICS && + (targetOpposingSide.effects[PBEffects::StealthRock] || + targetOpposingSide.effects[PBEffects::Spikes]>0 || + targetOpposingSide.effects[PBEffects::ToxicSpikes]>0 || + targetOpposingSide.effects[PBEffects::StickyWeb]) + return super + end + + def pbEffectAgainstTarget(user,target) + if target.pbCanLowerStatStage?(@statDown[0],user,self) + target.pbLowerStatStage(@statDown[0],@statDown[1],user) + end + if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 + target.pbOwnSide.effects[PBEffects::AuroraVeil] = 0 + @battle.pbDisplay(_INTL("{1}'s Aurora Veil wore off!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::LightScreen]>0 + target.pbOwnSide.effects[PBEffects::LightScreen] = 0 + @battle.pbDisplay(_INTL("{1}'s Light Screen wore off!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::Reflect]>0 + target.pbOwnSide.effects[PBEffects::Reflect] = 0 + @battle.pbDisplay(_INTL("{1}'s Reflect wore off!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::Mist]>0 + target.pbOwnSide.effects[PBEffects::Mist] = 0 + @battle.pbDisplay(_INTL("{1}'s Mist faded!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::Safeguard]>0 + target.pbOwnSide.effects[PBEffects::Safeguard] = 0 + @battle.pbDisplay(_INTL("{1} is no longer protected by Safeguard!!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::StealthRock] || + (NEWEST_BATTLE_MECHANICS && + target.pbOpposingSide.effects[PBEffects::StealthRock]) + target.pbOwnSide.effects[PBEffects::StealthRock] = false + target.pbOpposingSide.effects[PBEffects::StealthRock] = false if NEWEST_BATTLE_MECHANICS + @battle.pbDisplay(_INTL("{1} blew away stealth rocks!",user.pbThis)) + end + if target.pbOwnSide.effects[PBEffects::Spikes]>0 || + (NEWEST_BATTLE_MECHANICS && + target.pbOpposingSide.effects[PBEffects::Spikes]>0) + target.pbOwnSide.effects[PBEffects::Spikes] = 0 + target.pbOpposingSide.effects[PBEffects::Spikes] = 0 if NEWEST_BATTLE_MECHANICS + @battle.pbDisplay(_INTL("{1} blew away spikes!",user.pbThis)) + end + if target.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 || + (NEWEST_BATTLE_MECHANICS && + target.pbOpposingSide.effects[PBEffects::ToxicSpikes]>0) + target.pbOwnSide.effects[PBEffects::ToxicSpikes] = 0 + target.pbOpposingSide.effects[PBEffects::ToxicSpikes] = 0 if NEWEST_BATTLE_MECHANICS + @battle.pbDisplay(_INTL("{1} blew away poison spikes!",user.pbThis)) + end + if target.pbOwnSide.effects[PBEffects::StickyWeb] || + (NEWEST_BATTLE_MECHANICS && + target.pbOpposingSide.effects[PBEffects::StickyWeb]) + target.pbOwnSide.effects[PBEffects::StickyWeb] = false + target.pbOpposingSide.effects[PBEffects::StickyWeb] = false if NEWEST_BATTLE_MECHANICS + @battle.pbDisplay(_INTL("{1} blew away sticky webs!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Decreases the target's Attack and Defense by 1 stage each. (Tickle) +#=============================================================================== +class PokeBattle_Move_04A < PokeBattle_TargetMultiStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1,PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Decreases the target's Attack by 2 stages. (Charm, Feather Dance) +#=============================================================================== +class PokeBattle_Move_04B < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,2] + end +end + + + +#=============================================================================== +# Decreases the target's Defense by 2 stages. (Screech) +#=============================================================================== +class PokeBattle_Move_04C < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,2] + end +end + + + +#=============================================================================== +# Decreases the target's Speed by 2 stages. (Cotton Spore, Scary Face, String Shot) +#=============================================================================== +class PokeBattle_Move_04D < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + inc = 2 + inc = 1 if isConst?(@id,PBMoves,:STRINGSHOT) && !NEWEST_BATTLE_MECHANICS + @statDown = [PBStats::SPEED,inc] + end +end + + + +#=============================================================================== +# Decreases the target's Special Attack by 2 stages. Only works on the opposite +# gender. (Captivate) +#=============================================================================== +class PokeBattle_Move_04E < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,2] + end + + def pbFailsAgainstTarget?(user,target) + return true if super + return false if damagingMove? + if user.gender==2 || target.gender==2 || user.gender==target.gender + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + if target.hasActiveAbility?(:OBLIVIOUS) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} prevents romance!",target.pbThis,target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + return false + end + + def pbAdditionalEffect(user,target) + return if user.gender==2 || target.gender==2 || user.gender==target.gender + return if target.hasActiveAbility?(:OBLIVIOUS) && !@battle.moldBreaker + super + end +end + + + +#=============================================================================== +# Decreases the target's Special Defense by 2 stages. +#=============================================================================== +class PokeBattle_Move_04F < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPDEF,2] + end +end + + + +#=============================================================================== +# Resets all target's stat stages to 0. (Clear Smog) +#=============================================================================== +class PokeBattle_Move_050 < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + if target.damageState.calcDamage>0 && !target.damageState.substitute && + target.hasAlteredStatStages? + target.pbResetStatStages + @battle.pbDisplay(_INTL("{1}'s stat changes were removed!",target.pbThis)) + end + end +end + + + +#=============================================================================== +# Resets all stat stages for all battlers to 0. (Haze) +#=============================================================================== +class PokeBattle_Move_051 < PokeBattle_Move + def pbMoveFailed?(user,targets) + failed = true + @battle.eachBattler do |b| + failed = false if b.hasAlteredStatStages? + break if !failed + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.eachBattler { |b| b.pbResetStatStages } + @battle.pbDisplay(_INTL("All stat changes were eliminated!")) + end +end + + + +#=============================================================================== +# User and target swap their Attack and Special Attack stat stages. (Power Swap) +#=============================================================================== +class PokeBattle_Move_052 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + [PBStats::ATTACK,PBStats::SPATK].each do |s| + user.stages[s],target.stages[s] = target.stages[s],user.stages[s] + end + @battle.pbDisplay(_INTL("{1} switched all changes to its Attack and Sp. Atk with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# User and target swap their Defense and Special Defense stat stages. (Guard Swap) +#=============================================================================== +class PokeBattle_Move_053 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + [PBStats::DEFENSE,PBStats::SPDEF].each do |s| + user.stages[s],target.stages[s] = target.stages[s],user.stages[s] + end + @battle.pbDisplay(_INTL("{1} switched all changes to its Defense and Sp. Def with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# User and target swap all their stat stages. (Heart Swap) +#=============================================================================== +class PokeBattle_Move_054 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + PBStats.eachBattleStat do |s| + user.stages[s],target.stages[s] = target.stages[s],user.stages[s] + end + @battle.pbDisplay(_INTL("{1} switched stat changes with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# User copies the target's stat stages. (Psych Up) +#=============================================================================== +class PokeBattle_Move_055 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + PBStats.eachBattleStat { |s| user.stages[s] = target.stages[s] } + if NEWEST_BATTLE_MECHANICS + user.effects[PBEffects::FocusEnergy] = target.effects[PBEffects::FocusEnergy] + user.effects[PBEffects::LaserFocus] = target.effects[PBEffects::LaserFocus] + end + @battle.pbDisplay(_INTL("{1} copied {2}'s stat changes!",user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# For 5 rounds, user's and ally's stat stages cannot be lowered by foes. (Mist) +#=============================================================================== +class PokeBattle_Move_056 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::Mist]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Mist] = 5 + @battle.pbDisplay(_INTL("{1} became shrouded in mist!",user.pbTeam)) + end +end + + + +#=============================================================================== +# Swaps the user's Attack and Defense stats. (Power Trick) +#=============================================================================== +class PokeBattle_Move_057 < PokeBattle_Move + def pbEffectGeneral(user) + user.attack,user.defense = user.defense,user.attack + user.effects[PBEffects::PowerTrick] = !user.effects[PBEffects::PowerTrick] + @battle.pbDisplay(_INTL("{1} switched its Attack and Defense!",user.pbThis)) + end +end + + + +#=============================================================================== +# Averages the user's and target's Attack. +# Averages the user's and target's Special Attack. (Power Split) +#=============================================================================== +class PokeBattle_Move_058 < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + newatk = ((user.attack+target.attack)/2).floor + newspatk = ((user.spatk+target.spatk)/2).floor + user.attack = target.attack = newatk + user.spatk = target.spatk = newspatk + @battle.pbDisplay(_INTL("{1} shared its power with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# Averages the user's and target's Defense. +# Averages the user's and target's Special Defense. (Guard Split) +#=============================================================================== +class PokeBattle_Move_059 < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + newdef = ((user.defense+target.defense)/2).floor + newspdef = ((user.spdef+target.spdef)/2).floor + user.defense = target.defense = newdef + user.spdef = target.spdef = newspdef + @battle.pbDisplay(_INTL("{1} shared its guard with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# Averages the user's and target's current HP. (Pain Split) +#=============================================================================== +class PokeBattle_Move_05A < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + newHP = (user.hp+target.hp)/2 + if user.hp>newHP; user.pbReduceHP(user.hp-newHP,false,false) + elsif user.hpnewHP; target.pbReduceHP(target.hp-newHP,false,false) + elsif target.hp0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Tailwind] = 4 + @battle.pbDisplay(_INTL("The Tailwind blew from behind {1}!",user.pbTeam(true))) + end +end + + + +#=============================================================================== +# This move turns into the last move used by the target, until user switches +# out. (Mimic) +#=============================================================================== +class PokeBattle_Move_05C < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + "014", # Chatter + "0B6", # Metronome + # Struggle + "002", # Struggle + # Moves that affect the moveset + "05C", # Mimic + "05D", # Sketch + "069" # Transform + ] + end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Transform] || !user.pbHasMove?(@id) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + lastMoveData = pbGetMoveData(target.lastRegularMoveUsed) + if target.lastRegularMoveUsed<=0 || + user.pbHasMove?(target.lastRegularMoveUsed) || + @moveBlacklist.include?(lastMoveData[MOVE_FUNCTION_CODE]) || + isConst?(lastMoveData[MOVE_TYPE],PBTypes,:SHADOW) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.eachMoveWithIndex do |m,i| + next if m.id!=@id + newMove = PBMove.new(target.lastRegularMoveUsed) + user.moves[i] = PokeBattle_Move.pbFromPBMove(@battle,newMove) + @battle.pbDisplay(_INTL("{1} learned {2}!",user.pbThis, + PBMoves.getName(target.lastRegularMoveUsed))) + user.pbCheckFormOnMovesetChange + break + end + end +end + + + +#=============================================================================== +# This move permanently turns into the last move used by the target. (Sketch) +#=============================================================================== +class PokeBattle_Move_05D < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + "014", # Chatter + "05D", # Sketch (this move) + # Struggle + "002" # Struggle + ] + end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Transform] || !user.pbHasMove?(@id) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + lastMoveData = pbGetMoveData(target.lastRegularMoveUsed) + if target.lastRegularMoveUsed<=0 || + user.pbHasMove?(target.lastRegularMoveUsed) || + @moveBlacklist.include?(lastMoveData[MOVE_FUNCTION_CODE]) || + isConst?(lastMoveData[MOVE_TYPE],PBTypes,:SHADOW) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.eachMoveWithIndex do |m,i| + next if m.id!=@id + newMove = PBMove.new(target.lastRegularMoveUsed) + user.pokemon.moves[i] = newMove + user.moves[i] = PokeBattle_Move.pbFromPBMove(@battle,newMove) + @battle.pbDisplay(_INTL("{1} learned {2}!",user.pbThis, + PBMoves.getName(target.lastRegularMoveUsed))) + user.pbCheckFormOnMovesetChange + break + end + end +end + + + +#=============================================================================== +# Changes user's type to that of a random user's move, except a type the user +# already has (even partially), OR changes to the user's first move's type. +# (Conversion) +#=============================================================================== +class PokeBattle_Move_05E < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !user.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + userTypes = user.pbTypes(true) + @newTypes = [] + user.eachMoveWithIndex do |m,i| + break if NEWEST_BATTLE_MECHANICS && i>0 + next if PBTypes.isPseudoType?(m.type) + next if userTypes.include?(m.type) + @newTypes.push(m.type) if !@newTypes.include?(m.type) + end + if @newTypes.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + newType = @newTypes[@battle.pbRandom(@newTypes.length)] + user.pbChangeTypes(newType) + typeName = PBTypes.getName(newType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",user.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Changes user's type to a random one that resists/is immune to the last move +# used by the target. (Conversion 2) +#=============================================================================== +class PokeBattle_Move_05F < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + if !user.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.lastMoveUsed<=0 || + target.lastMoveUsedType<0 || + PBTypes.isPseudoType?(pbGetMoveData(target.lastMoveUsed,MOVE_TYPE)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @newTypes = [] + for i in 0..PBTypes.maxValue + next if PBTypes.isPseudoType?(i) + next if user.pbHasType?(i) + next if !PBTypes.resistant?(target.lastMoveUsedType,i) + @newTypes.push(i) + end + if @newTypes.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + newType = @newTypes[@battle.pbRandom(@newTypes.length)] + user.pbChangeTypes(newType) + typeName = PBTypes.getName(newType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",user.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Changes user's type depending on the environment. (Camouflage) +#=============================================================================== +class PokeBattle_Move_060 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !user.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @newType = getID(PBTypes,:NORMAL) + checkedTerrain = false + case @battle.field.terrain + when PBBattleTerrains::Electric + if hasConst?(PBTypes,:ELECTRIC) + @newType = getID(PBTypes,:ELECTRIC); checkedTerrain = true + end + when PBBattleTerrains::Grassy + if hasConst?(PBTypes,:GRASS) + @newType = getID(PBTypes,:GRASS); checkedTerrain = true + end + when PBBattleTerrains::Misty + if hasConst?(PBTypes,:FAIRY) + @newType = getID(PBTypes,:FAIRY); checkedTerrain = true + end + when PBBattleTerrains::Psychic + if hasConst?(PBTypes,:PSYCHIC) + @newType = getID(PBTypes,:PSYCHIC); checkedTerrain = true + end + end + if !checkedTerrain + case @battle.environment + when PBEnvironment::Grass; @newType = getID(PBTypes,:GRASS) + when PBEnvironment::TallGrass; @newType = getID(PBTypes,:GRASS) + when PBEnvironment::MovingWater; @newType = getID(PBTypes,:WATER) + when PBEnvironment::StillWater; @newType = getID(PBTypes,:WATER) + when PBEnvironment::Puddle; @newType = getID(PBTypes,:WATER) + when PBEnvironment::Underwater; @newType = getID(PBTypes,:WATER) + when PBEnvironment::Cave; @newType = getID(PBTypes,:ROCK) + when PBEnvironment::Rock; @newType = getID(PBTypes,:GROUND) + when PBEnvironment::Sand; @newType = getID(PBTypes,:GROUND) + when PBEnvironment::Forest; @newType = getID(PBTypes,:BUG) + when PBEnvironment::ForestGrass; @newType = getID(PBTypes,:BUG) + when PBEnvironment::Snow; @newType = getID(PBTypes,:ICE) + when PBEnvironment::Ice; @newType = getID(PBTypes,:ICE) + when PBEnvironment::Volcano; @newType = getID(PBTypes,:FIRE) + when PBEnvironment::Graveyard; @newType = getID(PBTypes,:GHOST) + when PBEnvironment::Sky; @newType = getID(PBTypes,:FLYING) + when PBEnvironment::Space; @newType = getID(PBTypes,:DRAGON) + when PBEnvironment::UltraSpace; @newType = getID(PBTypes,:PSYCHIC) + end + end + if !user.pbHasOtherType?(@newType) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbChangeTypes(@newType) + typeName = PBTypes.getName(@newType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",user.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Target becomes Water type. (Soak) +#=============================================================================== +class PokeBattle_Move_061 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !target.canChangeType? || + !target.pbHasOtherType?(getConst(PBTypes,:WATER)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + newType = getConst(PBTypes,:WATER) + user.pbChangeTypes(newType) + typeName = PBTypes.getName(newType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",target.pbThis,typeName)) + end +end + + + +#=============================================================================== +# User copes target's types. (Reflect Type) +#=============================================================================== +class PokeBattle_Move_062 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + if !user.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + newTypes = target.pbTypes(true) + if newTypes.length==0 # Target has no type to copy + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if user.pbTypes==target.pbTypes && + user.effects[PBEffects::Type3]==target.effects[PBEffects::Type3] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.pbChangeTypes(target) + @battle.pbDisplay(_INTL("{1}'s type changed to match {2}'s!", + user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# Target's ability becomes Simple. (Simple Beam) +#=============================================================================== +class PokeBattle_Move_063 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklist = [ + :TRUANT, + # This ability + :SIMPLEBEAM, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be replaced +# :FORECAST, # This can be replaced + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbMoveFailed?(user,targets) + if !hasConst?(PBAbilities,:SIMPLE) # Ability isn't defined + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + @abilityBlacklist.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + @battle.pbShowAbilitySplash(target,true,false) + oldAbil = target.ability + target.ability = getConst(PBAbilities,:SIMPLE) + @battle.pbReplaceAbilitySplash(target) + @battle.pbDisplay(_INTL("{1} acquired {2}!",target.pbThis,target.abilityName)) + @battle.pbHideAbilitySplash(target) + target.pbOnAbilityChanged(oldAbil) + end +end + + + +#=============================================================================== +# Target's ability becomes Insomnia. (Worry Seed) +#=============================================================================== +class PokeBattle_Move_064 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklist = [ + :TRUANT, + # This ability + :INSOMNIA, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be replaced +# :FORECAST, # This can be replaced + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbMoveFailed?(user,targets) + if !hasConst?(PBAbilities,:INSOMNIA) # Ability isn't defined + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + @abilityBlacklist.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + @battle.pbShowAbilitySplash(target,true,false) + oldAbil = target.ability + target.ability = getConst(PBAbilities,:INSOMNIA) + @battle.pbReplaceAbilitySplash(target) + @battle.pbDisplay(_INTL("{1} acquired {2}!",target.pbThis,target.abilityName)) + @battle.pbHideAbilitySplash(target) + target.pbOnAbilityChanged(oldAbil) + end +end + + + +#=============================================================================== +# User copies target's ability. (Role Play) +#=============================================================================== +class PokeBattle_Move_065 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @abilityBlacklistUnlosable = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be lost +# :FORECAST, # This can be lost + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + @abilityBlacklistUngainable = [ + # Replaces self with another ability + :POWEROFALCHEMY, + :RECEIVER, + :TRACE, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, + :FLOWERGIFT, + :FORECAST, + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Appearance-changing abilities + :ILLUSION, + :IMPOSTER, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM, + # Abilities that would be overpowered if allowed to be transferred + :WONDERGUARD + ] + end + + def pbMoveFailed?(user,targets) + @abilityBlacklistUnlosable.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.ability==0 || user.ability==target.ability + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUngainable.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + @battle.pbShowAbilitySplash(user,true,false) + oldAbil = user.ability + user.ability = target.ability + @battle.pbReplaceAbilitySplash(user) + @battle.pbDisplay(_INTL("{1} copied {2}'s {3}!", + user.pbThis,target.pbThis(true),target.abilityName)) + @battle.pbHideAbilitySplash(user) + user.pbOnAbilityChanged(oldAbil) + user.pbEffectsOnSwitchIn + end +end + + + +#=============================================================================== +# Target copies user's ability. (Entrainment) +#=============================================================================== +class PokeBattle_Move_066 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklistUnlosable = [ + :TRUANT, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be lost +# :FORECAST, # This can be lost + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + @abilityBlacklistUngainable = [ + # Replaces self with another ability + :POWEROFALCHEMY, + :RECEIVER, + :TRACE, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, + :FLOWERGIFT, + :FORECAST, + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Appearance-changing abilities + :ILLUSION, + :IMPOSTER, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbMoveFailed?(user,targets) + if user.ability==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUngainable.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + @abilityBlacklistUnlosable.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + @battle.pbShowAbilitySplash(target,true,false) + oldAbil = target.ability + target.ability = user.ability + @battle.pbReplaceAbilitySplash(target) + @battle.pbDisplay(_INTL("{1} acquired {2}!",target.pbThis,target.abilityName)) + @battle.pbHideAbilitySplash(target) + target.pbOnAbilityChanged(oldAbil) + target.pbEffectsOnSwitchIn + end +end + + + +#=============================================================================== +# User and target swap abilities. (Skill Swap) +#=============================================================================== +class PokeBattle_Move_067 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @abilityBlacklistUnlosable = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be lost +# :FORECAST, # This can be lost + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + @abilityBlacklistUngainable = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, + :FLOWERGIFT, + :FORECAST, + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Appearance-changing abilities + :ILLUSION, + :IMPOSTER, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM, + # Abilities that would be overpowered if allowed to be transferred + :WONDERGUARD + ] + end + + def pbMoveFailed?(user,targets) + if user.ability==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUnlosable.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUngainable.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.ability==0 || + (user.ability==target.ability && !NEWEST_BATTLE_MECHANICS) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUnlosable.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUngainable.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + if user.opposes?(target) + @battle.pbShowAbilitySplash(user,false,false) + @battle.pbShowAbilitySplash(target,true,false) + end + oldUserAbil = user.ability + oldTargetAbil = target.ability + user.ability = oldTargetAbil + target.ability = oldUserAbil + if user.opposes?(target) + @battle.pbReplaceAbilitySplash(user) + @battle.pbReplaceAbilitySplash(target) + end + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} swapped Abilities with its target!",user.pbThis)) + else + @battle.pbDisplay(_INTL("{1} swapped its {2} Ability with its target's {3} Ability!", + user.pbThis,target.abilityName,user.abilityName)) + end + if user.opposes?(target) + @battle.pbHideAbilitySplash(user) + @battle.pbHideAbilitySplash(target) + end + user.pbOnAbilityChanged(oldUserAbil) + target.pbOnAbilityChanged(oldTargetAbil) + user.pbEffectsOnSwitchIn + target.pbEffectsOnSwitchIn + end +end + + + +#=============================================================================== +# Target's ability is negated. (Gastro Acid) +#=============================================================================== +class PokeBattle_Move_068 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklist = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be negated +# :FORECAST, # This can be negated + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbFailsAgainstTarget?(user,target) + @abilityBlacklist.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::GastroAcid] = true + target.effects[PBEffects::Truant] = false + @battle.pbDisplay(_INTL("{1}'s Ability was suppressed!",target.pbThis)) + target.pbOnAbilityChanged(target.ability) + end +end + + + +#=============================================================================== +# User transforms into the target. (Transform) +#=============================================================================== +class PokeBattle_Move_069 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Transform] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Transform] || + target.effects[PBEffects::Illusion] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.pbTransform(target) + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + super + @battle.scene.pbChangePokemon(user,targets[0].pokemon) + end +end + + + +#=============================================================================== +# Inflicts a fixed 20HP damage. (Sonic Boom) +#=============================================================================== +class PokeBattle_Move_06A < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + return 20 + end +end + + + +#=============================================================================== +# Inflicts a fixed 40HP damage. (Dragon Rage) +#=============================================================================== +class PokeBattle_Move_06B < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + return 40 + end +end + + + +#=============================================================================== +# Halves the target's current HP. (Nature's Madness, Super Fang) +#=============================================================================== +class PokeBattle_Move_06C < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + return (target.hp/2.0).round + end +end + + + +#=============================================================================== +# Inflicts damage equal to the user's level. (Night Shade, Seismic Toss) +#=============================================================================== +class PokeBattle_Move_06D < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + return user.level + end +end + + + +#=============================================================================== +# Inflicts damage to bring the target's HP down to equal the user's HP. (Endeavor) +#=============================================================================== +class PokeBattle_Move_06E < PokeBattle_FixedDamageMove + def pbFailsAgainstTarget?(user,target) + if user.hp>=target.hp + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbNumHits(user,targets); return 1; end + + def pbFixedDamage(user,target) + return target.hp-user.hp + end +end + + + +#=============================================================================== +# Inflicts damage between 0.5 and 1.5 times the user's level. (Psywave) +#=============================================================================== +class PokeBattle_Move_06F < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + min = (user.level/2).floor + max = (user.level*3/2).floor + return min+@battle.pbRandom(max-min+1) + end +end + + + +#=============================================================================== +# OHKO. Accuracy increases by difference between levels of user and target. +#=============================================================================== +class PokeBattle_Move_070 < PokeBattle_FixedDamageMove + def hitsDiggingTargets?; return isConst?(@id,PBMoves,:FISSURE); end + + def pbFailsAgainstTarget?(user,target) + if target.level>user.level + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + if target.hasActiveAbility?(:STURDY) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("But it failed to affect {1}!",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("But it failed to affect {1} because of its {2}!", + target.pbThis(true),target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + if NEWEST_BATTLE_MECHANICS && + isConst?(target.damageState.typeMod,PBTypes,:ICE) && target.pbHasType?(:ICE) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbAccuracyCheck(user,target) + acc = @accuracy+user.level-target.level + acc -= 10 if NEWEST_BATTLE_MECHANICS && isConst?(@id,PBMoves,:SHEERCOLD) && + !user.pbHasType?(:ICE) + return @battle.pbRandom(100)0 + hitAlly.each do |b| + @battle.pbDisplay(_INTL("The bursting flame hit {1}!", + @battle.battlers[b[0]].pbThis(true))) + end + end + switchedAlly = [] + hitAlly.each do |b| + @battle.battlers[b[0]].pbItemHPHealCheck + if @battle.battlers[b[0]].pbAbilitiesOnDamageTaken(b[1]) + switchedAlly.push(@battle.battlers[b[0]]) + end + end + switchedAlly.each { |b| b.pbEffectsOnSwitchIn(true) } + end +end + + + +#=============================================================================== +# Power is doubled if the target is using Dive. Hits some semi-invulnerable +# targets. (Surf) +#=============================================================================== +class PokeBattle_Move_075 < PokeBattle_Move + def hitsDivingTargets?; return true; end + + def pbModifyDamage(damageMult,user,target) + damageMult *= 2 if target.inTwoTurnAttack?("0CB") # Dive + return damageMult + end +end + + + +#=============================================================================== +# Power is doubled if the target is using Dig. Power is halved if Grassy Terrain +# is in effect. Hits some semi-invulnerable targets. (Earthquake) +#=============================================================================== +class PokeBattle_Move_076 < PokeBattle_Move + def hitsDiggingTargets?; return true; end + + def pbModifyDamage(damageMult,user,target) + damageMult *= 2 if target.inTwoTurnAttack?("0CA") # Dig + damageMult = (damageMult/2.0).round if @battle.field.terrain==PBBattleTerrains::Grassy + return damageMult + end +end + + + +#=============================================================================== +# Power is doubled if the target is using Bounce, Fly or Sky Drop. Hits some +# semi-invulnerable targets. (Gust) +#=============================================================================== +class PokeBattle_Move_077 < PokeBattle_Move + def hitsFlyingTargets?; return true; end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if target.inTwoTurnAttack?("0C9","0CC","0CE") || # Fly/Bounce/Sky Drop + target.effects[PBEffects::SkyDrop]>=0 + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the target is using Bounce, Fly or Sky Drop. Hits some +# semi-invulnerable targets. May make the target flinch. (Twister) +#=============================================================================== +class PokeBattle_Move_078 < PokeBattle_FlinchMove + def hitsFlyingTargets?; return true; end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if target.inTwoTurnAttack?("0C9","0CC","0CE") || # Fly/Bounce/Sky Drop + target.effects[PBEffects::SkyDrop]>=0 + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if Fusion Flare has already been used this round. (Fusion Bolt) +#=============================================================================== +class PokeBattle_Move_079 < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + @doublePower = @battle.field.effects[PBEffects::FusionFlare] + super + end + + def pbBaseDamageMultiplier(damageMult,user,target) + damageMult *= 2 if @doublePower + return damageMult + end + + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::FusionBolt] = true + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if (targets.length>0 && targets[0].damageState.critical) || + @doublePower # Charged anim + super + end +end + + + +#=============================================================================== +# Power is doubled if Fusion Bolt has already been used this round. (Fusion Flare) +#=============================================================================== +class PokeBattle_Move_07A < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + @doublePower = @battle.field.effects[PBEffects::FusionBolt] + super + end + + def pbBaseDamageMultiplier(damageMult,user,target) + damageMult *= 2 if @doublePower + return damageMult + end + + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::FusionFlare] = true + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if (targets.length>0 && targets[0].damageState.critical) || + @doublePower # Charged anim + super + end +end + + + +#=============================================================================== +# Power is doubled if the target is poisoned. (Venoshock) +#=============================================================================== +class PokeBattle_Move_07B < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if target.poisoned? && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + baseDmg *= 2 + end + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the target is paralyzed. Cures the target of paralysis. +# (Smelling Salts) +#=============================================================================== +class PokeBattle_Move_07C < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if target.paralyzed? && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + baseDmg *= 2 + end + return baseDmg + end + + def pbEffectAfterAllHits(user,target) + return if target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.status!=PBStatuses::PARALYSIS + target.pbCureStatus + end +end + + + +#=============================================================================== +# Power is doubled if the target is asleep. Wakes the target up. (Wake-Up Slap) +#=============================================================================== +class PokeBattle_Move_07D < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if target.asleep? && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + baseDmg *= 2 + end + return baseDmg + end + + def pbEffectAfterAllHits(user,target) + return if target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.status!=PBStatuses::SLEEP + target.pbCureStatus + end +end + + + +#=============================================================================== +# Power is doubled if the user is burned, poisoned or paralyzed. (Facade) +# Burn's halving of Attack is negated (new mechanics). +#=============================================================================== +class PokeBattle_Move_07E < PokeBattle_Move + def damageReducedByBurn?; return !NEWEST_BATTLE_MECHANICS; end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if user.poisoned? || user.burned? || user.paralyzed? + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the target has a status problem. (Hex) +#=============================================================================== +class PokeBattle_Move_07F < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if target.pbHasAnyStatus? && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + baseDmg *= 2 + end + return baseDmg + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/006_Move_Effects_080-0FF.rb b/Data/Scripts/011_Battle/002_Move/006_Move_Effects_080-0FF.rb new file mode 100644 index 000000000..2b0a07c44 --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/006_Move_Effects_080-0FF.rb @@ -0,0 +1,3746 @@ +#=============================================================================== +# Power is doubled if the target's HP is down to 1/2 or less. (Brine) +#=============================================================================== +class PokeBattle_Move_080 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if target.hp<=target.totalhp/2 + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the user has lost HP due to the target's move this round. +# (Avalanche, Revenge) +#=============================================================================== +class PokeBattle_Move_081 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if user.lastAttacker.include?(target.index) + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the target has already lost HP this round. (Assurance) +#=============================================================================== +class PokeBattle_Move_082 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if target.tookDamage + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if a user's ally has already used this move this round. (Round) +# If an ally is about to use the same move, make it go next, ignoring priority. +#=============================================================================== +class PokeBattle_Move_083 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if user.pbOwnSide.effects[PBEffects::Round] + return baseDmg + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Round] = true + user.eachAlly do |b| + next if @battle.choices[b.index][0]!=:UseMove || b.movedThisRound? + next if @battle.choices[b.index][2].function!=@function + b.effects[PBEffects::MoveNext] = true + b.effects[PBEffects::Quash] = 0 + break + end + end +end + + + +#=============================================================================== +# Power is doubled if the target has already moved this round. (Payback) +#=============================================================================== +class PokeBattle_Move_084 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if @battle.choices[target.index][0]!=:None && + ((@battle.choices[target.index][0]!=:UseMove && + @battle.choices[target.index][0]!=:Shift) || target.movedThisRound?) + baseDmg *= 2 + end + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if a user's teammate fainted last round. (Retaliate) +#=============================================================================== +class PokeBattle_Move_085 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + lrf = user.pbOwnSide.effects[PBEffects::LastRoundFainted] + baseDmg *= 2 if lrf>=0 && lrf==@battle.turnCount-1 + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the user has no held item. (Acrobatics) +#=============================================================================== +class PokeBattle_Move_086 < PokeBattle_Move + def pbBaseDamageMultiplier(damageMult,user,target) + damageMult *= 2 if user.item==0 + return damageMult + end +end + + + +#=============================================================================== +# Power is doubled in weather. Type changes depending on the weather. (Weather Ball) +#=============================================================================== +class PokeBattle_Move_087 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if @battle.pbWeather!=PBWeather::None + return baseDmg + end + + def pbBaseType(user) + ret = getID(PBTypes,:NORMAL) + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + ret = getConst(PBTypes,:FIRE) || ret + when PBWeather::Rain, PBWeather::HeavyRain + ret = getConst(PBTypes,:WATER) || ret + when PBWeather::Sandstorm + ret = getConst(PBTypes,:ROCK) || ret + when PBWeather::Hail + ret = getConst(PBTypes,:ICE) || ret + end + return ret + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + t = pbBaseType(user) + hitNum = 1 if isConst?(t,PBTypes,:FIRE) # Type-specific anims + hitNum = 2 if isConst?(t,PBTypes,:WATER) + hitNum = 3 if isConst?(t,PBTypes,:ROCK) + hitNum = 4 if isConst?(t,PBTypes,:ICE) + super + end +end + + + +#=============================================================================== +# Interrupts a foe switching out or using U-turn/Volt Switch/Parting Shot. Power +# is doubled in that case. (Pursuit) +# (Handled in Battle's pbAttackPhase): Makes this attack happen before switching. +#=============================================================================== +class PokeBattle_Move_088 < PokeBattle_Move + def pbAccuracyCheck(user,target) + return true if @battle.switching + return super + end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if @battle.switching + return baseDmg + end +end + + + +#=============================================================================== +# Power increases with the user's happiness. (Return) +#=============================================================================== +class PokeBattle_Move_089 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [(user.happiness*2/5).floor,1].max + end +end + + + +#=============================================================================== +# Power decreases with the user's happiness. (Frustration) +#=============================================================================== +class PokeBattle_Move_08A < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [((255-user.happiness)*2/5).floor,1].max + end +end + + + +#=============================================================================== +# Power increases with the user's HP. (Eruption, Water Spout) +#=============================================================================== +class PokeBattle_Move_08B < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [150*user.hp/user.totalhp,1].max + end +end + + + +#=============================================================================== +# Power increases with the target's HP. (Crush Grip, Wring Out) +#=============================================================================== +class PokeBattle_Move_08C < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [120*target.hp/target.totalhp,1].max + end +end + + + +#=============================================================================== +# Power increases the quicker the target is than the user. (Gyro Ball) +#=============================================================================== +class PokeBattle_Move_08D < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [[(25*target.pbSpeed/user.pbSpeed).floor,150].min,1].max + end +end + + + +#=============================================================================== +# Power increases with the user's positive stat changes (ignores negative ones). +# (Power Trip, Stored Power) +#=============================================================================== +class PokeBattle_Move_08E < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + mult = 1 + PBStats.eachBattleStat { |s| mult += user.stages[s] if user.stages[s]>0 } + return 20*mult + end +end + + + +#=============================================================================== +# Power increases with the target's positive stat changes (ignores negative ones). +# (Punishment) +#=============================================================================== +class PokeBattle_Move_08F < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + mult = 3 + PBStats.eachBattleStat { |s| mult += target.stages[s] if target.stages[s]>0 } + return [20*mult,200].min + end +end + + + +#=============================================================================== +# Power and type depends on the user's IVs. (Hidden Power) +#=============================================================================== +class PokeBattle_Move_090 < PokeBattle_Move + def pbBaseType(user) + hp = pbHiddenPower(user) + return hp[0] + end + + def pbBaseDamage(baseDmg,user,target) + return super if NEWEST_BATTLE_MECHANICS + hp = pbHiddenPower(user) + return hp[1] + end +end + + + +def pbHiddenPower(pkmn) + # NOTE: This allows Hidden Power to be Fairy-type (if you have that type in + # your game). I don't care that the official games don't work like that. + iv = pkmn.iv + idxType = 0; power = 60 + types = [] + for i in 0..PBTypes.maxValue + next if PBTypes.isPseudoType?(i) + next if isConst?(i,PBTypes,:NORMAL) || isConst?(i,PBTypes,:SHADOW) + types.push(i) + end + idxType |= (iv[PBStats::HP]&1) + idxType |= (iv[PBStats::ATTACK]&1)<<1 + idxType |= (iv[PBStats::DEFENSE]&1)<<2 + idxType |= (iv[PBStats::SPEED]&1)<<3 + idxType |= (iv[PBStats::SPATK]&1)<<4 + idxType |= (iv[PBStats::SPDEF]&1)<<5 + idxType = (types.length-1)*idxType/63 + type = types[idxType] + if !NEWEST_BATTLE_MECHANICS + powerMin = 30 + powerMax = 70 + power |= (iv[PBStats::HP]&2)>>1 + power |= (iv[PBStats::ATTACK]&2) + power |= (iv[PBStats::DEFENSE]&2)<<1 + power |= (iv[PBStats::SPEED]&2)<<2 + power |= (iv[PBStats::SPATK]&2)<<3 + power |= (iv[PBStats::SPDEF]&2)<<4 + power = powerMin+(powerMax-powerMin)*power/63 + end + return [type,power] +end + + + +#=============================================================================== +# Power doubles for each consecutive use. (Fury Cutter) +#=============================================================================== +class PokeBattle_Move_091 < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + oldVal = user.effects[PBEffects::FuryCutter] + super + maxMult = 1 + while (@baseDamage<<(maxMult-1))<160 + maxMult += 1 # 1-4 for base damage of 20, 1-3 for base damage of 40 + end + user.effects[PBEffects::FuryCutter] = (oldVal>=maxMult) ? maxMult : oldVal+1 + end + + def pbBaseDamage(baseDmg,user,target) + return baseDmg<<(user.effects[PBEffects::FuryCutter]-1) + end +end + + + +#=============================================================================== +# Power is multiplied by the number of consecutive rounds in which this move was +# used by any Pokémon on the user's side. (Echoed Voice) +#=============================================================================== +class PokeBattle_Move_092 < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + oldVal = user.pbOwnSide.effects[PBEffects::EchoedVoiceCounter] + super + if !user.pbOwnSide.effects[PBEffects::EchoedVoiceUsed] + user.pbOwnSide.effects[PBEffects::EchoedVoiceCounter] = (oldVal>=5) ? 5 : oldVal+1 + end + user.pbOwnSide.effects[PBEffects::EchoedVoiceUsed] = true + end + + def pbBaseDamage(baseDmg,user,target) + return baseDmg*user.pbOwnSide.effects[PBEffects::EchoedVoiceCounter] # 1-5 + end +end + + + +#=============================================================================== +# User rages until the start of a round in which they don't use this move. (Rage) +# (Handled in Battler's pbProcessMoveAgainstTarget): Ups rager's Attack by 1 +# stage each time it loses HP due to a move. +#=============================================================================== +class PokeBattle_Move_093 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::Rage] = true + end +end + + + +#=============================================================================== +# Randomly damages or heals the target. (Present) +# NOTE: Apparently a Normal Gem should be consumed even if this move will heal, +# but I think that's silly so I've omitted that effect. +#=============================================================================== +class PokeBattle_Move_094 < PokeBattle_Move + def pbOnStartUse(user,targets) + @presentDmg = 0 # 0 = heal, >0 = damage + r = @battle.pbRandom(100) + if r<40; @presentDmg = 40 + elsif r<70; @presentDmg = 80 + elsif r<80; @presentDmg = 120 + end + end + + def pbFailsAgainstTarget?(user,target) + return false if @presentDmg>0 + if !target.canHeal? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbDamagingMove? + return false if @presentDmg==0 + return super + end + + def pbBaseDamage(baseDmg,user,target) + return @presentDmg + end + + def pbEffectAgainstTarget(user,target) + return if @presentDmg>0 + target.pbRecoverHP(target.totalhp/4) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if @presentDmg==0 # Healing anim + super + end +end + + + +#=============================================================================== +# Power is chosen at random. Power is doubled if the target is using Dig. Hits +# some semi-invulnerable targets. (Magnitude) +#=============================================================================== +class PokeBattle_Move_095 < PokeBattle_Move + def hitsDiggingTargets?; return true; end + + def pbOnStartUse(user,targets) + baseDmg = [10,30,50,70,90,110,150] + magnitudes = [ + 4, + 5,5, + 6,6,6,6, + 7,7,7,7,7,7, + 8,8,8,8, + 9,9, + 10 + ] + magni = magnitudes[@battle.pbRandom(magnitudes.length)] + @magnitudeDmg = baseDmg[magni-4] + @battle.pbDisplay(_INTL("Magnitude {1}!",magni)) + end + + def pbBaseDamage(baseDmg,user,target) + return @magnitudeDmg + end + + def pbModifyDamage(damageMult,user,target) + damageMult *= 2 if target.inTwoTurnAttack?("0CA") # Dig + damageMult = (damageMult/2.0).round if @battle.field.terrain==PBBattleTerrains::Grassy + return damageMult + end +end + + + +#=============================================================================== +# Power and type depend on the user's held berry. Destroys the berry. +# (Natural Gift) +#=============================================================================== +class PokeBattle_Move_096 < PokeBattle_Move + def initialize(battle,move) + super + @typeArray = { + :NORMAL => [:CHILANBERRY], + :FIRE => [:CHERIBERRY, :BLUKBERRY, :WATMELBERRY, :OCCABERRY], + :WATER => [:CHESTOBERRY, :NANABBERRY, :DURINBERRY, :PASSHOBERRY], + :ELECTRIC => [:PECHABERRY, :WEPEARBERRY, :BELUEBERRY, :WACANBERRY], + :GRASS => [:RAWSTBERRY, :PINAPBERRY, :RINDOBERRY, :LIECHIBERRY], + :ICE => [:ASPEARBERRY, :POMEGBERRY, :YACHEBERRY, :GANLONBERRY], + :FIGHTING => [:LEPPABERRY, :KELPSYBERRY, :CHOPLEBERRY, :SALACBERRY], + :POISON => [:ORANBERRY, :QUALOTBERRY, :KEBIABERRY, :PETAYABERRY], + :GROUND => [:PERSIMBERRY, :HONDEWBERRY, :SHUCABERRY, :APICOTBERRY], + :FLYING => [:LUMBERRY, :GREPABERRY, :COBABERRY, :LANSATBERRY], + :PSYCHIC => [:SITRUSBERRY, :TAMATOBERRY, :PAYAPABERRY, :STARFBERRY], + :BUG => [:FIGYBERRY, :CORNNBERRY, :TANGABERRY, :ENIGMABERRY], + :ROCK => [:WIKIBERRY, :MAGOSTBERRY, :CHARTIBERRY, :MICLEBERRY], + :GHOST => [:MAGOBERRY, :RABUTABERRY, :KASIBBERRY, :CUSTAPBERRY], + :DRAGON => [:AGUAVBERRY, :NOMELBERRY, :HABANBERRY, :JABOCABERRY], + :DARK => [:IAPAPABERRY, :SPELONBERRY, :COLBURBERRY, :ROWAPBERRY, :MARANGABERRY], + :STEEL => [:RAZZBERRY, :PAMTREBERRY, :BABIRIBERRY], + :FAIRY => [:ROSELIBERRY, :KEEBERRY] + } + @damageArray = { + 60 => [:CHERIBERRY, :CHESTOBERRY, :PECHABERRY, :RAWSTBERRY, :ASPEARBERRY, + :LEPPABERRY, :ORANBERRY, :PERSIMBERRY, :LUMBERRY, :SITRUSBERRY, + :FIGYBERRY, :WIKIBERRY, :MAGOBERRY, :AGUAVBERRY, :IAPAPABERRY, + :RAZZBERRY, :OCCABERRY, :PASSHOBERRY, :WACANBERRY, :RINDOBERRY, + :YACHEBERRY, :CHOPLEBERRY, :KEBIABERRY, :SHUCABERRY, :COBABERRY, + :PAYAPABERRY, :TANGABERRY, :CHARTIBERRY, :KASIBBERRY, :HABANBERRY, + :COLBURBERRY, :BABIRIBERRY, :CHILANBERRY, :ROSELIBERRY], + 70 => [:BLUKBERRY, :NANABBERRY, :WEPEARBERRY, :PINAPBERRY, :POMEGBERRY, + :KELPSYBERRY, :QUALOTBERRY, :HONDEWBERRY, :GREPABERRY, :TAMATOBERRY, + :CORNNBERRY, :MAGOSTBERRY, :RABUTABERRY, :NOMELBERRY, :SPELONBERRY, + :PAMTREBERRY], + 80 => [:WATMELBERRY, :DURINBERRY, :BELUEBERRY, :LIECHIBERRY, :GANLONBERRY, + :SALACBERRY, :PETAYABERRY, :APICOTBERRY, :LANSATBERRY, :STARFBERRY, + :ENIGMABERRY, :MICLEBERRY, :CUSTAPBERRY, :JABOCABERRY, :ROWAPBERRY, + :KEEBERRY, :MARANGABERRY] + } + @berry = 0 + end + + def pbMoveFailed?(user,targets) + # NOTE: Unnerve does not stop a Pokémon using this move. + @berry = user.item + if !pbIsBerry?(@berry) || !user.itemActive? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + # NOTE: The AI calls this method via pbCalcType, but it involves @berry which + # won't always be accurate (although it will always be defined). Since + # the AI won't want to use it if the user has no item anyway, and + # complex item movement is unlikely, perhaps this is good enough. + def pbBaseType(user) + ret = getID(PBTypes,:NORMAL) + found = false + @typeArray.each do |type, items| + items.each do |i| + next if !isConst?(@berry,PBItems,i) + ret = getConst(PBTypes,type) || ret + found = true; break + end + break if found + end + return ret + end + + # This is a separate method so that the AI can use it as well + def pbNaturalGiftBaseDamage(heldItem) + ret = 1 + found = false + @damageArray.each do |dmg, items| + items.each do |i| + next if !isConst?(heldItem,PBItems,i) + ret = dmg + ret += 20 if NEWEST_BATTLE_MECHANICS + found = true; break + end + break if found + end + return ret + end + + def pbBaseDamage(baseDmg,user,target) + return pbNaturalGiftBaseDamage(@berry) + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + # NOTE: The item is consumed even if this move was Protected against or it + # missed. The item is not consumed if the target was switched out by + # an effect like a target's Red Card. + # NOTE: There is no item consumption animation. + user.pbConsumeItem(true,true,false) if user.item>0 + @berry = 0 + end +end + + + +#=============================================================================== +# Power increases the less PP this move has. (Trump Card) +#=============================================================================== +class PokeBattle_Move_097 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + dmgs = [200,80,60,50,40] + ppLeft = [@pp,dmgs.length-1].min # PP is reduced before the move is used + baseDmg = dmgs[ppLeft] + return baseDmg + end +end + + + +#=============================================================================== +# Power increases the less HP the user has. (Flail, Reversal) +#=============================================================================== +class PokeBattle_Move_098 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + ret = 20 + n = 48*user.hp/user.totalhp + if n<2; ret = 200 + elsif n<5; ret = 150 + elsif n<10; ret = 100 + elsif n<17; ret = 80 + elsif n<33; ret = 40 + end + return ret + end +end + + + +#=============================================================================== +# Power increases the quicker the user is than the target. (Electro Ball) +#=============================================================================== +class PokeBattle_Move_099 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + ret = 40 + n = user.pbSpeed/target.pbSpeed + if n>=4; ret = 150 + elsif n>=3; ret = 120 + elsif n>=2; ret = 80 + elsif n>=1; ret = 60 + end + return ret + end +end + + + +#=============================================================================== +# Power increases the heavier the target is. (Grass Knot, Low Kick) +#=============================================================================== +class PokeBattle_Move_09A < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + ret = 20 + weight = target.pbWeight + if weight>=2000; ret = 120 + elsif weight>=1000; ret = 100 + elsif weight>=500; ret = 80 + elsif weight>=250; ret = 60 + elsif weight>=100; ret = 40 + end + return ret + end +end + + + +#=============================================================================== +# Power increases the heavier the user is than the target. (Heat Crash, Heavy Slam) +# Does double damage and has perfect accuracy if the target is Minimized. +#=============================================================================== +class PokeBattle_Move_09B < PokeBattle_Move + def tramplesMinimize?(param=1) + return true if NEWEST_BATTLE_MECHANICS # Perfect accuracy and double damage + return super + end + + def pbBaseDamage(baseDmg,user,target) + ret = 40 + n = (user.pbWeight/target.pbWeight).floor + if n>=5; ret = 120 + elsif n>=4; ret = 100 + elsif n>=3; ret = 80 + elsif n>=2; ret = 60 + end + return ret + end +end + + + +#=============================================================================== +# Powers up the ally's attack this round by 1.5. (Helping Hand) +#=============================================================================== +class PokeBattle_Move_09C < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + if target.fainted? || target.effects[PBEffects::HelpingHand] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedTargetAlreadyMoved?(target) + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::HelpingHand] = true + @battle.pbDisplay(_INTL("{1} is ready to help {2}!",user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# Weakens Electric attacks. (Mud Sport) +#=============================================================================== +class PokeBattle_Move_09D < PokeBattle_Move + def pbMoveFailed?(user,targets) + if NEWEST_BATTLE_MECHANICS + if @battle.field.effects[PBEffects::MudSportField]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + else + @battle.eachBattler do |b| + next if !b.effects[PBEffects::MudSport] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + end + return false + end + + def pbEffectGeneral(user) + if NEWEST_BATTLE_MECHANICS + @battle.field.effects[PBEffects::MudSportField] = 5 + else + user.effects[PBEffects::MudSport] = true + end + @battle.pbDisplay(_INTL("Electricity's power was weakened!")) + end +end + + + +#=============================================================================== +# Weakens Fire attacks. (Water Sport) +#=============================================================================== +class PokeBattle_Move_09E < PokeBattle_Move + def pbMoveFailed?(user,targets) + if NEWEST_BATTLE_MECHANICS + if @battle.field.effects[PBEffects::WaterSportField]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + else + @battle.eachBattler do |b| + next if !b.effects[PBEffects::WaterSport] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + end + return false + end + + def pbEffectGeneral(user) + if NEWEST_BATTLE_MECHANICS + @battle.field.effects[PBEffects::WaterSportField] = 5 + else + user.effects[PBEffects::WaterSport] = true + end + @battle.pbDisplay(_INTL("Fire's power was weakened!")) + end +end + + + +#=============================================================================== +# Type depends on the user's held item. (Judgment, Multi-Attack, Techno Blast) +#=============================================================================== +class PokeBattle_Move_09F < PokeBattle_Move + def initialize(battle,move) + super + if isConst?(@id,PBMoves,:JUDGMENT) + @itemTypes = { + :FISTPLATE => :FIGHTING, + :SKYPLATE => :FLYING, + :TOXICPLATE => :POISON, + :EARTHPLATE => :GROUND, + :STONEPLATE => :ROCK, + :INSECTPLATE => :BUG, + :SPOOKYPLATE => :GHOST, + :IRONPLATE => :STEEL, + :FLAMEPLATE => :FIRE, + :SPLASHPLATE => :WATER, + :MEADOWPLATE => :GRASS, + :ZAPPLATE => :ELECTRIC, + :MINDPLATE => :PSYCHIC, + :ICICLEPLATE => :ICE, + :DRACOPLATE => :DRAGON, + :DREADPLATE => :DARK, + :PIXIEPLATE => :FAIRY + } + elsif isConst?(@id,PBMoves,:TECHNOBLAST) + @itemTypes = { + :SHOCKDRIVE => :ELECTRIC, + :BURNDRIVE => :FIRE, + :CHILLDRIVE => :ICE, + :DOUSEDRIVE => :WATER + } + elsif isConst?(@id,PBMoves,:MULTIATTACK) + @itemTypes = { + :FIGHTINGMEMORY => :FIGHTING, + :SLYINGMEMORY => :FLYING, + :POISONMEMORY => :POISON, + :GROUNDMEMORY => :GROUND, + :ROCKMEMORY => :ROCK, + :BUGMEMORY => :BUG, + :GHOSTMEMORY => :GHOST, + :STEELMEMORY => :STEEL, + :FIREMEMORY => :FIRE, + :WATERMEMORY => :WATER, + :GRASSMEMORY => :GRASS, + :ELECTRICMEMORY => :ELECTRIC, + :PSYCHICMEMORY => :PSYCHIC, + :ICEMEMORY => :ICE, + :DRAGONMEMORY => :DRAGON, + :DARKMEMORY => :DARK, + :FAIRYMEMORY => :FAIRY + } + end + end + + def pbBaseType(user) + ret = getID(PBTypes,:NORMAL) + if user.itemActive? + @itemTypes.each do |item, itemType| + next if !isConst?(user.item,PBItems,item) + t = hasConst?(PBTypes,itemType) + ret = t || ret + break + end + end + return ret + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + if isConst?(@id,PBMoves,:TECHNOBLAST) # Type-specific anim + t = pbBaseType(user) + hitNum = 0 + hitNum = 1 if isConst?(t,PBTypes,:ELECTRIC) + hitNum = 2 if isConst?(t,PBTypes,:FIRE) + hitNum = 3 if isConst?(t,PBTypes,:ICE) + hitNum = 4 if isConst?(t,PBTypes,:WATER) + end + super + end +end + + + +#=============================================================================== +# This attack is always a critical hit. (Frost Breath, Storm Throw) +#=============================================================================== +class PokeBattle_Move_0A0 < PokeBattle_Move + def pbCritialOverride(user,target); return 1; end +end + + + +#=============================================================================== +# For 5 rounds, foes' attacks cannot become critical hits. (Lucky Chant) +#=============================================================================== +class PokeBattle_Move_0A1 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::LuckyChant]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::LuckyChant] = 5 + @battle.pbDisplay(_INTL("The Lucky Chant shielded {1} from critical hits!",user.pbTeam(true))) + end +end + + + +#=============================================================================== +# For 5 rounds, lowers power of physical attacks against the user's side. +# (Reflect) +#=============================================================================== +class PokeBattle_Move_0A2 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::Reflect]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Reflect] = 5 + user.pbOwnSide.effects[PBEffects::Reflect] = 8 if user.hasActiveItem?(:LIGHTCLAY) + @battle.pbDisplay(_INTL("{1} raised {2}'s Defense!",@name,user.pbTeam(true))) + end +end + + + +#=============================================================================== +# For 5 rounds, lowers power of special attacks against the user's side. (Light Screen) +#=============================================================================== +class PokeBattle_Move_0A3 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::LightScreen]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::LightScreen] = 5 + user.pbOwnSide.effects[PBEffects::LightScreen] = 8 if user.hasActiveItem?(:LIGHTCLAY) + @battle.pbDisplay(_INTL("{1} raised {2}'s Special Defense!",@name,user.pbTeam(true))) + end +end + + + +#=============================================================================== +# Effect depends on the environment. (Secret Power) +#=============================================================================== +class PokeBattle_Move_0A4 < PokeBattle_Move + def flinchingMove?; return [6,10,12].include?(@secretPower); end + + def pbOnStartUse(user,targets) + # NOTE: This is Gen 7's list plus some of Gen 6 plus a bit of my own. + @secretPower = 0 # Body Slam, paralysis + case @battle.field.terrain + when PBBattleTerrains::Electric + @secretPower = 1 # Thunder Shock, paralysis + when PBBattleTerrains::Grassy + @secretPower = 2 # Vine Whip, sleep + when PBBattleTerrains::Misty + @secretPower = 3 # Fairy Wind, lower Sp. Atk by 1 + when PBBattleTerrains::Psychic + @secretPower = 4 # Confusion, lower Speed by 1 + else + case @battle.environment + when PBEnvironment::Grass, PBEnvironment::TallGrass, + PBEnvironment::Forest, PBEnvironment::ForestGrass + @secretPower = 2 # (Same as Grassy Terrain) + when PBEnvironment::MovingWater, PBEnvironment::StillWater, + PBEnvironment::Puddle, PBEnvironment::Underwater + @secretPower = 5 # Water Pulse, lower Attack by 1 + when PBEnvironment::Puddle + @secretPower = 6 # Mud Shot, lower Speed by 1 + when PBEnvironment::Cave + @secretPower = 7 # Rock Throw, flinch + when PBEnvironment::Rock, PBEnvironment::Sand + @secretPower = 8 # Mud-Slap, lower Acc by 1 + when PBEnvironment::Snow, PBEnvironment::Ice + @secretPower = 9 # Ice Shard, freeze + when PBEnvironment::Volcano + @secretPower = 10 # Incinerate, burn + when PBEnvironment::Graveyard + @secretPower = 11 # Shadow Sneak, flinch + when PBEnvironment::Sky + @secretPower = 12 # Gust, lower Speed by 1 + when PBEnvironment::Space + @secretPower = 13 # Swift, flinch + when PBEnvironment::UltraSpace + @secretPower = 14 # Psywave, lower Defense by 1 + end + end + end + + # NOTE: This intentionally doesn't use def pbAdditionalEffect, because that + # method is called per hit and this move's additional effect only occurs + # once per use, after all the hits have happened (two hits are possible + # via Parental Bond). + def pbEffectAfterAllHits(user,target) + return if target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + chance = pbAdditionalEffectChance(user,target) + return if @battle.pbRandom(100)>=chance + case @secretPower + when 2 + target.pbSleep if target.pbCanSleep?(user,false,self) + when 10 + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + when 0, 1 + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + when 9 + target.pbFreeze if target.pbCanFreeze?(user,false,self) + when 5 + if target.pbCanLowerStatStage?(PBStats::ATTACK,user,self) + target.pbLowerStatStage(PBStats::ATTACK,1,user) + end + when 14 + if target.pbCanLowerStatStage?(PBStats::DEFENSE,user,self) + target.pbLowerStatStage(PBStats::DEFENSE,1,user) + end + when 3 + if target.pbCanLowerStatStage?(PBStats::SPATK,user,self) + target.pbLowerStatStage(PBStats::SPATK,1,user) + end + when 4, 6, 12 + if target.pbCanLowerStatStage?(PBStats::SPEED,user,self) + target.pbLowerStatStage(PBStats::SPEED,1,user) + end + when 8 + if target.pbCanLowerStatStage?(PBStats::ACCURACY,user,self) + target.pbLowerStatStage(PBStats::ACCURACY,1,user) + end + when 7, 11, 13 + target.pbFlinch(user) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + id = getConst(PBMoves,:BODYSLAM) # Environment-specific anim + case @secretPower + when 1; id = getConst(PBMoves,:THUNDERSHOCK) || id + when 2; id = getConst(PBMoves,:VINEWHIP) || id + when 3; id = getConst(PBMoves,:FAIRYWIND) || id + when 4; id = getConst(PBMoves,:CONFUSION) || id + when 5; id = getConst(PBMoves,:WATERPULSE) || id + when 6; id = getConst(PBMoves,:MUDSHOT) || id + when 7; id = getConst(PBMoves,:ROCKTHROW) || id + when 8; id = getConst(PBMoves,:MUDSLAP) || id + when 9; id = getConst(PBMoves,:ICESHARD) || id + when 10; id = getConst(PBMoves,:INCINERATE) || id + when 11; id = getConst(PBMoves,:SHADOWSNEAK) || id + when 12; id = getConst(PBMoves,:GUST) || id + when 13; id = getConst(PBMoves,:SWIFT) || id + when 14; id = getConst(PBMoves,:PSYWAVE) || id + end + super + end +end + + + +#=============================================================================== +# Always hits. +#=============================================================================== +class PokeBattle_Move_0A5 < PokeBattle_Move + def pbAccuracyCheck(user,target); return true; end +end + + + +#=============================================================================== +# User's attack next round against the target will definitely hit. +# (Lock-On, Mind Reader) +#=============================================================================== +class PokeBattle_Move_0A6 < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + user.effects[PBEffects::LockOn] = 2 + user.effects[PBEffects::LockOnPos] = target.index + @battle.pbDisplay(_INTL("{1} took aim at {2}!",user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# Target's evasion stat changes are ignored from now on. (Foresight, Odor Sleuth) +# Normal and Fighting moves have normal effectiveness against the Ghost-type target. +#=============================================================================== +class PokeBattle_Move_0A7 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Foresight] = true + @battle.pbDisplay(_INTL("{1} was identified!",target.pbThis)) + end +end + + + +#=============================================================================== +# Target's evasion stat changes are ignored from now on. (Miracle Eye) +# Psychic moves have normal effectiveness against the Dark-type target. +#=============================================================================== +class PokeBattle_Move_0A8 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::MiracleEye] = true + @battle.pbDisplay(_INTL("{1} was identified!",target.pbThis)) + end +end + + + +#=============================================================================== +# This move ignores target's Defense, Special Defense and evasion stat changes. +# (Chip Away, Darkest Lariat, Sacred Sword) +#=============================================================================== +class PokeBattle_Move_0A9 < PokeBattle_Move + def pbCalcAccuracyMultipliers(user,target,multipliers) + super + modifiers[EVA_STAGE] = 0 # Accuracy stat stage + end + + def pbGetDefenseStats(user,target) + ret1, ret2 = super + return ret1, 6 # Def/SpDef stat stage + end +end + + + +#=============================================================================== +# User is protected against moves with the "B" flag this round. (Detect, Protect) +#=============================================================================== +class PokeBattle_Move_0AA < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::Protect + end +end + + + +#=============================================================================== +# User's side is protected against moves with priority greater than 0 this round. +# (Quick Guard) +#=============================================================================== +class PokeBattle_Move_0AB < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::QuickGuard + @sidedEffect = true + end +end + + + +#=============================================================================== +# User's side is protected against moves that target multiple battlers this round. +# (Wide Guard) +#=============================================================================== +class PokeBattle_Move_0AC < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::WideGuard + @sidedEffect = true + end +end + + + +#=============================================================================== +# Ends target's protections immediately. (Feint) +#=============================================================================== +class PokeBattle_Move_0AD < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::BanefulBunker] = false + target.effects[PBEffects::KingsShield] = false + target.effects[PBEffects::Protect] = false + target.effects[PBEffects::SpikyShield] = false + target.pbOwnSide.effects[PBEffects::CraftyShield] = false + target.pbOwnSide.effects[PBEffects::MatBlock] = false + target.pbOwnSide.effects[PBEffects::QuickGuard] = false + target.pbOwnSide.effects[PBEffects::WideGuard] = false + end +end + + + +#=============================================================================== +# Uses the last move that the target used. (Mirror Move) +#=============================================================================== +class PokeBattle_Move_0AE < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + def callsAnotherMove?; return true; end + + def pbFailsAgainstTarget?(user,target) + if target.lastRegularMoveUsed<=0 || + !pbGetMoveData(target.lastRegularMoveUsed,MOVE_FLAGS)[/e/] # Not copyable by Mirror Move + @battle.pbDisplay(_INTL("The mirror move failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbUseMoveSimple(target.lastRegularMoveUsed,target.index) + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + # No animation + end +end + + + +#=============================================================================== +# Uses the last move that was used. (Copycat) +#=============================================================================== +class PokeBattle_Move_0AF < PokeBattle_Move + def callsAnotherMove?; return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + # Struggle, Chatter, Belch + "002", # Struggle + "014", # Chatter + "158", # Belch # Not listed on Bulbapedia + # Moves that affect the moveset + "05C", # Mimic + "05D", # Sketch + "069", # Transform + # Counter moves + "071", # Counter + "072", # Mirror Coat + "073", # Metal Burst # Not listed on Bulbapedia + # Helping Hand, Feint (always blacklisted together, don't know why) + "09C", # Helping Hand + "0AD", # Feint + # Protection moves + "0AA", # Detect, Protect + "0AB", # Quick Guard # Not listed on Bulbapedia + "0AC", # Wide Guard # Not listed on Bulbapedia + "0E8", # Endure + "149", # Mat Block + "14A", # Crafty Shield # Not listed on Bulbapedia + "14B", # King's Shield + "14C", # Spiky Shield + "168", # Baneful Bunker + # Moves that call other moves + "0AE", # Mirror Move + "0AF", # Copycat (this move) + "0B0", # Me First + "0B3", # Nature Power # Not listed on Bulbapedia + "0B4", # Sleep Talk + "0B5", # Assist + "0B6", # Metronome + # Move-redirecting and stealing moves + "0B1", # Magic Coat # Not listed on Bulbapedia + "0B2", # Snatch + "117", # Follow Me, Rage Powder + "16A", # Spotlight + # Set up effects that trigger upon KO + "0E6", # Grudge # Not listed on Bulbapedia + "0E7", # Destiny Bond + # Held item-moving moves + "0F1", # Covet, Thief + "0F2", # Switcheroo, Trick + "0F3", # Bestow + # Moves that start focussing at the start of the round + "115", # Focus Punch + "171", # Shell Trap + "172", # Beak Blast + # Event moves that do nothing + "133", # Hold Hands + "134" # Celebrate + ] + if NEWEST_BATTLE_MECHANICS + @moveBlacklist += [ + # Target-switching moves + "0EB", # Roar, Whirlwind + "0EC" # Circle Throw, Dragon Tail + ] + end + end + + def pbMoveFailed?(user,targets) + if @battle.lastMoveUsed<=0 || + @moveBlacklist.include?(pbGetMoveData(@battle.lastMoveUsed,MOVE_FUNCTION_CODE)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbUseMoveSimple(@battle.lastMoveUsed) + end +end + + + +#=============================================================================== +# Uses the move the target was about to use this round, with 1.5x power. +# (Me First) +#=============================================================================== +class PokeBattle_Move_0B0 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + def callsAnotherMove?; return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + "0F1", # Covet, Thief + # Struggle, Chatter, Belch + "002", # Struggle + "014", # Chatter + "158", # Belch + # Counter moves + "071", # Counter + "072", # Mirror Coat + "073", # Metal Burst + # Moves that start focussing at the start of the round + "115", # Focus Punch + "171", # Shell Trap + "172" # Beak Blast + ] + end + + def pbFailsAgainstTarget?(user,target) + return true if pbMoveFailedTargetAlreadyMoved?(target) + oppMove = @battle.choices[target.index][2] + if !oppMove || oppMove.id<=0 || + oppMove.statusMove? || @moveBlacklist.include?(oppMove.function) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.effects[PBEffects::MeFirst] = true + user.pbUseMoveSimple(@battle.choices[target.index][2].id) + user.effects[PBEffects::MeFirst] = false + end +end + + + +#=============================================================================== +# This round, reflects all moves with the "C" flag targeting the user back at +# their origin. (Magic Coat) +#=============================================================================== +class PokeBattle_Move_0B1 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::MagicCoat] = true + @battle.pbDisplay(_INTL("{1} shrouded itself with Magic Coat!",user.pbThis)) + end +end + + + +#=============================================================================== +# This round, snatches all used moves with the "D" flag. (Snatch) +#=============================================================================== +class PokeBattle_Move_0B2 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::Snatch] = 1 + @battle.eachBattler do |b| + next if b.effects[PBEffects::Snatch]0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedAromaVeil?(user,target) + canDisable = false + target.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + next if m.pp==0 && m.totalpp>0 + canDisable = true + break + end + if !canDisable + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Disable] = 5 + target.effects[PBEffects::DisableMove] = target.lastRegularMoveUsed + @battle.pbDisplay(_INTL("{1}'s {2} was disabled!",target.pbThis, + PBMoves.getName(target.lastRegularMoveUsed))) + target.pbItemStatusCureCheck + end +end + + + +#=============================================================================== +# For 4 rounds, disables the target's non-damaging moves. (Taunt) +#=============================================================================== +class PokeBattle_Move_0BA < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Taunt]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedAromaVeil?(user,target) + if NEWEST_BATTLE_MECHANICS && target.hasActiveAbility?(:OBLIVIOUS) && + !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("But it failed!")) + else + @battle.pbDisplay(_INTL("But it failed because of {1}'s {2}!", + target.pbThis(true),target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Taunt] = 4 + @battle.pbDisplay(_INTL("{1} fell for the taunt!",target.pbThis)) + target.pbItemStatusCureCheck + end +end + + + +#=============================================================================== +# For 5 rounds, disables the target's healing moves. (Heal Block) +#=============================================================================== +class PokeBattle_Move_0BB < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::HealBlock]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedAromaVeil?(user,target) + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::HealBlock] = 5 + @battle.pbDisplay(_INTL("{1} was prevented from healing!",target.pbThis)) + target.pbItemStatusCureCheck + end +end + + + +#=============================================================================== +# For 4 rounds, the target must use the same move each round. (Encore) +#=============================================================================== +class PokeBattle_Move_0BC < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + "0BC", # Encore + # Struggle + "002", # Struggle + # Moves that affect the moveset + "05C", # Mimic + "05D", # Sketch + "069", # Transform + # Moves that call other moves (see also below) + "0AE" # Mirror Move + ] + if NEWEST_BATTLE_MECHANICS + @moveBlacklist += [ + # Moves that call other moves +# "0AE", # Mirror Move # See above + "0AF", # Copycat + "0B0", # Me First + "0B3", # Nature Power + "0B4", # Sleep Talk + "0B5", # Assist + "0B6" # Metronome + ] + end + end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Encore]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.lastRegularMoveUsed<=0 || + @moveBlacklist.include?(pbGetMoveData(target.lastRegularMoveUsed,MOVE_FUNCTION_CODE)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.effects[PBEffects::ShellTrap] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedAromaVeil?(user,target) + canEncore = false + target.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + next if m.pp==0 && m.totalpp>0 + canEncore = true + break + end + if !canEncore + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Encore] = 4 + target.effects[PBEffects::EncoreMove] = target.lastRegularMoveUsed + @battle.pbDisplay(_INTL("{1} received an encore!",target.pbThis)) + target.pbItemStatusCureCheck + end +end + + + +#=============================================================================== +# Hits twice. +#=============================================================================== +class PokeBattle_Move_0BD < PokeBattle_Move + def multiHitMove?; return true; end + def pbNumHits(user,targets); return 2; end +end + + + +#=============================================================================== +# Hits twice. May poison the target on each hit. (Twineedle) +#=============================================================================== +class PokeBattle_Move_0BE < PokeBattle_PoisonMove + def multiHitMove?; return true; end + def pbNumHits(user,targets); return 2; end +end + + + +#=============================================================================== +# Hits 3 times. Power is multiplied by the hit number. (Triple Kick) +# An accuracy check is performed for each hit. +#=============================================================================== +class PokeBattle_Move_0BF < PokeBattle_Move + def multiHitMove?; return true; end + def pbNumHits(user,targets); return 3; end + + def successCheckPerHit? + return @accCheckPerHit + end + + def pbOnStartUse(user,targets) + @calcBaseDmg = 0 + @accCheckPerHit = !user.hasActiveAbility?(:SKILLLINK) + end + + def pbBaseDamage(baseDmg,user,target) + @calcBaseDmg += baseDmg + return @calcBaseDmg + end +end + + + +#=============================================================================== +# Hits 2-5 times. +#=============================================================================== +class PokeBattle_Move_0C0 < PokeBattle_Move + def multiHitMove?; return true; end + + def pbNumHits(user,targets) + if isConst?(@id,PBMoves,:WATERSHURIKEN) && + isConst?(user.species,PBSpecies,:GRENINJA) && user.form==1 + return 3 + end + hitChances = [2,2,3,3,4,5] + r = @battle.pbRandom(hitChances.length) + r = hitChances.length-1 if user.hasActiveAbility?(:SKILLLINK) + return hitChances[r] + end + + def pbBaseDamage(baseDmg,user,target) + if isConst?(@id,PBMoves,:WATERSHURIKEN) && + isConst?(user.species,PBSpecies,:GRENINJA) && user.form==1 + return 20 + end + return super + end +end + + + +#=============================================================================== +# Hits X times, where X is the number of non-user unfainted status-free Pokémon +# in the user's party (not including partner trainers). Fails if X is 0. +# Base power of each hit depends on the base Attack stat for the species of that +# hit's participant. (Beat Up) +#=============================================================================== +class PokeBattle_Move_0C1 < PokeBattle_Move + def multiHitMove?; return true; end + + def pbMoveFailed?(user,targets) + @beatUpList = [] + @battle.eachInTeamFromBattlerIndex(user.index) do |pkmn,i| + next if !pkmn.able? || pkmn.status!=PBStatuses::NONE + @beatUpList.push(i) + end + if @beatUpList.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbNumHits(user,targets) + return @beatUpList.length + end + + def pbBaseDamage(baseDmg,user,target) + i = @beatUpList.shift # First element in array, and removes it from array + atk = @battle.pbParty(user.index)[i].baseStats[PBStats::ATTACK] + return 5+(atk/10) + end +end + + + +#=============================================================================== +# Two turn attack. Attacks first turn, skips second turn (if successful). +#=============================================================================== +class PokeBattle_Move_0C2 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::HyperBeam] = 2 + user.currentMove = @id + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Razor Wind) +#=============================================================================== +class PokeBattle_Move_0C3 < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} whipped up a whirlwind!",user.pbThis)) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Solar Beam, Solar Blade) +# Power halved in all weather except sunshine. In sunshine, takes 1 turn instead. +#=============================================================================== +class PokeBattle_Move_0C4 < PokeBattle_TwoTurnMove + def pbIsChargingTurn?(user) + ret = super + if user.effects[PBEffects::TwoTurnAttack]==0 + w = @battle.pbWeather + if w==PBWeather::Sun || w==PBWeather::HarshSun + @powerHerb = false + @chargingTurn = true + @damagingTurn = true + return false + end + end + return ret + end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} took in sunlight!",user.pbThis)) + end + + def pbBaseDamageMultiplier(damageMult,user,target) + w = @battle.pbWeather + if w>0 && w!=PBWeather::Sun && w!=PBWeather::HarshSun + damageMult = (damageMult/2.0).round + end + return damageMult + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Freeze Shock) +# May paralyze the target. +#=============================================================================== +class PokeBattle_Move_0C5 < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} became cloaked in a freezing light!",user.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Ice Burn) +# May burn the target. +#=============================================================================== +class PokeBattle_Move_0C6 < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} became cloaked in freezing air!",user.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Sky Attack) +# May make the target flinch. +#=============================================================================== +class PokeBattle_Move_0C7 < PokeBattle_TwoTurnMove + def flinchingMove?; return true; end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} became cloaked in a harsh light!",user.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbFlinch(user) + end +end + + + +#=============================================================================== +# Two turn attack. Ups user's Defense by 1 stage first turn, attacks second turn. +# (Skull Bash) +#=============================================================================== +class PokeBattle_Move_0C8 < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} tucked in its head!",user.pbThis)) + end + + def pbChargingTurnEffect(user,target) + if user.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + user.pbRaiseStatStage(PBStats::DEFENSE,1,user) + end + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Fly) +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +#=============================================================================== +class PokeBattle_Move_0C9 < PokeBattle_TwoTurnMove + def unusableInGravity?; return true; end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} flew up high!",user.pbThis)) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Dig) +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +#=============================================================================== +class PokeBattle_Move_0CA < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} burrowed its way under the ground!",user.pbThis)) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Dive) +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +#=============================================================================== +class PokeBattle_Move_0CB < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} hid underwater!",user.pbThis)) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Bounce) +# May paralyze the target. +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +#=============================================================================== +class PokeBattle_Move_0CC < PokeBattle_TwoTurnMove + def unusableInGravity?; return true; end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} sprang up!",user.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Shadow Force) +# Is invulnerable during use. Ends target's protections upon hit. +#=============================================================================== +class PokeBattle_Move_0CD < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} vanished instantly!",user.pbThis)) + end + + def pbAttackingTurnEffect(user,target) + target.effects[PBEffects::BanefulBunker] = false + target.effects[PBEffects::KingsShield] = false + target.effects[PBEffects::Protect] = false + target.effects[PBEffects::SpikyShield] = false + target.pbOwnSide.effects[PBEffects::CraftyShield] = false + target.pbOwnSide.effects[PBEffects::MatBlock] = false + target.pbOwnSide.effects[PBEffects::QuickGuard] = false + target.pbOwnSide.effects[PBEffects::WideGuard] = false + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Sky Drop) +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +# Target is also semi-invulnerable during use, and can't take any action. +# Doesn't damage airborne Pokémon (but still makes them unable to move during). +#=============================================================================== +class PokeBattle_Move_0CE < PokeBattle_TwoTurnMove + def unusableInGravity?; return true; end + + def pbIsChargingTurn?(user) + # NOTE: Sky Drop doesn't benefit from Power Herb, probably because it works + # differently (i.e. immobilises the target during use too). + @powerHerb = false + @chargingTurn = (user.effects[PBEffects::TwoTurnAttack]==0) + @damagingTurn = (user.effects[PBEffects::TwoTurnAttack]!=0) + return !@damagingTurn + end + + def pbFailsAgainstTarget?(user,target) + if !target.opposes?(user) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.effects[PBEffects::Substitute]>0 && !ignoresSubstitute?(user) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if NEWEST_BATTLE_MECHANICS && target.pbWeight>=2000 # 200.0kg + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.semiInvulnerable? || + (target.effects[PBEffects::SkyDrop]>=0 && @chargingTurn) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.effects[PBEffects::SkyDrop]!=user.index && @damagingTurn + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbCalcTypeMod(movetype,user,target) + return PBTypeEffectiveness::INEFFECTIVE if target.pbHasType?(:FLYING) + return super + end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} took {2} into the sky!",user.pbThis,targets[0].pbThis(true))) + end + + def pbAttackingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} was freed from the Sky Drop!",targets[0].pbThis)) + end + + def pbChargingTurnEffect(user,target) + target.effects[PBEffects::SkyDrop] = user.index + end + + def pbAttackingTurnEffect(user,target) + target.effects[PBEffects::SkyDrop] = -1 + end +end + + + +#=============================================================================== +# Trapping move. Traps for 5 or 6 rounds. Trapped Pokémon lose 1/16 of max HP +# at end of each round. +#=============================================================================== +class PokeBattle_Move_0CF < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + return if target.fainted? || target.damageState.substitute + return if target.effects[PBEffects::Trapping]>0 + # Set trapping effect duration and info + if user.hasActiveItem?(:GRIPCLAW) + target.effects[PBEffects::Trapping] = (NEWEST_BATTLE_MECHANICS) ? 8 : 6 + else + target.effects[PBEffects::Trapping] = 5+@battle.pbRandom(2) + end + target.effects[PBEffects::TrappingMove] = @id + target.effects[PBEffects::TrappingUser] = user.index + # Message + msg = _INTL("{1} was trapped in the vortex!",target.pbThis) + if isConst?(@id,PBMoves,:BIND) + msg = _INTL("{1} was squeezed by {2}!",target.pbThis,user.pbThis(true)) + elsif isConst?(@id,PBMoves,:CLAMP) + msg = _INTL("{1} clamped {2}!",user.pbThis,target.pbThis(true)) + elsif isConst?(@id,PBMoves,:FIRESPIN) + msg = _INTL("{1} was trapped in the fiery vortex!",target.pbThis) + elsif isConst?(@id,PBMoves,:INFESTATION) + msg = _INTL("{1} has been afflicted with an infestation by {2}!",target.pbThis,user.pbThis(true)) + elsif isConst?(@id,PBMoves,:MAGMASTORM) + msg = _INTL("{1} became trapped by Magma Storm!",target.pbThis) + elsif isConst?(@id,PBMoves,:SANDTOMB) + msg = _INTL("{1} became trapped by Sand Tomb!",target.pbThis) + elsif isConst?(@id,PBMoves,:WHIRLPOOL) + msg = _INTL("{1} became trapped in the vortex!",target.pbThis) + elsif isConst?(@id,PBMoves,:WRAP) + msg = _INTL("{1} was wrapped by {2}!",target.pbThis,user.pbThis(true)) + end + @battle.pbDisplay(msg) + end +end + + + +#=============================================================================== +# Trapping move. Traps for 5 or 6 rounds. Trapped Pokémon lose 1/16 of max HP +# at end of each round. (Whirlpool) +# Power is doubled if target is using Dive. Hits some semi-invulnerable targets. +#=============================================================================== +class PokeBattle_Move_0D0 < PokeBattle_Move_0CF + def hitsDivingTargets?; return true; end + + def pbModifyDamage(damageMult,user,target) + damageMult *= 2 if target.inTwoTurnAttack?("0CB") # Dive + return damageMult + end +end + + + +#=============================================================================== +# User must use this move for 2 more rounds. No battlers can sleep. (Uproar) +# NOTE: Bulbapedia claims that an uproar will wake up Pokémon even if they have +# Soundproof, and will not allow Pokémon to fall asleep even if they have +# Soundproof. I think this is an oversight, so I've let Soundproof Pokémon +# be unaffected by Uproar waking/non-sleeping effects. +#=============================================================================== +class PokeBattle_Move_0D1 < PokeBattle_Move + def pbEffectGeneral(user) + return if user.effects[PBEffects::Uproar]>0 + user.effects[PBEffects::Uproar] = 3 + user.currentMove = @id + @battle.pbDisplay(_INTL("{1} caused an uproar!",user.pbThis)) + @battle.pbPriority(true).each do |b| + next if b.fainted? || b.status!=PBStatuses::SLEEP + next if b.hasActiveAbility?(:SOUNDPROOF) + b.pbCureStatus + end + end +end + + + +#=============================================================================== +# User must use this move for 1 or 2 more rounds. At end, user becomes confused. +# (Outrage, Petal Dange, Thrash) +#=============================================================================== +class PokeBattle_Move_0D2 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + if !target.damageState.unaffected && user.effects[PBEffects::Outrage]==0 + user.effects[PBEffects::Outrage] = 2+@battle.pbRandom(2) + user.currentMove = @id + end + if user.effects[PBEffects::Outrage]>0 + user.effects[PBEffects::Outrage] -= 1 + if user.effects[PBEffects::Outrage]==0 && user.pbCanConfuseSelf?(false) + user.pbConfuse(_INTL("{1} became confused due to fatigue!",user.pbThis)) + end + end + end +end + + + +#=============================================================================== +# User must use this move for 4 more rounds. Power doubles each round. +# Power is also doubled if user has curled up. (Ice Ball, Rollout) +#=============================================================================== +class PokeBattle_Move_0D3 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + shift = (4-user.effects[PBEffects::Rollout]) # 0-4, where 0 is most powerful + shift += 1 if user.effects[PBEffects::DefenseCurl] + baseDmg = baseDmg<0 + end +end + + + +#=============================================================================== +# User bides its time this round and next round. The round after, deals 2x the +# total direct damage it took while biding to the last battler that damaged it. +# (Bide) +#=============================================================================== +class PokeBattle_Move_0D4 < PokeBattle_FixedDamageMove + def pbAddTarget(targets,user) + return if user.effects[PBEffects::Bide]!=1 # Not the attack turn + idxTarget = user.effects[PBEffects::BideTarget] + t = (idxTarget>=0) ? @battle.battlers[idxTarget] : nil + if !user.pbAddTarget(targets,user,t,self,false) + user.pbAddTargetRandomFoe(targets,user,self,false) + end + end + + def pbMoveFailed?(user,targets) + return false if user.effects[PBEffects::Bide]!=1 # Not the attack turn + if user.effects[PBEffects::BideDamage]==0 + @battle.pbDisplay(_INTL("But it failed!")) + user.effects[PBEffects::Bide] = 0 # No need to reset other Bide variables + return true + end + if targets.length==0 + @battle.pbDisplay(_INTL("But there was no target...")) + user.effects[PBEffects::Bide] = 0 # No need to reset other Bide variables + return true + end + return false + end + + def pbOnStartUse(user,targets) + @damagingTurn = (user.effects[PBEffects::Bide]==1) # If attack turn + end + + def pbDisplayUseMessage(user) + if @damagingTurn # Attack turn + @battle.pbDisplayBrief(_INTL("{1} unleashed energy!",user.pbThis)) + elsif user.effects[PBEffects::Bide]>1 # Charging turns + @battle.pbDisplayBrief(_INTL("{1} is storing energy!",user.pbThis)) + else + super # Start using Bide + end + end + + def pbDamagingMove? # Stops damage being dealt in the charging turns + return false if !@damagingTurn + return super + end + + def pbFixedDamage(user,target) + return user.effects[PBEffects::BideDamage]*2 + end + + def pbEffectGeneral(user) + if user.effects[PBEffects::Bide]==0 # Starting using Bide + user.effects[PBEffects::Bide] = 3 + user.effects[PBEffects::BideDamage] = 0 + user.effects[PBEffects::BideTarget] = -1 + user.currentMove = @id + end + user.effects[PBEffects::Bide] -= 1 + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if !@damagingTurn # Charging anim + super + end +end + + + +#=============================================================================== +# Heals user by 1/2 of its max HP. +#=============================================================================== +class PokeBattle_Move_0D5 < PokeBattle_HealingMove + def pbHealAmount(user) + return (user.totalhp/2.0).round + end +end + + + +#=============================================================================== +# Heals user by 1/2 of its max HP. (Roost) +# User roosts, and its Flying type is ignored for attacks used against it. +#=============================================================================== +class PokeBattle_Move_0D6 < PokeBattle_HealingMove + def pbHealAmount(user) + return (user.totalhp/2.0).round + end + + def pbEffectAfterAllHits(user,target) + user.effects[PBEffects::Roost] = true + end +end + + + +#=============================================================================== +# Battler in user's position is healed by 1/2 of its max HP, at the end of the +# next round. (Wish) +#=============================================================================== +class PokeBattle_Move_0D7 < PokeBattle_Move + def healingMove?; return true; end + + def pbMoveFailed?(user,targets) + if @battle.positions[user.index].effects[PBEffects::Wish]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.positions[user.index].effects[PBEffects::Wish] = 2 + @battle.positions[user.index].effects[PBEffects::WishAmount] = (user.totalhp/2.0).round + @battle.positions[user.index].effects[PBEffects::WishMaker] = user.pokemonIndex + end +end + + + +#=============================================================================== +# Heals user by an amount depending on the weather. (Moonlight, Morning Sun, +# Synthesis) +#=============================================================================== +class PokeBattle_Move_0D8 < PokeBattle_HealingMove + def pbOnStartUse(user,targets) + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + @healAmount = (user.totalhp*2/3.0).round + when PBWeather::None, PBWeather::StrongWinds + @healAmount = (user.totalhp/2.0).round + else + @healAmount = (user.totalhp/4.0).round + end + end + + def pbHealAmount(user) + return @healAmount + end +end + + + +#=============================================================================== +# Heals user to full HP. User falls asleep for 2 more rounds. (Rest) +#=============================================================================== +class PokeBattle_Move_0D9 < PokeBattle_HealingMove + def pbMoveFailed?(user,targets) + if user.asleep? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if !user.pbCanSleep?(user,true,self,true) + return true if super + return false + end + + def pbHealAmount(user) + return user.totalhp-user.hp + end + + def pbEffectGeneral(user) + user.pbSleepSelf(_INTL("{1} slept and became healthy!",user.pbThis),3) + super + end +end + + + +#=============================================================================== +# Rings the user. Ringed Pokémon gain 1/16 of max HP at the end of each round. +# (Aqua Ring) +#=============================================================================== +class PokeBattle_Move_0DA < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::AquaRing] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::AquaRing] = true + @battle.pbDisplay(_INTL("{1} surrounded itself with a veil of water!",user.pbThis)) + end +end + + + +#=============================================================================== +# Ingrains the user. Ingrained Pokémon gain 1/16 of max HP at the end of each +# round, and cannot flee or switch out. (Ingrain) +#=============================================================================== +class PokeBattle_Move_0DB < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Ingrain] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Ingrain] = true + @battle.pbDisplay(_INTL("{1} planted its roots!",user.pbThis)) + end +end + + + +#=============================================================================== +# Seeds the target. Seeded Pokémon lose 1/8 of max HP at the end of each round, +# and the Pokémon in the user's position gains the same amount. (Leech Seed) +#=============================================================================== +class PokeBattle_Move_0DC < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::LeechSeed]>=0 + @battle.pbDisplay(_INTL("{1} evaded the attack!",target.pbThis)) + return true + end + if target.pbHasType?(:GRASS) + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return true + end + return false + end + + def pbMissMessage(user,target) + @battle.pbDisplay(_INTL("{1} evaded the attack!",target.pbThis)) + return true + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::LeechSeed] = user.index + @battle.pbDisplay(_INTL("{1} was seeded!",target.pbThis)) + end +end + + + +#=============================================================================== +# User gains half the HP it inflicts as damage. +#=============================================================================== +class PokeBattle_Move_0DD < PokeBattle_Move + def healingMove?; return NEWEST_BATTLE_MECHANICS; end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.hpLost<=0 + hpGain = (target.damageState.hpLost/2.0).round + user.pbRecoverHPFromDrain(hpGain,target) + end +end + + + +#=============================================================================== +# User gains half the HP it inflicts as damage. Fails if target is not asleep. +# (Dream Eater) +#=============================================================================== +class PokeBattle_Move_0DE < PokeBattle_Move + def healingMove?; return NEWEST_BATTLE_MECHANICS; end + + def pbFailsAgainstTarget?(user,target) + if !target.asleep? + @battle.pbDisplay(_INTL("{1} wasn't affected!",target.pbThis)) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.hpLost<=0 + hpGain = (target.damageState.hpLost/2.0).round + user.pbRecoverHPFromDrain(hpGain,target) + end +end + + + +#=============================================================================== +# Heals target by 1/2 of its max HP. (Heal Pulse) +#=============================================================================== +class PokeBattle_Move_0DF < PokeBattle_Move + def healingMove?; return true; end + + def pbFailsAgainstTarget?(user,target) + if target.hp==target.totalhp + @battle.pbDisplay(_INTL("{1}'s HP is full!",target.pbThis)) + return true + elsif !target.canHeal? + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + hpGain = (target.totalhp/2.0).round + if pulseMove? && user.hasActiveAbility?(:MEGALAUNCHER) + hpGain = (target.totalhp*3/4.0).round + end + target.pbRecoverHP(hpGain) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + end +end + + + +#=============================================================================== +# User faints, even if the move does nothing else. (Explosion, Self-Destruct) +#=============================================================================== +class PokeBattle_Move_0E0 < PokeBattle_Move + def worksWithNoTargets?; return true; end + def pbNumHits(user,targets); return 1; end + + def pbMoveFailed?(user,targets) + if !@battle.moldBreaker + bearer = @battle.pbCheckGlobalAbility(:DAMP) + if bearer!=nil + @battle.pbShowAbilitySplash(bearer) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} cannot use {2}!",user.pbThis,@name)) + else + @battle.pbDisplay(_INTL("{1} cannot use {2} because of {3}'s {4}!", + user.pbThis,@name,bearer.pbThis(true),bearer.abilityName)) + end + @battle.pbHideAbilitySplash(bearer) + return true + end + end + return false + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Inflicts fixed damage equal to user's current HP. (Final Gambit) +# User faints (if successful). +#=============================================================================== +class PokeBattle_Move_0E1 < PokeBattle_FixedDamageMove + def pbNumHits(user,targets); return 1; end + + def pbOnStartUse(user,targets) + @finalGambitDamage = user.hp + end + + def pbFixedDamage(user,target) + return @finalGambitDamage + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Decreases the target's Attack and Special Attack by 2 stages each. (Memento) +# User faints (if successful). +#=============================================================================== +class PokeBattle_Move_0E2 < PokeBattle_TargetMultiStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,2,PBStats::SPATK,2] + end + + # NOTE: The user faints even if the target's stats cannot be changed, so this + # method must always return false to allow the move's usage to continue. + def pbFailsAgainstTarget?(user,target) + return false + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# User faints. The Pokémon that replaces the user is fully healed (HP and +# status). Fails if user won't be replaced. (Healing Wish) +#=============================================================================== +class PokeBattle_Move_0E3 < PokeBattle_Move + def healingMove?; return true; end + + def pbMoveFailed?(user,targets) + if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + @battle.positions[user.index].effects[PBEffects::HealingWish] = true + end +end + + + +#=============================================================================== +# User faints. The Pokémon that replaces the user is fully healed (HP, PP and +# status). Fails if user won't be replaced. (Lunar Dance) +#=============================================================================== +class PokeBattle_Move_0E4 < PokeBattle_Move + def healingMove?; return true; end + + def pbMoveFailed?(user,targets) + if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + @battle.positions[user.index].effects[PBEffects::LunarDance] = true + end +end + + + +#=============================================================================== +# All current battlers will perish after 3 more rounds. (Perish Song) +#=============================================================================== +class PokeBattle_Move_0E5 < PokeBattle_Move + def pbMoveFailed?(user,targets) + failed = true + targets.each do |b| + next if b.effects[PBEffects::PerishSong]>0 # Heard it before + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return target.effects[PBEffects::PerishSong]>0 # Heard it before + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::PerishSong] = 4 + target.effects[PBEffects::PerishSongUser] = user.index + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + super + @battle.pbDisplay(_INTL("All Pokémon that hear the song will faint in three turns!")) + end +end + + + +#=============================================================================== +# If user is KO'd before it next moves, the attack that caused it loses all PP. +# (Grudge) +#=============================================================================== +class PokeBattle_Move_0E6 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::Grudge] = true + @battle.pbDisplay(_INTL("{1} wants its target to bear a grudge!",user.pbThis)) + end +end + + + +#=============================================================================== +# If user is KO'd before it next moves, the battler that caused it also faints. +# (Destiny Bond) +#=============================================================================== +class PokeBattle_Move_0E7 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if NEWEST_BATTLE_MECHANICS && user.effects[PBEffects::DestinyBondPrevious] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::DestinyBond] = true + @battle.pbDisplay(_INTL("{1} is hoping to take its attacker down with it!",user.pbThis)) + end +end + + + +#=============================================================================== +# If user would be KO'd this round, it survives with 1HP instead. (Endure) +#=============================================================================== +class PokeBattle_Move_0E8 < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::Endure + end + + def pbProtectMessage(user) + @battle.pbDisplay(_INTL("{1} braced itself!",user.pbThis)) + end +end + + + +#=============================================================================== +# If target would be KO'd by this attack, it survives with 1HP instead. +# (False Swipe, Hold Back) +#=============================================================================== +class PokeBattle_Move_0E9 < PokeBattle_Move + def nonLethal?(user,target); return true; end +end + + + +#=============================================================================== +# User flees from battle. Fails in trainer battles. (Teleport) +#=============================================================================== +class PokeBattle_Move_0EA < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !@battle.pbCanRun?(user.index) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbDisplay(_INTL("{1} fled from battle!",user.pbThis)) + @battle.decision = 3 # Escaped + end +end + + + +#=============================================================================== +# In wild battles, makes target flee. Fails if target is a higher level than the +# user. +# In trainer battles, target switches out. +# For status moves. (Roar, Whirlwind) +#=============================================================================== +class PokeBattle_Move_0EB < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + if target.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} anchors itself!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} anchors itself with {2}!",target.pbThis,target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + if target.effects[PBEffects::Ingrain] + @battle.pbDisplay(_INTL("{1} anchored itself with its roots!",target.pbThis)) + return true + end + if @battle.wildBattle? && target.level>user.level + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if @battle.trainerBattle? + canSwitch = false + @battle.eachInTeamFromBattlerIndex(target.index) do |pkmn,i| + next if !@battle.pbCanSwitchLax?(target.index,i) + canSwitch = true + break + end + if !canSwitch + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + end + return false + end + + def pbEffectGeneral(user) + @battle.decision = 3 if @battle.wildBattle? # Escaped from battle + end + + def pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers) + return if @battle.wildBattle? + return if user.fainted? || numHits==0 + roarSwitched = [] + targets.each do |b| + next if b.fainted? || b.damageState.unaffected || switchedBattlers.include?(b.index) + newPkmn = @battle.pbGetReplacementPokemonIndex(b.index,true) # Random + next if newPkmn<0 + @battle.pbRecallAndReplace(b.index,newPkmn) + @battle.pbDisplay(_INTL("{1} was dragged out!",b.pbThis)) + @battle.pbClearChoice(b.index) # Replacement Pokémon does nothing this round + switchedBattlers.push(b.index) + roarSwitched.push(b.index) + end + if roarSwitched.length>0 + @battle.moldBreaker = false if roarSwitched.include?(user.index) + @battle.pbPriority(true).each do |b| + b.pbEffectsOnSwitchIn(true) if roarSwitched.include?(b.index) + end + end + end +end + + + +#=============================================================================== +# In wild battles, makes target flee. Fails if target is a higher level than the +# user. +# In trainer battles, target switches out. +# For damaging moves. (Circle Throw, Dragon Tail) +#=============================================================================== +class PokeBattle_Move_0EC < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + if @battle.wildBattle? && target.level<=user.level && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + @battle.decision = 3 + end + end + + def pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers) + return if @battle.wildBattle? + return if user.fainted? || numHits==0 + roarSwitched = [] + targets.each do |b| + next if b.fainted? || b.damageState.unaffected || b.damageState.substitute + next if switchedBattlers.include?(b.index) + next if b.effects[PBEffects::Ingrain] + next if b.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker + newPkmn = @battle.pbGetReplacementPokemonIndex(b.index,true) # Random + next if newPkmn<0 + @battle.pbRecallAndReplace(b.index,newPkmn) + @battle.pbDisplay(_INTL("{1} was dragged out!",b.pbThis)) + @battle.pbClearChoice(b.index) # Replacement Pokémon does nothing this round + switchedBattlers.push(b.index) + roarSwitched.push(b.index) + end + if roarSwitched>0 + @battle.moldBreaker = false if roarSwitched.include?(user.index) + @battle.pbPriority(true).each do |b| + b.pbEffectsOnSwitchIn(true) if roarSwitched.include?(b.index) + end + end + end +end + + + +#=============================================================================== +# User switches out. Various effects affecting the user are passed to the +# replacement. (Baton Pass) +#=============================================================================== +class PokeBattle_Move_0ED < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + return if user.fainted? || numHits==0 + return if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbPursuit(user.index) + return if user.fainted? + newPkmn = @battle.pbGetReplacementPokemonIndex(user.index) # Owner chooses + return if newPkmn<0 + @battle.pbRecallAndReplace(user.index,newPkmn,true) + @battle.pbClearChoice(user.index) # Replacement Pokémon does nothing this round + @battle.moldBreaker = false + switchedBattlers.push(user.index) + user.pbEffectsOnSwitchIn(true) + end +end + + + +#=============================================================================== +# After inflicting damage, user switches out. Ignores trapping moves. +# (U-turn, Volt Switch) +#=============================================================================== +class PokeBattle_Move_0EE < PokeBattle_Move + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + return if user.fainted? || numHits==0 + targetSwitched = true + targets.each do |b| + targetSwitched = false if !switchedBattlers.include?(b.index) + end + return if targetSwitched + return if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbDisplay(_INTL("{1} went back to {2}!",user.pbThis, + @battle.pbGetOwnerName(user.index))) + @battle.pbPursuit(user.index) + return if user.fainted? + newPkmn = @battle.pbGetReplacementPokemonIndex(user.index) # Owner chooses + return if newPkmn<0 + @battle.pbRecallAndReplace(user.index,newPkmn) + @battle.pbClearChoice(user.index) # Replacement Pokémon does nothing this round + @battle.moldBreaker = false + switchedBattlers.push(user.index) + user.pbEffectsOnSwitchIn(true) + end +end + + + +#=============================================================================== +# Target can no longer switch out or flee, as long as the user remains active. +# (Anchor Shot, Block, Mean Look, Spider Web, Spirit Shackle, Thousand Waves) +#=============================================================================== +class PokeBattle_Move_0EF < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + if target.effects[PBEffects::MeanLook]>=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if NEWEST_BATTLE_MECHANICS && target.pbHasType?(:GHOST) + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.effects[PBEffects::MeanLook] = user.index + @battle.pbDisplay(_INTL("{1} can no longer escape!",target.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.fainted? || target.damageState.substitute + return if target.effects[PBEffects::MeanLook]>=0 + return if NEWEST_BATTLE_MECHANICS && target.pbHasType?(:GHOST) + target.effects[PBEffects::MeanLook] = user.index + @battle.pbDisplay(_INTL("{1} can no longer escape!",target.pbThis)) + end +end + + + +#=============================================================================== +# Target drops its item. It regains the item at the end of the battle. (Knock Off) +# If target has a losable item, damage is multiplied by 1.5. +#=============================================================================== +class PokeBattle_Move_0F0 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if NEWEST_BATTLE_MECHANICS && + target.item!=0 && !target.unlosableItem?(target.item) + # NOTE: Damage is still boosted even if target has Sticky Hold or a + # substitute. + baseDmg = (baseDmg*1.5).round + end + return baseDmg + end + + def pbEffectAfterAllHits(user,target) + return if @battle.wildBattle? && user.opposes? # Wild Pokémon can't knock off + return if user.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.item==0 || target.unlosableItem?(target.item) + return if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker + itemName = target.itemName + target.pbRemoveItem(false) + @battle.pbDisplay(_INTL("{1} dropped its {2}!",target.pbThis,itemName)) + end +end + + + +#=============================================================================== +# User steals the target's item, if the user has none itself. (Covet, Thief) +# Items stolen from wild Pokémon are kept after the battle. +#=============================================================================== +class PokeBattle_Move_0F1 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + return if @battle.wildBattle? && user.opposes? # Wild Pokémon can't thieve + return if user.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.item==0 || user.item!=0 + return if target.unlosableItem?(target.item) + return if user.unlosableItem?(target.item) + return if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker + itemName = target.itemName + user.item = target.item + # Permanently steal the item from wild Pokémon + if @battle.wildBattle? && target.opposes? && + target.initialItem==target.item && user.initialItem==0 + user.setInitialItem(target.item) + target.pbRemoveItem + else + target.pbRemoveItem(false) + end + @battle.pbDisplay(_INTL("{1} stole {2}'s {3}!",user.pbThis,target.pbThis(true),itemName)) + user.pbHeldItemTriggerCheck + end +end + + + +#=============================================================================== +# User and target swap items. They remain swapped after wild battles. +# (Switcheroo, Trick) +#=============================================================================== +class PokeBattle_Move_0F2 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.wildBattle? && user.opposes? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if user.item==0 && target.item==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.unlosableItem?(target.item) || + target.unlosableItem?(user.item) || + user.unlosableItem?(user.item) || + user.unlosableItem?(target.item) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("But it failed to affect {1}!",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("But it failed to affect {1} because of its {2}!", + target.pbThis(true),target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + oldUserItem = user.item; oldUserItemName = user.itemName + oldTargetItem = target.item; oldTargetItemName = target.itemName + user.item = oldTargetItem + user.effects[PBEffects::ChoiceBand] = -1 + user.effects[PBEffects::Unburden] = (user.item==0 && oldUserItem>0) + target.item = oldUserItem + target.effects[PBEffects::ChoiceBand] = -1 + target.effects[PBEffects::Unburden] = (target.item==0 && oldTargetItem>0) + # Permanently steal the item from wild Pokémon + if @battle.wildBattle? && target.opposes? && + target.initialItem==oldTargetItem && user.initialItem==0 + user.setInitialItem(oldTargetItem) + end + @battle.pbDisplay(_INTL("{1} switched items with its opponent!",user.pbThis)) + if oldUserItem>0 && oldTargetItem>0 + @battle.pbDisplay(_INTL("{1} obtained {2}.",user.pbThis,oldTargetItemName)) + elsif oldTargetItem>0 + @battle.pbDisplay(_INTL("{1} obtained {2}.",user.pbThis,oldTargetItemName)) + end + @battle.pbDisplay(_INTL("{1} obtained {2}.",target.pbThis,oldUserItemName)) if oldUserItem>0 + user.pbHeldItemTriggerCheck + target.pbHeldItemTriggerCheck + end +end + + + +#=============================================================================== +# User gives its item to the target. The item remains given after wild battles. +# (Bestow) +#=============================================================================== +class PokeBattle_Move_0F3 < PokeBattle_Move + def ignoresSubstitute?(user) + return true if NEWEST_BATTLE_MECHANICS + return super + end + + def pbMoveFailed?(user,targets) + if user.item==0 || user.unlosableItem?(user.item) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.item!=0 || target.unlosableItem?(user.item) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + itemName = user.itemName + target.item = user.item + # Permanently steal the item from wild Pokémon + if @battle.wildBattle? && user.opposes? && + user.initialItem==user.item && target.initialItem==0 + target.setInitialItem(user.item) + user.pbRemoveItem + else + user.pbRemoveItem(false) + end + @battle.pbDisplay(_INTL("{1} received {2} from {3}!",target.pbThis,itemName,user.pbThis(true))) + target.pbHeldItemTriggerCheck + end +end + + + +#=============================================================================== +# User consumes target's berry and gains its effect. (Bug Bite, Pluck) +#=============================================================================== +class PokeBattle_Move_0F4 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + return if user.fainted? || target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.item==0 || !pbIsBerry?(target.item) + return if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker + item = target.item + itemName = target.itemName + target.pbRemoveItem + @battle.pbDisplay(_INTL("{1} stole and ate its target's {2}!",user.pbThis,itemName)) + user.pbHeldItemTriggerCheck(item,false) + end +end + + + +#=============================================================================== +# Target's berry/Gem is destroyed. (Incinerate) +#=============================================================================== +class PokeBattle_Move_0F5 < PokeBattle_Move + def pbEffectWhenDealingDamage(user,target) + return if target.damageState.substitute || target.damageState.berryWeakened + return if !pbIsBerry?(target.item) && + !(NEWEST_BATTLE_MECHANICS && pbIsGem?(target.item)) + target.pbRemoveItem + @battle.pbDisplay(_INTL("{1}'s {2} was incinerated!",target.pbThis,target.itemName)) + end +end + + + +#=============================================================================== +# User recovers the last item it held and consumed. (Recycle) +#=============================================================================== +class PokeBattle_Move_0F6 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.recycleItem==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + item = user.recycleItem + user.item = item + user.setInitialItem(item) if @battle.wildBattle? && user.initialItem==0 + user.setRecycleItem(0) + user.effects[PBEffects::PickupItem] = 0 + user.effects[PBEffects::PickupUse] = 0 + itemName = PBItems.getName(item) + if itemName.starts_with_vowel? + @battle.pbDisplay(_INTL("{1} found an {2}!",user.pbThis,itemName)) + else + @battle.pbDisplay(_INTL("{1} found a {2}!",user.pbThis,itemName)) + end + user.pbHeldItemTriggerCheck + end +end + + + +#=============================================================================== +# User flings its item at the target. Power/effect depend on the item. (Fling) +#=============================================================================== +class PokeBattle_Move_0F7 < PokeBattle_Move + def initialize(battle,move) + super + # 80 => all Mega Stones + # 10 => all Berries + @flingPowers = { + 130 => [:IRONBALL + ], + 100 => [:HARDSTONE,:RAREBONE, + # Fossils + :ARMORFOSSIL,:CLAWFOSSIL,:COVERFOSSIL,:DOMEFOSSIL,:HELIXFOSSIL, + :JAWFOSSIL,:OLDAMBER,:PLUMEFOSSIL,:ROOTFOSSIL,:SAILFOSSIL, + :SKULLFOSSIL + ], + 90 => [:DEEPSEATOOTH,:GRIPCLAW,:THICKCLUB, + # Plates + :DRACOPLATE,:DREADPLATE,:EARTHPLATE,:FISTPLATE,:FLAMEPLATE, + :ICICLEPLATE,:INSECTPLATE,:IRONPLATE,:MEADOWPLATE,:MINDPLATE, + :PIXIEPLATE,:SKYPLATE,:SPLASHPLATE,:SPOOKYPLATE,:STONEPLATE, + :TOXICPLATE,:ZAPPLATE + ], + 80 => [:ASSAULTVEST,:DAWNSTONE,:DUSKSTONE,:ELECTIRIZER,:MAGMARIZER, + :ODDKEYSTONE,:OVALSTONE,:PROTECTOR,:QUICKCLAW,:RAZORCLAW,:SACHET, + :SAFETYGOGGLES,:SHINYSTONE,:STICKYBARB,:WEAKNESSPOLICY, + :WHIPPEDDREAM + ], + 70 => [:DRAGONFANG,:POISONBARB, + # EV-training items (Macho Brace is 60) + :POWERANKLET,:POWERBAND,:POWERBELT,:POWERBRACER,:POWERLENS, + :POWERWEIGHT, + # Drives + :BURNDRIVE,:CHILLDRIVE,:DOUSEDRIVE,:SHOCKDRIVE + ], + 60 => [:ADAMANTORB,:DAMPROCK,:GRISEOUSORB,:HEATROCK,:LUSTROUSORB, + :MACHOBRACE,:ROCKYHELMET,:STICK,:TERRAINEXTENDER + ], + 50 => [:DUBIOUSDISC,:SHARPBEAK, + # Memories + :BUGMEMORY,:DARKMEMORY,:DRAGONMEMORY,:ELECTRICMEMORY,:FAIRYMEMORY, + :FIGHTINGMEMORY,:FIREMEMORY,:FLYINGMEMORY,:GHOSTMEMORY, + :GRASSMEMORY,:GROUNDMEMORY,:ICEMEMORY,:POISONMEMORY, + :PSYCHICMEMORY,:ROCKMEMORY,:STEELMEMORY,:WATERMEMORY + ], + 40 => [:EVIOLITE,:ICYROCK,:LUCKYPUNCH + ], + 30 => [:ABSORBBULB,:ADRENALINEORB,:AMULETCOIN,:BINDINGBAND,:BLACKBELT, + :BLACKGLASSES,:BLACKSLUDGE,:BOTTLECAP,:CELLBATTERY,:CHARCOAL, + :CLEANSETAG,:DEEPSEASCALE,:DRAGONSCALE,:EJECTBUTTON,:ESCAPEROPE, + :EXPSHARE,:FLAMEORB,:FLOATSTONE,:FLUFFYTAIL,:GOLDBOTTLECAP, + :HEARTSCALE,:HONEY,:KINGSROCK,:LIFEORB,:LIGHTBALL,:LIGHTCLAY, + :LUCKYEGG,:LUMINOUSMOSS,:MAGNET,:METALCOAT,:METRONOME, + :MIRACLESEED,:MYSTICWATER,:NEVERMELTICE,:PASSORB,:POKEDOLL, + :POKETOY,:PRISMSCALE,:PROTECTIVEPADS,:RAZORFANG,:SACREDASH, + :SCOPELENS,:SHELLBELL,:SHOALSALT,:SHOALSHELL,:SMOKEBALL,:SNOWBALL, + :SOULDEW,:SPELLTAG,:TOXICORB,:TWISTEDSPOON,:UPGRADE, + # Healing items + :ANTIDOTE,:AWAKENING,:BERRYJUICE,:BIGMALASADA,:BLUEFLUTE, + :BURNHEAL,:CASTELIACONE,:ELIXIR,:ENERGYPOWDER,:ENERGYROOT,:ETHER, + :FRESHWATER,:FULLHEAL,:FULLRESTORE,:HEALPOWDER,:HYPERPOTION, + :ICEHEAL,:LAVACOOKIE,:LEMONADE,:LUMIOSEGALETTE,:MAXELIXIR, + :MAXETHER,:MAXPOTION,:MAXREVIVE,:MOOMOOMILK,:OLDGATEAU, + :PARALYZEHEAL,:PARLYZHEAL,:PEWTERCRUNCHIES,:POTION,:RAGECANDYBAR, + :REDFLUTE,:REVIVALHERB,:REVIVE,:SHALOURSABLE,:SODAPOP, + :SUPERPOTION,:SWEETHEART,:YELLOWFLUTE, + # Battle items + :XACCURACY,:XACCURACY2,:XACCURACY3,:XACCURACY6, + :XATTACK,:XATTACK2,:XATTACK3,:XATTACK6, + :XDEFEND,:XDEFEND2,:XDEFEND3,:XDEFEND6, + :XDEFENSE,:XDEFENSE2,:XDEFENSE3,:XDEFENSE6, + :XSPATK,:XSPATK2,:XSPATK3,:XSPATK6, + :XSPECIAL,:XSPECIAL2,:XSPECIAL3,:XSPECIAL6, + :XSPDEF,:XSPDEF2,:XSPDEF3,:XSPDEF6, + :XSPEED,:XSPEED2,:XSPEED3,:XSPEED6, + :DIREHIT,:DIREHIT2,:DIREHIT3, + :ABILITYURGE,:GUARDSPEC,:ITEMDROP,:ITEMURGE,:RESETURGE, + # Vitamins + :CALCIUM,:CARBOS,:HPUP,:IRON,:PPUP,:PPMAX,:PROTEIN,:ZINC, + :RARECANDY, + # Most evolution stones (see also 80) + :EVERSTONE,:FIRESTONE,:ICESTONE,:LEAFSTONE,:MOONSTONE,:SUNSTONE, + :THUNDERSTONE,:WATERSTONE, + # Repels + :MAXREPEL,:REPEL,:SUPERREPEL, + # Mulches + :AMAZEMULCH,:BOOSTMULCH,:DAMPMULCH,:GOOEYMULCH,:GROWTHMULCH, + :RICHMULCH,:STABLEMULCH,:SURPRISEMULCH, + # Shards + :BLUESHARD,:GREENSHARD,:REDSHARD,:YELLOWSHARD, + # Valuables + :BALMMUSHROOM,:BIGMUSHROOM,:BIGNUGGET,:BIGPEARL,:COMETSHARD, + :NUGGET,:PEARL,:PEARLSTRING,:RELICBAND,:RELICCOPPER,:RELICCROWN, + :RELICGOLD,:RELICSILVER,:RELICSTATUE,:RELICVASE,:STARDUST, + :STARPIECE,:STRANGESOUVENIR,:TINYMUSHROOM + ], + 20 => [# Wings + :CLEVERWING,:GENIUSWING,:HEALTHWING,:MUSCLEWING,:PRETTYWING, + :RESISTWING,:SWIFTWING + ], + 10 => [:AIRBALLOON,:BIGROOT,:BRIGHTPOWDER,:CHOICEBAND,:CHOICESCARF, + :CHOICESPECS,:DESTINYKNOT,:DISCOUNTCOUPON,:EXPERTBELT,:FOCUSBAND, + :FOCUSSASH,:LAGGINGTAIL,:LEFTOVERS,:MENTALHERB,:METALPOWDER, + :MUSCLEBAND,:POWERHERB,:QUICKPOWDER,:REAPERCLOTH,:REDCARD, + :RINGTARGET,:SHEDSHELL,:SILKSCARF,:SILVERPOWDER,:SMOOTHROCK, + :SOFTSAND,:SOOTHEBELL,:WHITEHERB,:WIDELENS,:WISEGLASSES,:ZOOMLENS, + # Terrain seeds + :ELECTRICSEED,:GRASSYSEED,:MISTYSEED,:PSYCHICSEED, + # Nectar + :PINKNECTAR,:PURPLENECTAR,:REDNECTAR,:YELLOWNECTAR, + # Incenses + :FULLINCENSE,:LAXINCENSE,:LUCKINCENSE,:ODDINCENSE,:PUREINCENSE, + :ROCKINCENSE,:ROSEINCENSE,:SEAINCENSE,:WAVEINCENSE, + # Scarves + :BLUESCARF,:GREENSCARF,:PINKSCARF,:REDSCARF,:YELLOWSCARF + ] + } + end + + def pbCheckFlingSuccess + @willFail = false + @willFail = true if user.item==0 || !user.itemActive? || user.unlosableItem?(user.item) + if pbIsBerry?(user.item) + @willFail = true if @battle.pbCheckOpposingAbility(:UNNERVE,user.index) + return + end + return if pbIsMegaStone?(user.item) + flingableItem = false + @flingPowers.each do |power,items| + items.each do |i| + next if !isConst?(user.item,PBItems,i) + flingableItem = true + break + end + break if flingableItem + end + @willFail = true if !flingableItem + end + + def pbMoveFailed?(user,targets) + if @willFail + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbDisplayUseMessage(user) + super + pbCheckFlingSuccess + if !@willFail + @battle.pbDisplay(_INTL("{1} flung its {2}!",user.pbThis,user.itemName)) + end + end + + def pbNumHits(user,targets); return 1; end + + def pbBaseDamage(baseDmg,user,target) + return 10 if pbIsBerry?(user.item) + return 80 if pbIsMegaStone?(user.item) + @flingPowers.each do |power,items| + items.each { |i| return power if isConst?(user.item,PBItems,i) } + end + return 10 + end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.substitute + return if target.hasActiveAbility?(:SHIELDDUST) && !@battle.moldBreaker + if isConst?(user.item,PBItems,:POISONBARB) + target.pbPoison(user) if target.pbCanPoison?(user,false,self) + elsif isConst?(user.item,PBItems,:TOXICORB) + target.pbPoison(user,nil,true) if target.pbCanPoison?(user,false,self) + elsif isConst?(user.item,PBItems,:FLAMEORB) + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + elsif isConst?(user.item,PBItems,:LIGHTBALL) + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + elsif isConst?(user.item,PBItems,:KINGSROCK) || + isConst?(user.item,PBItems,:RAZORFANG) + target.pbFlinch(user) + else + target.pbHeldItemTriggerCheck(user.item,true) + end + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + # NOTE: The item is consumed even if this move was Protected against or it + # missed. The item is not consumed if the target was switched out by + # an effect like a target's Red Card. + # NOTE: There is no item consumption animation. + user.pbConsumeItem(true,true,false) if user.item>0 + end +end + + + +#=============================================================================== +# For 5 rounds, the target cannnot use its held item, its held item has no +# effect, and no items can be used on it. (Embargo) +#=============================================================================== +class PokeBattle_Move_0F8 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Embargo]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Embargo] = 5 + @battle.pbDisplay(_INTL("{1} can't use items anymore!",target.pbThis)) + end +end + + + +#=============================================================================== +# For 5 rounds, all held items cannot be used in any way and have no effect. +# Held items can still change hands, but can't be thrown. (Magic Room) +#=============================================================================== +class PokeBattle_Move_0F9 < PokeBattle_Move + def pbEffectGeneral(user) + if @battle.field.effects[PBEffects::MagicRoom]>0 + @battle.field.effects[PBEffects::MagicRoom] = 0 + @battle.pbDisplay(_INTL("The area returned to normal!")) + else + @battle.field.effects[PBEffects::MagicRoom] = 5 + @battle.pbDisplay(_INTL("It created a bizarre area in which Pokémon's held items lose their effects!")) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if @battle.field.effects[PBEffects::MagicRoom]>0 # No animation + super + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/4 of the damage this move dealt. +#=============================================================================== +class PokeBattle_Move_0FA < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/4.0).round + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/3 of the damage this move dealt. +#=============================================================================== +class PokeBattle_Move_0FB < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/3.0).round + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/2 of the damage this move dealt. +# (Head Smash, Light of Ruin) +#=============================================================================== +class PokeBattle_Move_0FC < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/2.0).round + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/3 of the damage this move dealt. +# May paralyze the target. (Volt Tackle) +#=============================================================================== +class PokeBattle_Move_0FD < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/3.0).round + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/3 of the damage this move dealt. +# May burn the target. (Flare Blitz) +#=============================================================================== +class PokeBattle_Move_0FE < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/3.0).round + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + end +end + + + +#=============================================================================== +# Starts sunny weather. (Sunny Day) +#=============================================================================== +class PokeBattle_Move_0FF < PokeBattle_WeatherMove + def initialize(battle,move) + super + @weatherType = PBWeather::Sun + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/007_Move_Effects_100-17F.rb b/Data/Scripts/011_Battle/002_Move/007_Move_Effects_100-17F.rb new file mode 100644 index 000000000..32b32c07e --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/007_Move_Effects_100-17F.rb @@ -0,0 +1,2626 @@ +#=============================================================================== +# Starts rainy weather. (Rain Dance) +#=============================================================================== +class PokeBattle_Move_100 < PokeBattle_WeatherMove + def initialize(battle,move) + super + @weatherType = PBWeather::Rain + end +end + + + +#=============================================================================== +# Starts sandstorm weather. (Sandstorm) +#=============================================================================== +class PokeBattle_Move_101 < PokeBattle_WeatherMove + def initialize(battle,move) + super + @weatherType = PBWeather::Sandstorm + end +end + + + +#=============================================================================== +# Starts hail weather. (Hail) +#=============================================================================== +class PokeBattle_Move_102 < PokeBattle_WeatherMove + def initialize(battle,move) + super + @weatherType = PBWeather::Hail + end +end + + + +#=============================================================================== +# Entry hazard. Lays spikes on the opposing side (max. 3 layers). (Spikes) +#=============================================================================== +class PokeBattle_Move_103 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOpposingSide.effects[PBEffects::Spikes]>=3 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOpposingSide.effects[PBEffects::Spikes] += 1 + @battle.pbDisplay(_INTL("Spikes were scattered all around {1}'s feet!", + user.pbOpposingTeam(true))) + end +end + + + +#=============================================================================== +# Entry hazard. Lays poison spikes on the opposing side (max. 2 layers). +# (Toxic Spikes) +#=============================================================================== +class PokeBattle_Move_104 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOpposingSide.effects[PBEffects::ToxicSpikes]>=2 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOpposingSide.effects[PBEffects::ToxicSpikes] += 1 + @battle.pbDisplay(_INTL("Poison spikes were scattered all around {1}'s feet!", + user.pbOpposingTeam(true))) + end +end + + + +#=============================================================================== +# Entry hazard. Lays stealth rocks on the opposing side. (Stealth Rock) +#=============================================================================== +class PokeBattle_Move_105 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOpposingSide.effects[PBEffects::StealthRock] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOpposingSide.effects[PBEffects::StealthRock] = true + @battle.pbDisplay(_INTL("Pointed stones float in the air around {1}!", + user.pbOpposingTeam(true))) + end +end + + + +#=============================================================================== +# Combos with another Pledge move used by the ally. (Grass Pledge) +# If the move is a combo, power is doubled and causes either a sea of fire or a +# swamp on the opposing side. +#=============================================================================== +class PokeBattle_Move_106 < PokeBattle_PledgeMove + def initialize(battle,move) + super + # [Function code to combo with, effect, override type, override animation] + @combos = [["107",:SeaOfFire,getConst(PBTypes,:FIRE),getConst(PBMoves,:FIREPLEDGE)], + ["108",:Swamp,nil,nil]] + end +end + + + +#=============================================================================== +# Combos with another Pledge move used by the ally. (Fire Pledge) +# If the move is a combo, power is doubled and causes either a rainbow on the +# user's side or a sea of fire on the opposing side. +#=============================================================================== +class PokeBattle_Move_107 < PokeBattle_PledgeMove + def initialize(battle,move) + super + # [Function code to combo with, effect, override type, override animation] + @combos = [["108",:Rainbow,getConst(PBTypes,:WATER),getConst(PBMoves,:WATERPLEDGE)], + ["106",:SeaOfFire,nil,nil]] + end +end + + + +#=============================================================================== +# Combos with another Pledge move used by the ally. (Water Pledge) +# If the move is a combo, power is doubled and causes either a swamp on the +# opposing side or a rainbow on the user's side. +#=============================================================================== +class PokeBattle_Move_108 < PokeBattle_PledgeMove + def initialize(battle,move) + super + # [Function code to combo with, effect, override type, override animation] + @combos = [["106",:Swamp,getConst(PBTypes,:GRASS),getConst(PBMoves,:GRASSPLEDGE)], + ["107",:Rainbow,nil,nil]] + end +end + + + +#=============================================================================== +# Scatters coins that the player picks up after winning the battle. (Pay Day) +# NOTE: In Gen 6+, if the user levels up after this move is used, the amount of +# money picked up depends on the user's new level rather than its level +# when it used the move. I think this is silly, so I haven't coded this +# effect. +#=============================================================================== +class PokeBattle_Move_109 < PokeBattle_Move + def pbEffectGeneral(user) + if user.pbOwnedByPlayer? + @battle.field.effects[PBEffects::PayDay] += 5*user.level + end + @battle.pbDisplay(_INTL("Coins were scattered everywhere!")) + end +end + + + +#=============================================================================== +# Ends the opposing side's Light Screen, Reflect and Aurora Break. (Brick Break, +# Psychic Fangs) +#=============================================================================== +class PokeBattle_Move_10A < PokeBattle_Move + def ignoresReflect?; return true; end + + def pbEffectGeneral(user) + if user.pbOpposingSide.effects[PBEffects::LightScreen]>0 + user.pbOpposingSide.effects[PBEffects::LightScreen] = 0 + @battle.pbDisplayP(_INTL("{1}'s Light Screen wore off!",user.pbOpposingTeam)) + end + if user.pbOpposingSide.effects[PBEffects::Reflect]>0 + user.pbOpposingSide.effects[PBEffects::Reflect] = 0 + @battle.pbDisplay(_INTL("{1}'s Reflect wore off!",user.pbOpposingTeam)) + end + if user.pbOpposingSide.effects[PBEffects::AuroraVeil]>0 + user.pbOpposingSide.effects[PBEffects::AuroraVeil] = 0 + @battle.pbDisplay(_INTL("{1}'s Aurora Veil wore off!",user.pbOpposingTeam)) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + if user.pbOpposingSide.effects[PBEffects::LightScreen]>0 || + user.pbOpposingSide.effects[PBEffects::Reflect]>0 || + user.pbOpposingSide.effects[PBEffects::AuroraVeil]>0 + hitNum = 1 # Wall-breaking anim + end + super + end +end + + + +#=============================================================================== +# If attack misses, user takes crash damage of 1/2 of max HP. +# (High Jump Kick, Jump Kick) +#=============================================================================== +class PokeBattle_Move_10B < PokeBattle_Move + def recoilMove?; return true; end + def unusableInGravity?; return true; end + + def pbCrashDamage(user) + return if !user.takesIndirectDamage? + @battle.pbDisplay(_INTL("{1} kept going and crashed!",user.pbThis)) + @battle.scene.pbDamageAnimation(user) + user.pbReduceHP(user.totalhp/2,false) + user.pbItemHPHealCheck + user.pbFaint if user.fainted? + end +end + + + +#=============================================================================== +# User turns 1/4 of max HP into a substitute. (Substitute) +#=============================================================================== +class PokeBattle_Move_10C < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Substitute]>0 + @battle.pbDisplay(_INTL("{1} already has a substitute!",user.pbThis)) + return true + end + @subLife = user.totalhp/4 + @subLife = 1 if @subLife<1 + if user.hp<=@subLife + @battle.pbDisplay(_INTL("But it does not have enough HP left to make a substitute!")) + return true + end + return false + end + + def pbOnStartUse(user,targets) + user.pbReduceHP(@subLife,false,false) + user.pbItemHPHealCheck + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Trapping] = 0 + user.effects[PBEffects::TrappingMove] = 0 + user.effects[PBEffects::Substitute] = @subLife + @battle.pbDisplay(_INTL("{1} put in a substitute!",user.pbThis)) + end +end + + + +#=============================================================================== +# User is Ghost: User loses 1/2 of max HP, and curses the target. +# Cursed Pokémon lose 1/4 of their max HP at the end of each round. +# User is not Ghost: Decreases the user's Speed by 1 stage, and increases the +# user's Attack and Defense by 1 stage each. (Curse) +#=============================================================================== +class PokeBattle_Move_10D < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbTarget(user) + return PBTargets::NearFoe if user.pbHasType?(:GHOST) + super + end + + def pbMoveFailed?(user,targets) + return false if user.pbHasType?(:GHOST) + if !user.pbCanLowerStatStage?(PBStats::SPEED,user,self) && + !user.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) && + !user.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if user.pbHasType?(:GHOST) && target.effects[PBEffects::Curse] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + return if user.pbHasType?(:GHOST) + # Non-Ghost effect + if user.pbCanLowerStatStage?(PBStats::SPEED,user,self) + user.pbLowerStatStage(PBStats::SPEED,1,user) + end + showAnim = true + if user.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + if user.pbRaiseStatStage(PBStats::ATTACK,1,user,showAnim) + showAnim = false + end + end + if user.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + user.pbRaiseStatStage(PBStats::DEFENSE,1,user,showAnim) + end + end + + def pbEffectAgainstTarget(user,target) + return if !user.pbHasType?(:GHOST) + # Ghost effect + @battle.pbDisplay(_INTL("{1} cut its own HP and laid a curse on {2}!",user.pbThis,target.pbThis(true))) + target.effects[PBEffects::Curse] = true + user.pbReduceHP(user.totalhp/2,false) + user.pbItemHPHealCheck + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if !user.pbHasType?(:GHOST) # Non-Ghost anim + super + end +end + + + +#=============================================================================== +# Target's last move used loses 4 PP. (Spite) +#=============================================================================== +class PokeBattle_Move_10E < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + failed = true + target.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed || m.pp==0 || m.totalpp<=0 + failed = false; break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + reduction = [4,m.pp].min + target.pbSetPP(m,m.pp-reduction) + @battle.pbDisplay(_INTL("It reduced the PP of {1}'s {2} by {3}!", + target.pbThis(true),m.name,reduction)) + break + end + end +end + + + +#=============================================================================== +# Target will lose 1/4 of max HP at end of each round, while asleep. (Nightmare) +#=============================================================================== +class PokeBattle_Move_10F < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !target.asleep? || target.effects[PBEffects::Nightmare] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Nightmare] = true + @battle.pbDisplay(_INTL("{1} began having a nightmare!",target.pbThis)) + end +end + + + +#=============================================================================== +# Removes trapping moves, entry hazards and Leech Seed on user/user's side. +# (Rapid Spin) +#=============================================================================== +class PokeBattle_Move_110 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + return if user.fainted? || target.damageState.unaffected + if user.effects[PBEffects::Trapping]>0 + trapMove = PBMoves.getName(user.effects[PBEffects::TrappingMove]) + trapUser = @battle.battlers[user.effects[PBEffects::TrappingUser]] + @battle.pbDisplay(_INTL("{1} got free of {2}'s {3}!",user.pbThis,trapUser.pbThis(true),trapMove)) + user.effects[PBEffects::Trapping] = 0 + user.effects[PBEffects::TrappingMove] = 0 + user.effects[PBEffects::TrappingUser] = -1 + end + if user.effects[PBEffects::LeechSeed]>=0 + user.effects[PBEffects::LeechSeed] = -1 + @battle.pbDisplay(_INTL("{1} shed Leech Seed!",user.pbThis)) + end + if user.pbOwnSide.effects[PBEffects::StealthRock] + user.pbOwnSide.effects[PBEffects::StealthRock] = false + @battle.pbDisplay(_INTL("{1} blew away stealth rocks!",user.pbThis)) + end + if user.pbOwnSide.effects[PBEffects::Spikes]>0 + user.pbOwnSide.effects[PBEffects::Spikes] = 0 + @battle.pbDisplay(_INTL("{1} blew away spikes!",user.pbThis)) + end + if user.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 + user.pbOwnSide.effects[PBEffects::ToxicSpikes] = 0 + @battle.pbDisplay(_INTL("{1} blew away poison spikes!",user.pbThis)) + end + if user.pbOwnSide.effects[PBEffects::StickyWeb] + user.pbOwnSide.effects[PBEffects::StickyWeb] = false + @battle.pbDisplay(_INTL("{1} blew away sticky webs!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Attacks 2 rounds in the future. (Doom Desire, Future Sight) +#=============================================================================== +class PokeBattle_Move_111 < PokeBattle_Move + def cannotRedirect?; return true; end + + def pbDamagingMove? # Stops damage being dealt in the setting-up turn + return false if !@battle.futureSight + return super + end + + def pbAccuracyCheck(user,target) + return true if !@battle.futureSight + return super + end + + def pbDisplayUseMessage(user) + super if !@battle.futureSight + end + + def pbFailsAgainstTarget?(user,target) + if !@battle.futureSight && + @battle.positions[target.index].effects[PBEffects::FutureSightCounter]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if @battle.futureSight # Attack is hitting + effects = @battle.positions[target.index].effects + effects[PBEffects::FutureSightCounter] = 3 + effects[PBEffects::FutureSightMove] = @id + effects[PBEffects::FutureSightUserIndex] = user.index + effects[PBEffects::FutureSightUserPartyIndex] = user.pokemonIndex + if isConst?(@id,PBMoves,:DOOMDESIRE) + @battle.pbDisplay(_INTL("{1} chose Doom Desire as its destiny!",user.pbThis)) + else + @battle.pbDisplay(_INTL("{1} foresaw an attack!",user.pbThis)) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if !@battle.futureSight # Charging anim + super + end +end + + + +#=============================================================================== +# Increases the user's Defense and Special Defense by 1 stage each. Ups the +# user's stockpile by 1 (max. 3). (Stockpile) +#=============================================================================== +class PokeBattle_Move_112 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Stockpile]>=3 + @battle.pbDisplay(_INTL("{1} can't stockpile any more!",user.pbThis)) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Stockpile] += 1 + @battle.pbDisplay(_INTL("{1} stockpiled {2}!", + user.pbThis,user.effects[PBEffects::Stockpile])) + showAnim = true + if user.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + if user.pbRaiseStatStage(PBStats::DEFENSE,1,user,showAnim) + user.effects[PBEffects::StockpileDef] += 1 + showAnim = false + end + end + if user.pbCanRaiseStatStage?(PBStats::SPDEF,user,self) + if user.pbRaiseStatStage(PBStats::SPDEF,1,user,showAnim) + user.effects[PBEffects::StockpileSpDef] += 1 + end + end + end +end + + + +#=============================================================================== +# Power is 100 multiplied by the user's stockpile (X). Resets the stockpile to +# 0. Decreases the user's Defense and Special Defense by X stages each. (Spit Up) +#=============================================================================== +class PokeBattle_Move_113 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Stockpile]==0 + @battle.pbDisplay(_INTL("But it failed to spit up a thing!")) + return true + end + return false + end + + def pbBaseDamage(baseDmg,user,target) + return 100*user.effects[PBEffects::Stockpile] + end + + def pbEffectAfterAllHits(user,target) + return if user.fainted? || user.effects[PBEffects::Stockpile]==0 + return if target.damageState.unaffected + @battle.pbDisplay(_INTL("{1}'s stockpiled effect wore off!",user.pbThis)) + return if @battle.pbAllFainted?(target.idxOwnSide) + showAnim = true + if user.effects[PBEffects::StockpileDef]>0 && + user.pbCanLowerStatStage?(PBStats::DEFENSE,user,self) + if user.pbLowerStatStage(PBStats::DEFENSE,user.effects[PBEffects::StockpileDef],user,showAnim) + showAnim = false + end + end + if user.effects[PBEffects::StockpileSpDef]>0 && + user.pbCanLowerStatStage?(PBStats::SPDEF,user,self) + user.pbLowerStatStage(PBStats::SPDEF,user.effects[PBEffects::StockpileSpDef],user,showAnim) + end + user.effects[PBEffects::Stockpile] = 0 + user.effects[PBEffects::StockpileDef] = 0 + user.effects[PBEffects::StockpileSpDef] = 0 + end +end + + + +#=============================================================================== +# Heals user depending on the user's stockpile (X). Resets the stockpile to 0. +# Decreases the user's Defense and Special Defense by X stages each. (Swallow) +#=============================================================================== +class PokeBattle_Move_114 < PokeBattle_Move + def healingMove?; return true; end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Stockpile]==0 + @battle.pbDisplay(_INTL("But it failed to swallow a thing!")) + return true + end + if !user.canHeal? && + user.effects[PBEffects::StockpileDef]==0 && + user.effects[PBEffects::StockpileSpDef]==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + hpGain = 0 + case [user.effects[PBEffects::Stockpile],1].max + when 1; hpGain = user.totalhp/4 + when 2; hpGain = user.totalhp/2 + when 3; hpGain = user.totalhp + end + if user.pbRecoverHP(hpGain)>0 + @battle.pbDisplay(_INTL("{1}'s HP was restored.",user.pbThis)) + end + @battle.pbDisplay(_INTL("{1}'s stockpiled effect wore off!",user.pbThis)) + showAnim = true + if user.effects[PBEffects::StockpileDef]>0 && + user.pbCanLowerStatStage?(PBStats::DEFENSE,user,self) + if user.pbLowerStatStage(PBStats::DEFENSE,user.effects[PBEffects::StockpileDef],user,showAnim) + showAnim = false + end + end + if user.effects[PBEffects::StockpileSpDef]>0 && + user.pbCanLowerStatStage?(PBStats::SPDEF,user,self) + user.pbLowerStatStage(PBStats::SPDEF,user.effects[PBEffects::StockpileSpDef],user,showAnim) + end + user.effects[PBEffects::Stockpile] = 0 + user.effects[PBEffects::StockpileDef] = 0 + user.effects[PBEffects::StockpileSpDef] = 0 + end +end + + + +#=============================================================================== +# Fails if user was hit by a damaging move this round. (Focus Punch) +#=============================================================================== +class PokeBattle_Move_115 < PokeBattle_Move + def pbDisplayChargeMessage(user) + user.effects[PBEffects::FocusPunch] = true + @battle.pbCommonAnimation("FocusPunch",user) + @battle.pbDisplay(_INTL("{1} is tightening its focus!",user.pbThis)) + end + + def pbDisplayUseMessage(user) + super if !user.effects[PBEffects::FocusPunch] || user.lastHPLost==0 + end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::FocusPunch] && user.lastHPLost>0 + @battle.pbDisplay(_INTL("{1} lost its focus and couldn't move!",user.pbThis)) + return true + end + return false + end +end + + + +#=============================================================================== +# Fails if the target didn't chose a damaging move to use this round, or has +# already moved. (Sucker Punch) +#=============================================================================== +class PokeBattle_Move_116 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if @battle.choices[target.index][0]!=:UseMove + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + oppMove = @battle.choices[target.index][2] + if !oppMove || oppMove.id<=0 || + (oppMove.function!="0B0" && # Me First + (target.movedThisRound? || oppMove.statusMove?)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# This round, user becomes the target of attacks that have single targets. +# (Follow Me, Rage Powder) +#=============================================================================== +class PokeBattle_Move_117 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::FollowMe] = 1 + user.eachAlly do |b| + next if b.effects[PBEffects::FollowMe]0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::Gravity] = 5 + @battle.pbDisplay(_INTL("Gravity intensified!")) + @battle.eachBattler do |b| + showMessage = false + if b.inTwoTurnAttack?("0C9","0CC","0CE") # Fly/Bounce/Sky Drop + b.effects[PBEffects::TwoTurnAttack] = 0 + @battle.pbClearChoice(b.index) if !b.movedThisRound? + showMessage = true + end + if b.effects[PBEffects::MagnetRise]>0 || + b.effects[PBEffects::Telekinesis]>0 || + b.effects[PBEffects::SkyDrop]>=0 + b.effects[PBEffects::MagnetRise] = 0 + b.effects[PBEffects::Telekinesis] = 0 + b.effects[PBEffects::SkyDrop] = -1 + showMessage = true + end + @battle.pbDisplay(_INTL("{1} couldn't stay airborne because of gravity!", + b.pbThis)) if showMessage + end + end +end + + + +#=============================================================================== +# For 5 rounds, user becomes airborne. (Magnet Rise) +#=============================================================================== +class PokeBattle_Move_119 < PokeBattle_Move + def unusableInGravity?; return true; end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Ingrain] || + user.effects[PBEffects::SmackDown] || + user.effects[PBEffects::MagnetRise]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::MagnetRise] = 5 + @battle.pbDisplay(_INTL("{1} levitated with electromagnetism!",user.pbThis)) + end +end + + + +#=============================================================================== +# For 3 rounds, target becomes airborne and can always be hit. (Telekinesis) +#=============================================================================== +class PokeBattle_Move_11A < PokeBattle_Move + def unusableInGravity?; return true; end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Ingrain] || + target.effects[PBEffects::SmackDown] || + target.effects[PBEffects::Telekinesis]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if isConst?(target.species,PBSpecies,:DIGLETT) || + isConst?(target.species,PBSpecies,:DUGTRIO) || + isConst?(target.species,PBSpecies,:SANDYGAST) || + isConst?(target.species,PBSpecies,:PALOSSAND) || + (isConst?(target.species,PBSpecies,:GENGAR) && target.mega?) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Telekinesis] = 3 + @battle.pbDisplay(_INTL("{1} was hurled into the air!",target.pbThis)) + end +end + + + +#=============================================================================== +# Hits airborne semi-invulnerable targets. (Sky Uppercut) +#=============================================================================== +class PokeBattle_Move_11B < PokeBattle_Move + def hitsFlyingTargets?; return true; end +end + + + +#=============================================================================== +# Grounds the target while it remains active. Hits some semi-invulnerable +# targets. (Smack Down, Thousand Arrows) +#=============================================================================== +class PokeBattle_Move_11C < PokeBattle_Move + def hitsFlyingTargets?; return true; end + + def pbCalcTypeModSingle(moveType,defType,user,target) + return PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(moveType,PBTypes,:GROUND) && + isConst?(defType,PBTypes,:FLYING) + return super + end + + def pbEffectAfterAllHits(user,target) + return if target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.inTwoTurnAttack?("0CE") || target.effects[PBEffects::SkyDrop]>=0 # Sky Drop + return if !target.airborne? && !target.inTwoTurnAttack?("0C9","0CC") # Fly/Bounce + target.effects[PBEffects::SmackDown] = true + if target.inTwoTurnAttack?("0C9","0CC") # Fly/Bounce. NOTE: Not Sky Drop. + target.effects[PBEffects::TwoTurnAttack] = 0 + @battle.pbClearChoice(target.index) if !target.movedThisRound? + end + target.effects[PBEffects::MagnetRise] = 0 + target.effects[PBEffects::Telekinesis] = 0 + @battle.pbDisplay(_INTL("{1} fell straight down!",target.pbThis)) + end +end + + + +#=============================================================================== +# Target moves immediately after the user, ignoring priority/speed. (After You) +#=============================================================================== +class PokeBattle_Move_11D < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + # Target has already moved this round + return true if pbMoveFailedTargetAlreadyMoved?(target) + # Target was going to move next anyway (somehow) + if target.effects[PBEffects::MoveNext] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + # Target didn't choose to use a move this round + oppMove = @battle.choices[target.index][2] + if !oppMove || oppMove.id<=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::MoveNext] = true + target.effects[PBEffects::Quash] = 0 + @battle.pbDisplay(_INTL("{1} took the kind offer!",target.pbThis)) + end +end + + + +#=============================================================================== +# Target moves last this round, ignoring priority/speed. (Quash) +#=============================================================================== +class PokeBattle_Move_11E < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return true if pbMoveFailedTargetAlreadyMoved?(target) + # Target isn't going to use a move + oppMove = @battle.choices[target.index][2] + if !oppMove || oppMove.id<=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + # Target is already maximally Quashed and will move last anyway + highestQuash = 0 + @battle.battlers.each do |b| + next if b.effects[PBEffects::Quash]<=highestQuash + highestQuash = b.effects[PBEffects::Quash] + end + if highestQuash>0 && target.effects[PBEffects::Quash]==highestQuash + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + # Target was already going to move last + if highestQuash==0 && @battle.pbPriority.last.index==target.index + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + highestQuash = 0 + @battle.battlers.each do |b| + next if b.effects[PBEffects::Quash]<=highestQuash + highestQuash = b.effects[PBEffects::Quash] + end + target.effects[PBEffects::Quash] = highestQuash+1 + target.effects[PBEffects::MoveNext] = false + @battle.pbDisplay(_INTL("{1}'s move was postponed!",target.pbThis)) + end +end + + + +#=============================================================================== +# For 5 rounds, for each priority bracket, slow Pokémon move before fast ones. +# (Trick Room) +#=============================================================================== +class PokeBattle_Move_11F < PokeBattle_Move + def pbEffectGeneral(user) + if @battle.field.effects[PBEffects::TrickRoom]>0 + @battle.field.effects[PBEffects::TrickRoom] = 0 + @battle.pbDisplay(_INTL("{1} reverted the dimensions!",user.pbThis)) + else + @battle.field.effects[PBEffects::TrickRoom] = 5 + @battle.pbDisplay(_INTL("{1} twisted the dimensions!",user.pbThis)) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if @battle.field.effects[PBEffects::TrickRoom]>0 # No animation + super + end +end + + + +#=============================================================================== +# User switches places with its ally. (Ally Switch) +#=============================================================================== +class PokeBattle_Move_120 < PokeBattle_Move + def pbMoveFailed?(user,targets) + numTargets = 0 + @idxAlly = -1 + idxUserOwner = @battle.pbGetOwnerIndexFromBattlerIndex(user.index) + user.eachAlly do |b| + next if @battle.pbGetOwnerIndexFromBattlerIndex(b.index)!=idxUserOwner + next if !b.near?(user) + numTargets += 1 + @idxAlly = b.index + end + if numTargets!=1 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + idxA = user.index + idxB = @idxAlly + if @battle.pbSwapBattlers(idxA,idxB) + @battle.pbDisplay(_INTL("{1} and {2} switched places!", + @battle.battlers[idxB].pbThis,@battle.battlers[idxA].pbThis(true))) + end + end +end + + + +#=============================================================================== +# Target's Attack is used instead of user's Attack for this move's calculations. +# (Foul Play) +#=============================================================================== +class PokeBattle_Move_121 < PokeBattle_Move + def pbGetAttackStats(user,target) + if specialMove? + return target.spatk, target.stages[PBStats::SPATK]+6 + end + return target.attack, target.stages[PBStats::ATTACK]+6 + end +end + + + +#=============================================================================== +# Target's Defense is used instead of its Special Defense for this move's +# calculations. (Psyshock, Psystrike, Secret Sword) +#=============================================================================== +class PokeBattle_Move_122 < PokeBattle_Move + def pbGetDefenseStats(user,target) + return target.defense, target.stages[PBStats::DEFENSE]+6 + end +end + + + +#=============================================================================== +# Only damages Pokémon that share a type with the user. (Synchronoise) +#=============================================================================== +class PokeBattle_Move_123 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + userTypes = user.pbTypes(true) + targetTypes = target.pbTypes(true) + sharesType = false + userTypes.each do |t| + next if !targetTypes.include?(t) + sharesType = true + break + end + if !sharesType + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + return false + end +end + + + +#=============================================================================== +# For 5 rounds, swaps all battlers' base Defense with base Special Defense. +# (Wonder Room) +#=============================================================================== +class PokeBattle_Move_124 < PokeBattle_Move + def pbEffectGeneral(user) + if @battle.field.effects[PBEffects::WonderRoom]>0 + @battle.field.effects[PBEffects::WonderRoom] = 0 + @battle.pbDisplay(_INTL("Wonder Room wore off, and the Defense and Sp. Def stats returned to normal!")) + else + @battle.field.effects[PBEffects::WonderRoom] = 5 + @battle.pbDisplay(_INTL("It created a bizarre area in which the Defense and Sp. Def stats are swapped!")) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if @battle.field.effects[PBEffects::WonderRoom]>0 # No animation + super + end +end + + + +#=============================================================================== +# Fails unless user has already used all other moves it knows. (Last Resort) +#=============================================================================== +class PokeBattle_Move_125 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + hasThisMove = false; hasOtherMoves = false; hasUnusedMoves = false + user.eachMove do |m| + hasThisMove = true if m.id==@id + hasOtherMoves = true if m.id!=@id + hasUnusedMoves = true if m.id!=@id && !user.movesUsed.include?(m.id) + end + if !hasThisMove || !hasOtherMoves || hasUnusedMoves + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# NOTE: Shadow moves use function codes 126-132 inclusive. +#=============================================================================== + + + +#=============================================================================== +# Does absolutely nothing. (Hold Hands) +#=============================================================================== +class PokeBattle_Move_133 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + hasAlly = false + user.eachAlly do |b| + hasAlly = true + break + end + if !hasAlly + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Does absolutely nothing. Shows a special message. (Celebrate) +#=============================================================================== +class PokeBattle_Move_134 < PokeBattle_Move + def pbEffectGeneral(user) + if @battle.wildBattle? && user.opposes? + @battle.pbDisplay(_INTL("Congratulations from {1}!",user.pbThis(true))) + else + @battle.pbDisplay(_INTL("Congratulations, {1}!",@battle.pbGetOwnerName(user.index))) + end + end +end + + + +#=============================================================================== +# Freezes the target. Effectiveness against Water-type is 2x. (Freeze-Dry) +#=============================================================================== +class PokeBattle_Move_135 < PokeBattle_FreezeMove + def pbCalcTypeModSingle(moveType,defType,user,target) + return PBTypeEffectiveness::SUPER_EFFECTIVE_ONE if isConst?(defType,PBTypes,:WATER) + return super + end +end + + + +#=============================================================================== +# Increases the user's Defense by 2 stages. (Diamond Storm) +#=============================================================================== +class PokeBattle_Move_136 < PokeBattle_Move_02F + # NOTE: In Gen 6, this move increased the user's Defense by 1 stage for each + # target it hit. This effect changed in Gen 7 and is now identical to + # function code 02F. +end + + + +#=============================================================================== +# Increases the user's and its ally's Defense and Special Defense by 1 stage +# each, if they have Plus or Minus. (Magnetic Flux) +#=============================================================================== +# NOTE: In Gen 5, this move should have a target of UserSide, while in Gen 6+ it +# should have a target of UserAndAllies. This is because, in Gen 5, this +# move shouldn't call def pbSuccessCheckAgainstTarget for each Pokémon +# currently in battle that will be affected by this move (i.e. allies +# aren't protected by their substitute/ability/etc., but they are in Gen +# 6+). We achieve this by not targeting any battlers in Gen 5, since +# pbSuccessCheckAgainstTarget is only called for targeted battlers. +class PokeBattle_Move_137 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + @validTargets = [] + @battle.eachSameSideBattler(user) do |b| + next if !b.hasActiveAbility?([:MINUS,:PLUS]) + next if !b.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) && + !b.pbCanRaiseStatStage?(PBStats::SPDEF,user,self) + @validTargets.push(b) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return false if @validTargets.any? { |b| b.index==target.index } + return true if !target.hasActiveAbility?([:MINUS,:PLUS]) + @battle.pbDisplay(_INTL("{1}'s stats can't be raised further!",target.pbThis)) + return true + end + + + def pbEffectAgainstTarget(user,target) + showAnim = true + if target.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + if target.pbRaiseStatStage(PBStats::DEFENSE,1,user,showAnim) + showAnim = false + end + end + if target.pbCanRaiseStatStage?(PBStats::SPDEF,user,self) + target.pbRaiseStatStage(PBStats::SPDEF,1,user,showAnim) + end + end + + def pbEffectGeneral(user) + return if pbTarget(user)==PBTargets::UserAndAllies + @validTargets.each { |b| pbEffectAgainstTarget(user,b) } + end +end + + + +#=============================================================================== +# Increases target's Special Defense by 1 stage. (Aromatic Mist) +#=============================================================================== +class PokeBattle_Move_138 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + return true if !target.pbCanRaiseStatStage?(PBStats::SPDEF,user,self,true) + return false + end + + def pbEffectAgainstTarget(user,target) + target.pbRaiseStatStage(PBStats::SPDEF,1,user) + end +end + + + +#=============================================================================== +# Decreases the target's Attack by 1 stage. Always hits. (Play Nice) +#=============================================================================== +class PokeBattle_Move_139 < PokeBattle_TargetStatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1] + end + + def pbAccuracyCheck(user,target); return true; end +end + + + +#=============================================================================== +# Decreases the target's Attack and Special Attack by 1 stage each. Always hits. +# (Noble Roar) +#=============================================================================== +class PokeBattle_Move_13A < PokeBattle_TargetMultiStatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1,PBStats::SPATK,1] + end + + def pbAccuracyCheck(user,target); return true; end +end + + + +#=============================================================================== +# Decreases the user's Defense by 1 stage. Always hits. Ends target's +# protections immediately. (Hyperspace Fury) +#=============================================================================== +class PokeBattle_Move_13B < PokeBattle_StatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,1] + end + + def pbMoveFailed?(user,targets) + if !isConst?(user.species,PBSpecies,:HOOPA) + @battle.pbDisplay(_INTL("But {1} can't use the move!",user.pbThis(true))) + return true + elsif user.form!=1 + @battle.pbDisplay(_INTL("But {1} can't use it the way it is now!",user.pbThis(true))) + return true + end + return false + end + + def pbAccuracyCheck(user,target); return true; end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::BanefulBunker] = false + target.effects[PBEffects::KingsShield] = false + target.effects[PBEffects::Protect] = false + target.effects[PBEffects::SpikyShield] = false + target.pbOwnSide.effects[PBEffects::CraftyShield] = false + target.pbOwnSide.effects[PBEffects::MatBlock] = false + target.pbOwnSide.effects[PBEffects::QuickGuard] = false + target.pbOwnSide.effects[PBEffects::WideGuard] = false + end +end + + + +#=============================================================================== +# Decreases the target's Special Attack by 1 stage. Always hits. (Confide) +#=============================================================================== +class PokeBattle_Move_13C < PokeBattle_TargetStatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,1] + end + + def pbAccuracyCheck(user,target); return true; end +end + + + +#=============================================================================== +# Decreases the target's Special Attack by 2 stages. (Eerie Impulse) +#=============================================================================== +class PokeBattle_Move_13D < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,2] + end +end + + + +#=============================================================================== +# Increases the Attack and Special Attack of all Grass-type Pokémon in battle by +# 1 stage each. Doesn't affect airborne Pokémon. (Rototiller) +#=============================================================================== +class PokeBattle_Move_13E < PokeBattle_Move + def pbMoveFailed?(user,targets) + @validTargets = [] + @battle.eachBattler do |b| + next if !b.pbHasType?(:GRASS) + next if b.airborne? || b.semiInvulnerable? + next if !b.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) && + !b.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + @validTargets.push(b.index) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return false if @validTargets.include?(target.index) + return true if !target.pbHasType?(:GRASS) + return true if target.airborne? || target.semiInvulnerable? + @battle.pbDisplay(_INTL("{1}'s stats can't be raised further!",target.pbThis)) + return true + end + + def pbEffectAgainstTarget(user,target) + showAnim = true + if target.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + if target.pbRaiseStatStage(PBStats::ATTACK,1,user,showAnim) + showAnim = false + end + end + if target.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + target.pbRaiseStatStage(PBStats::SPATK,1,user,showAnim) + end + end +end + + + +#=============================================================================== +# Increases the Defense of all Grass-type Pokémon on the field by 1 stage each. +# (Flower Shield) +#=============================================================================== +class PokeBattle_Move_13F < PokeBattle_Move + def pbMoveFailed?(user,targets) + @validTargets = [] + @battle.eachBattler do |b| + next if !b.pbHasType?(:GRASS) + next if b.semiInvulnerable? + next if !b.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + @validTargets.push(b.index) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return false if @validTargets.include?(target.index) + return true if !target.pbHasType?(:GRASS) || target.semiInvulnerable? + return !target.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self,true) + end + + def pbEffectAgainstTarget(user,target) + target.pbRaiseStatStage(PBStats::DEFENSE,1,user) + end +end + + + +#=============================================================================== +# Decreases the Attack, Special Attack and Speed of all poisoned targets by 1 +# stage each. (Venom Drench) +#=============================================================================== +class PokeBattle_Move_140 < PokeBattle_Move + def pbMoveFailed?(user,targets) + @validTargets = [] + targets.each do |b| + next if !b || b.fainted? + next if !b.poisoned? + next if !b.pbCanLowerStatStage?(PBStats::ATTACK,user,self) && + !b.pbCanLowerStatStage?(PBStats::SPATK,user,self) && + !b.pbCanLowerStatStage?(PBStats::SPEED,user,self) + @validTargets.push(b.index) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if !@validTargets.include?(target.index) + showAnim = true + [PBStats::ATTACK,PBStats::SPATK,PBStats::SPEED].each do |s| + next if !target.pbCanLowerStatStage?(s,user,self) + if target.pbLowerStatStage(s,1,user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# Reverses all stat changes of the target. (Topsy-Turvy) +#=============================================================================== +class PokeBattle_Move_141 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + failed = true + PBStats.eachBattleStat do |s| + next if target.stages[s]==0 + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + PBStats.eachBattleStat { |s| target.stages[s] *= -1 } + @battle.pbDisplay(_INTL("{1}'s stats were reversed!",target.pbThis)) + end +end + + + +#=============================================================================== +# Gives target the Ghost type. (Trick-or-Treat) +#=============================================================================== +class PokeBattle_Move_142 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !hasConst?(PBTypes,:GHOST) || target.pbHasType?(:GHOST) || !target.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + ghostType = getConst(PBTypes,:GHOST) + target.effects[PBEffects::Type3] = ghostType + typeName = PBTypes.getName(ghostType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",target.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Gives target the Grass type. (Forest's Curse) +#=============================================================================== +class PokeBattle_Move_143 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !hasConst?(PBTypes,:GRASS) || target.pbHasType?(:GRASS) || !target.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + grassType = getConst(PBTypes,:GRASS) + target.effects[PBEffects::Type3] = grassType + typeName = PBTypes.getName(grassType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",target.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Type effectiveness is multiplied by the Flying-type's effectiveness against +# the target. Does double damage and has perfect accuracy if the target is +# Minimized. (Flying Press) +#=============================================================================== +class PokeBattle_Move_144 < PokeBattle_Move + def tramplesMinimize?(param=1) + return true if param==1 && NEWEST_BATTLE_MECHANICS # Perfect accuracy + return true if param==2 # Double damage + return super + end + + def pbCalcTypeModSingle(moveType,defType,user,target) + ret = super + if hasConst?(PBTypes,:FLYING) + flyingEff = PBTypes.getEffectiveness(getConst(PBTypes,:FLYING),defType) + ret *= flyingEff.to_f/PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE + end + return ret + end +end + + + +#=============================================================================== +# Target's moves become Electric-type for the rest of the round. (Electrify) +#=============================================================================== +class PokeBattle_Move_145 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Electrify] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedTargetAlreadyMoved?(target) + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Electrify] = true + @battle.pbDisplay(_INTL("{1}'s moves have been electrified!",target.pbThis)) + end +end + + + +#=============================================================================== +# All Normal-type moves become Electric-type for the rest of the round. +# (Ion Deluge, Plasma Fists) +#=============================================================================== +class PokeBattle_Move_146 < PokeBattle_Move + def pbMoveFailed?(user,targets) + return false if damagingMove? + if @battle.field.effects[PBEffects::IonDeluge] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedLastInRound?(user) + return false + end + + def pbEffectGeneral(user) + return if @battle.field.effects[PBEffects::IonDeluge] + @battle.field.effects[PBEffects::IonDeluge] = true + @battle.pbDisplay(_INTL("A deluge of ions showers the battlefield!")) + end +end + + + +#=============================================================================== +# Always hits. Ends target's protections immediately. (Hyperspace Hole) +#=============================================================================== +class PokeBattle_Move_147 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + def pbAccuracyCheck(user,target); return true; end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::BanefulBunker] = false + target.effects[PBEffects::KingsShield] = false + target.effects[PBEffects::Protect] = false + target.effects[PBEffects::SpikyShield] = false + target.pbOwnSide.effects[PBEffects::CraftyShield] = false + target.pbOwnSide.effects[PBEffects::MatBlock] = false + target.pbOwnSide.effects[PBEffects::QuickGuard] = false + target.pbOwnSide.effects[PBEffects::WideGuard] = false + end +end + + + +#=============================================================================== +# Powders the foe. This round, if it uses a Fire move, it loses 1/4 of its max +# HP instead. (Powder) +#=============================================================================== +class PokeBattle_Move_148 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Powder] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Powder] = true + @battle.pbDisplay(_INTL("{1} is covered in powder!",user.pbThis)) + end +end + + + +#=============================================================================== +# This round, the user's side is unaffected by damaging moves. (Mat Block) +#=============================================================================== +class PokeBattle_Move_149 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.turnCount>1 || user.lastRoundMoved>=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedLastInRound?(user) + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::MatBlock] = true + @battle.pbDisplay(_INTL("{1} intends to flip up a mat and block incoming attacks!",user.pbThis)) + end +end + + + +#=============================================================================== +# User's side is protected against status moves this round. (Crafty Shield) +#=============================================================================== +class PokeBattle_Move_14A < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::CraftyShield] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedLastInRound?(user) + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::CraftyShield] = true + @battle.pbDisplay(_INTL("Crafty Shield protected {1}!",user.pbTeam(true))) + end +end + + + +#=============================================================================== +# User is protected against damaging moves this round. Decreases the Attack of +# the user of a stopped contact move by 2 stages. (King's Shield) +#=============================================================================== +class PokeBattle_Move_14B < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::KingsShield + end +end + + + +#=============================================================================== +# User is protected against moves that target it this round. Damages the user of +# a stopped contact move by 1/8 of its max HP. (Spiky Shield) +#=============================================================================== +class PokeBattle_Move_14C < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::SpikyShield + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Phantom Force) +# Is invulnerable during use. Ends target's protections upon hit. +#=============================================================================== +class PokeBattle_Move_14D < PokeBattle_Move_0CD + # NOTE: This move is identical to function code 0CD (Shadow Force). +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, and increases the user's Special Attack, +# Special Defense and Speed by 2 stages each in the second turn. (Geomancy) +#=============================================================================== +class PokeBattle_Move_14E < PokeBattle_TwoTurnMove + def pbMoveFailed?(user,targets) + return false if user.effects[PBEffects::TwoTurnAttack]>0 # Charging turn + if !user.pbCanRaiseStatStage?(PBStats::SPATK,user,self) && + !user.pbCanRaiseStatStage?(PBStats::SPDEF,user,self) && + !user.pbCanRaiseStatStage?(PBStats::SPEED,user,self) + @battle.pbDisplay(_INTL("{1}'s stats won't go any higher!",user.pbThis)) + return true + end + return false + end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} is absorbing power!",user.pbThis)) + end + + def pbAttackingTurnEffect(user,target) + showAnim = true + [PBStats::SPATK,PBStats::SPDEF,PBStats::SPEED].each do |s| + next if !user.pbCanRaiseStatStage?(s,user,self) + if user.pbRaiseStatStage(s,2,user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# User gains 3/4 the HP it inflicts as damage. (Draining Kiss, Oblivion Wing) +#=============================================================================== +class PokeBattle_Move_14F < PokeBattle_Move + def healingMove?; return NEWEST_BATTLE_MECHANICS; end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.hpLost<=0 + hpGain = (target.damageState.hpLost*0.75).round + user.pbRecoverHPFromDrain(hpGain,target) + end +end + + + +#=============================================================================== +# If this move KO's the target, increases the user's Attack by 3 stages. +# (Fell Stinger) +#=============================================================================== +class PokeBattle_Move_150 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + return if !target.damageState.fainted + return if !user.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + user.pbRaiseStatStage(PBStats::ATTACK,3,user) + end +end + + + +#=============================================================================== +# Decreases the target's Attack and Special Attack by 1 stage each. Then, user +# switches out. Ignores trapping moves. (Parting Shot) +#=============================================================================== +class PokeBattle_Move_151 < PokeBattle_TargetMultiStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1,PBStats::SPATK,1] + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + switcher = user + targets.each do |b| + next if switchedBattlers.include?(b.index) + switcher = b if b.effects[PBEffects::MagicCoat] || b.effects[PBEffects::MagicBounce] + end + return if switcher.fainted? || numHits==0 + return if !@battle.pbCanChooseNonActive?(switcher.index) + @battle.pbDisplay(_INTL("{1} went back to {2}!",switcher.pbThis, + @battle.pbGetOwnerName(switcher.index))) + @battle.pbPursuit(switcher.index) + return if switcher.fainted? + newPkmn = @battle.pbGetReplacementPokemonIndex(switcher.index) # Owner chooses + return if newPkmn<0 + @battle.pbRecallAndReplace(switcher.index,newPkmn) + @battle.pbClearChoice(switcher.index) # Replacement Pokémon does nothing this round + @battle.moldBreaker = false if switcher.index==user.index + switchedBattlers.push(switcher.index) + switcher.pbEffectsOnSwitchIn(true) + end +end + + + +#=============================================================================== +# No Pokémon can switch out or flee until the end of the next round, as long as +# the user remains active. (Fairy Lock) +#=============================================================================== +class PokeBattle_Move_152 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.effects[PBEffects::FairyLock]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::FairyLock] = 2 + @battle.pbDisplay(_INTL("No one will be able to run away during the next turn!")) + end +end + + + +#=============================================================================== +# Entry hazard. Lays stealth rocks on the opposing side. (Sticky Web) +#=============================================================================== +class PokeBattle_Move_153 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOpposingSide.effects[PBEffects::StickyWeb] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOpposingSide.effects[PBEffects::StickyWeb] = true + @battle.pbDisplay(_INTL("A sticky web has been laid out beneath {1}'s feet!", + user.pbOpposingTeam(true))) + end +end + + + +#=============================================================================== +# For 5 rounds, creates an electric terrain which boosts Electric-type moves and +# prevents Pokémon from falling asleep. Affects non-airborne Pokémon only. +# (Electric Terrain) +#=============================================================================== +class PokeBattle_Move_154 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.terrain==PBBattleTerrains::Electric + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartTerrain(user,PBBattleTerrains::Electric) + end +end + + + +#=============================================================================== +# For 5 rounds, creates a grassy terrain which boosts Grass-type moves and heals +# Pokémon at the end of each round. Affects non-airborne Pokémon only. +# (Grassy Terrain) +#=============================================================================== +class PokeBattle_Move_155 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.terrain==PBBattleTerrains::Grassy + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartTerrain(user,PBBattleTerrains::Grassy) + end +end + + + +#=============================================================================== +# For 5 rounds, creates a misty terrain which weakens Dragon-type moves and +# protects Pokémon from status problems. Affects non-airborne Pokémon only. +# (Misty Terrain) +#=============================================================================== +class PokeBattle_Move_156 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.terrain==PBBattleTerrains::Misty + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartTerrain(user,PBBattleTerrains::Misty) + end +end + + + +#=============================================================================== +# Doubles the prize money the player gets after winning the battle. (Happy Hour) +#=============================================================================== +class PokeBattle_Move_157 < PokeBattle_Move + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::HappyHour] = true if !user.opposes? + @battle.pbDisplay(_INTL("Everyone is caught up in the happy atmosphere!")) + end +end + + + +#=============================================================================== +# Fails unless user has consumed a berry at some point. (Belch) +#=============================================================================== +class PokeBattle_Move_158 < PokeBattle_Move + def pbCanChooseMove?(user,commandPhase,showMessages) + if !user.belched? + if showMessages + msg = _INTL("{1} hasn't eaten any held berry, so it can't possibly belch!",user.pbThis) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + return true + end + + def pbMoveFailed?(user,targets) + if !user.belched? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Poisons the target and decreases its Speed by 1 stage. (Toxic Thread) +#=============================================================================== +class PokeBattle_Move_159 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !target.pbCanPoison?(user,false,self) && + !target.pbCanLowerStatStage?(PBStats::SPEED,user,self) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.pbPoison(user) if target.pbCanPoison?(user,false,self) + if target.pbCanLowerStatStage?(PBStats::SPEED,user,self) + target.pbLowerStatStage(PBStats::SPEED,1,user) + end + end +end + + + +#=============================================================================== +# Cures the target's burn. (Sparkling Aria) +#=============================================================================== +class PokeBattle_Move_15A < PokeBattle_Move + def pbAdditionalEffect(user,target) + return if target.fainted? || target.damageState.substitute + return if target.status!=PBStatuses::BURN + target.pbCureStatus + end +end + + + +#=============================================================================== +# Cures the target's permanent status problems. Heals user by 1/2 of its max HP. +# (Purify) +#=============================================================================== +class PokeBattle_Move_15B < PokeBattle_HealingMove + def pbFailsAgainstTarget?(user,target) + if target.status==PBStatuses::NONE + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbHealAmount(user) + return (user.totalhp/2.0).round + end + + def pbEffectAgainstTarget(user,target) + target.pbCureStatus + super + end +end + + + +#=============================================================================== +# Increases the user's and its ally's Attack and Special Attack by 1 stage each, +# if they have Plus or Minus. (Gear Up) +#=============================================================================== +# NOTE: In Gen 5, this move should have a target of UserSide, while in Gen 6+ it +# should have a target of UserAndAllies. This is because, in Gen 5, this +# move shouldn't call def pbSuccessCheckAgainstTarget for each Pokémon +# currently in battle that will be affected by this move (i.e. allies +# aren't protected by their substitute/ability/etc., but they are in Gen +# 6+). We achieve this by not targeting any battlers in Gen 5, since +# pbSuccessCheckAgainstTarget is only called for targeted battlers. +class PokeBattle_Move_15C < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + @validTargets = [] + @battle.eachSameSideBattler(user) do |b| + next if !b.hasActiveAbility?([:MINUS,:PLUS]) + next if !b.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) && + !b.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + @validTargets.push(b) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return false if @validTargets.any? { |b| b.index==target.index } + return true if !target.hasActiveAbility?([:MINUS,:PLUS]) + @battle.pbDisplay(_INTL("{1}'s stats can't be raised further!",target.pbThis)) + return true + end + + def pbEffectAgainstTarget(user,target) + showAnim = true + if target.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + if target.pbRaiseStatStage(PBStats::ATTACK,1,user,showAnim) + showAnim = false + end + end + if target.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + target.pbRaiseStatStage(PBStats::SPATK,1,user,showAnim) + end + end + + def pbEffectGeneral(user) + return if pbTarget(user)==PBTargets::UserAndAllies + @validTargets.each { |b| pbEffectAgainstTarget(user,b) } + end +end + + + +#=============================================================================== +# User gains stat stages equal to each of the target's positive stat stages, +# and target's positive stat stages become 0, before damage calculation. +# (Spectral Thief) +#=============================================================================== +class PokeBattle_Move_15D < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbCalcDamage(user,target,numTargets=1) + if target.hasRaisedStatStages? + pbShowAnimation(@id,user,target,1) # Stat stage-draining animation + @battle.pbDisplay(_INTL("{1} stole the target's boosted stats!",user.pbThis)) + showAnim = true + PBStats.eachBattleStat do |s| + next if target.stages[s]<=0 + if user.pbCanRaiseStatStage?(s,user,self) + if user.pbRaiseStatStage(s,target.stages[s],user,showAnim) + showAnim = false + end + end + target.stages[s] = 0 + end + end + super + end +end + + + +#=============================================================================== +# Until the end of the next round, the user's moves will always be critical hits. +# (Laser Focus) +#=============================================================================== +class PokeBattle_Move_15E < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::LaserFocus] = 2 + @battle.pbDisplay(_INTL("{1} concentrated intensely!",user.pbThis)) + end +end + + + +#=============================================================================== +# Decreases the user's Defense by 1 stage. (Clanging Scales) +#=============================================================================== +class PokeBattle_Move_15F < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Decreases the target's Attack by 1 stage. Heals user by an amount equal to the +# target's Attack stat (after applying stat stages, before this move decreases +# it). (Strength Sap) +#=============================================================================== +class PokeBattle_Move_160 < PokeBattle_Move + def healingMove?; return true; end + + def pbFailsAgainstTarget?(user,target) + # NOTE: The official games appear to just check whether the target's Attack + # stat stage is -6 and fail if so, but I've added the "fail if target + # has Contrary and is at +6" check too for symmetry. This move still + # works even if the stat stage cannot be changed due to an ability or + # other effect. + if !@battle.moldBreaker && target.hasActiveAbility?(:CONTRARY) && + target.statStageAtMax?(PBStats::ATTACK) + @battle.pbDisplay(_INTL("But it failed!")) + return true + elsif target.statStageAtMin?(PBStats::ATTACK) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + # Calculate target's effective attack value + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + atk = target.attack + atkStage = target.stages[PBStats::ATTACK]+6 + healAmt = (atk.to_f*stageMul[atkStage]/stageDiv[atkStage]).floor + # Reduce target's Attack stat + if target.pbCanLowerStatStage?(PBStats::ATTACK,user,self) + target.pbLowerStatStage(PBStats::ATTACK,1,user) + end + # Heal user + if target.hasActiveAbility?(:LIQUIDOOZE) + @battle.pbShowAbilitySplash(target) + user.pbReduceHP(healAmt) + @battle.pbDisplay(_INTL("{1} sucked up the liquid ooze!",user.pbThis)) + @battle.pbHideAbilitySplash(target) + user.pbItemHPHealCheck + elsif user.canHeal? + healAmt = (healAmt*1.3).floor if user.hasActiveItem?(:BIGROOT) + user.pbRecoverHP(healAmt) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",user.pbThis)) + end + end +end + + + +#=============================================================================== +# User and target swap their Speed stats (not their stat stages). (Speed Swap) +#=============================================================================== +class PokeBattle_Move_161 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + user.speed, target.speed = target.speed, user.speed + @battle.pbDisplay(_INTL("{1} switched Speed with its target!",user.pbThis)) + end +end + + + +#=============================================================================== +# User loses their Fire type. Fails if user is not Fire-type. (Burn Up) +#=============================================================================== +class PokeBattle_Move_162 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !user.pbHasType?(:FIRE) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAfterAllHits(user,target) + if !user.effects[PBEffects::BurnUp] + user.effects[PBEffects::BurnUp] = true + @battle.pbDisplay(_INTL("{1} burned itself out!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Ignores all abilities that alter this move's success or damage. +# (Moongeist Beam, Sunsteel Strike) +#=============================================================================== +class PokeBattle_Move_163 < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + super + @battle.moldBreaker = true if !specialUsage + end +end + + + +#=============================================================================== +# Ignores all abilities that alter this move's success or damage. This move is +# physical if user's Attack is higher than its Special Attack (after applying +# stat stages), and special otherwise. (Photon Geyser) +#=============================================================================== +class PokeBattle_Move_164 < PokeBattle_Move_163 + def initialize(battle,move) + super + @calcCategory = 1 + end + + def physicalMove?(thisType=nil); return (@calcCategory==0); end + def specialMove?(thisType=nil); return (@calcCategory==1); end + + def pbOnStartUse(user,targets) + # Calculate user's effective attacking value + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + atk = user.attack + atkStage = user.stages[PBStats::ATTACK]+6 + realAtk = (atk.to_f*stageMul[atkStage]/stageDiv[atkStage]).floor + spAtk = user.spatk + spAtkStage = user.stages[PBStats::SPATK]+6 + realSpAtk = (spAtk.to_f*stageMul[spAtkStage]/stageDiv[spAtkStage]).floor + # Determine move's category + @calcCategory = (realAtk>realSpAtk) ? 0 : 1 + end +end + + + +#=============================================================================== +# Negates the target's ability while it remains on the field, if it has already +# performed its action this round. (Core Enforcer) +#=============================================================================== +class PokeBattle_Move_165 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklist = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be negated +# :FORECAST, # This can be negated + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.substitute || target.effects[PBEffects::GastroAcid] + @abilityBlacklist.each do |abil| + return if isConst?(target.ability,PBAbilities,abil) + end + return if @battle.choices[target.index][0]!=:UseItem && + !((@battle.choices[target.index][0]==:UseMove || + @battle.choices[target.index][0]==:Shift) && target.movedThisRound?) + target.effects[PBEffects::GastroAcid] = true + target.effects[PBEffects::Truant] = false + @battle.pbDisplay(_INTL("{1}'s Ability was suppressed!",target.pbThis)) + target.pbOnAbilityChanged(target.ability) + end +end + + + +#=============================================================================== +# Power is doubled if the user's last move failed. (Stomping Tantrum) +#=============================================================================== +class PokeBattle_Move_166 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if user.lastRoundMoveFailed + return baseDmg + end +end + + + +#=============================================================================== +# For 5 rounds, lowers power of attacks against the user's side. Fails if +# weather is not hail. (Aurora Veil) +#=============================================================================== +class PokeBattle_Move_167 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.pbWeather!=PBWeather::Hail + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if user.pbOwnSide.effects[PBEffects::AuroraVeil]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::AuroraVeil] = 5 + user.pbOwnSide.effects[PBEffects::AuroraVeil] = 8 if user.hasActiveItem?(:LIGHTCLAY) + @battle.pbDisplay(_INTL("{1} made {2} stronger against physical and special moves!", + @name,user.pbTeam(true))) + end +end + + + +#=============================================================================== +# User is protected against moves with the "B" flag this round. If a Pokémon +# makes contact with the user while this effect applies, that Pokémon is +# poisoned. (Baneful Bunker) +#=============================================================================== +class PokeBattle_Move_168 < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::BanefulBunker + end +end + + + +#=============================================================================== +# This move's type is the same as the user's first type. (Revelation Dance) +#=============================================================================== +class PokeBattle_Move_169 < PokeBattle_Move + def pbBaseType(user) + userTypes = user.pbTypes(true) + return (userTypes.length==0) ? -1 : userTypes[0] + end +end + + + +#=============================================================================== +# This round, target becomes the target of attacks that have single targets. +# (Spotlight) +#=============================================================================== +class PokeBattle_Move_16A < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Spotlight] = 1 + target.eachAlly do |b| + next if b.effects[PBEffects::Spotlight]0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Instruct] = true + end +end + + + +#=============================================================================== +# Target cannot use sound-based moves for 2 more rounds. (Throat Chop) +#=============================================================================== +class PokeBattle_Move_16C < PokeBattle_Move + def pbAdditionalEffect(user,target) + return if target.fainted? || target.damageState.substitute + @battle.pbDisplay(_INTL("The effects of {1} prevent {2} from using certain moves!", + @name,target.pbThis(true))) if target.effects[PBEffects::ThroatChop]==0 + target.effects[PBEffects::ThroatChop] = 3 + end +end + + + +#=============================================================================== +# Heals user by 1/2 of its max HP, or 2/3 of its max HP in a sandstorm. (Shore Up) +#=============================================================================== +class PokeBattle_Move_16D < PokeBattle_HealingMove + def pbHealAmount(user) + return (user.totalhp*2/3.0).round if @battle.pbWeather==PBWeather::Sandstorm + return (user.totalhp/2.0).round + end +end + + + +#=============================================================================== +# Heals target by 1/2 of its max HP, or 2/3 of its max HP in Grassy Terrain. +# (Floral Healing) +#=============================================================================== +class PokeBattle_Move_16E < PokeBattle_Move + def healingMove?; return true; end + + def pbFailsAgainstTarget?(user,target) + if target.hp==target.totalhp + @battle.pbDisplay(_INTL("{1}'s HP is full!",target.pbThis)) + return true + elsif !target.canHeal? + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + hpGain = (target.totalhp/2.0).round + hpGain = (target.totalhp*2/3.0).round if @battle.field.terrain==PBBattleTerrains::Grassy + target.pbRecoverHP(hpGain) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + end +end + + + +#=============================================================================== +# Damages target if target is a foe, or heals target by 1/2 of its max HP if +# target is an ally. (Pollen Puff) +#=============================================================================== +class PokeBattle_Move_16F < PokeBattle_Move + def pbTarget(user) + return PBTargets::NearFoe if user.effects[PBEffects::HealBlock]>0 + super + end + + def pbOnStartUse(user,targets) + @healing = false + @healing = !user.opposes?(targets[0]) if targets.length>0 + end + + def pbFailsAgainstTarget?(user,target) + return false if !@healing + if target.effects[PBEffects::Substitute]>0 && !ignoresSubstitute?(user) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if !target.canHeal? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbDamagingMove? + return false if @healing + return super + end + + def pbEffectAgainstTarget(user,target) + return if !@healing + target.pbRecoverHP(target.totalhp/2) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if @healing # Healing anim + super + end +end + + + +#=============================================================================== +# Damages user by 1/2 of its max HP, even if this move misses. (Mind Blown) +#=============================================================================== +class PokeBattle_Move_170 < PokeBattle_Move + def worksWithNoTargets?; return true; end + + def pbMoveFailed?(user,targets) + if !@battle.moldBreaker + bearer = @battle.pbCheckGlobalAbility(:DAMP) + if bearer!=nil + @battle.pbShowAbilitySplash(bearer) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} cannot use {2}!",user.pbThis,@name)) + else + @battle.pbDisplay(_INTL("{1} cannot use {2} because of {3}'s {4}!", + user.pbThis,@name,bearer.pbThis(true),bearer.abilityName)) + end + @battle.pbHideAbilitySplash(bearer) + return true + end + end + return false + end + + def pbSelfKO(user) + return if !user.takesIndirectDamage? + user.pbReduceHP((user.totalhp/2.0).round,false) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Fails if user has not been hit by an opponent's physical move this round. +# (Shell Trap) +#=============================================================================== +class PokeBattle_Move_171 < PokeBattle_Move + def pbDisplayChargeMessage(user) + user.effects[PBEffects::ShellTrap] = true + @battle.pbCommonAnimation("ShellTrap",user) + @battle.pbDisplay(_INTL("{1} set a shell trap!",user.pbThis)) + end + + def pbDisplayUseMessage(user) + super if user.tookPhysicalHit + end + + def pbMoveFailed?(user,targets) + if !user.effects[PBEffects::ShellTrap] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if !user.tookPhysicalHit + @battle.pbDisplay(_INTL("{1}'s shell trap didn't work!",user.pbThis)) + return true + end + return false + end +end + + + +#=============================================================================== +# If a Pokémon makes contact with the user before it uses this move, the +# attacker is burned. (Beak Blast) +#=============================================================================== +class PokeBattle_Move_172 < PokeBattle_Move + def pbDisplayChargeMessage(user) + user.effects[PBEffects::BeakBlast] = true + @battle.pbCommonAnimation("BeakBlast",user) + @battle.pbDisplay(_INTL("{1} started heating up its beak!",user.pbThis)) + end + + def pbMoveFailed?(user,targets) + if !user.effects[PBEffects::BeakBlast] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# For 5 rounds, creates a psychic terrain which boosts Psychic-type moves and +# prevents Pokémon from being hit by >0 priority moves. Affects non-airborne +# Pokémon only. (Psychic Terrain) +#=============================================================================== +class PokeBattle_Move_173 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.terrain==PBBattleTerrains::Psychic + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartTerrain(user,PBBattleTerrains::Psychic) + end +end + + + +#=============================================================================== +# Fails if this isn't the user's first turn. (First Impression) +#=============================================================================== +class PokeBattle_Move_174 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.turnCount>1 || user.lastRoundMoved>=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Hits twice. Causes the target to flinch. Does double damage and has perfect +# accuracy if the target is Minimized. (Double Iron Bash) +#=============================================================================== +class PokeBattle_Move_175 < PokeBattle_FlinchMove + def multiHitMove?; return true; end + def pbNumHits(user,targets); return 2; end + def tramplesMinimize?(param=1); return true; end +end + + + +# NOTE: If you're inventing new move effects, use function code 176 and onwards. +# Actually, you might as well use high numbers like 500+ (up to FFFF), +# just to make sure later additions to Essentials don't clash with your +# new effects. \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_PBWeather.rb b/Data/Scripts/011_Battle/002_PBWeather.rb new file mode 100644 index 000000000..d5482b784 --- /dev/null +++ b/Data/Scripts/011_Battle/002_PBWeather.rb @@ -0,0 +1,32 @@ +begin + module PBWeather + None = 0 + Sun = 1 + Rain = 2 + Sandstorm = 3 + Hail = 4 + HarshSun = 5 + HeavyRain = 6 + StrongWinds = 7 + ShadowSky = 8 + + def self.animationName(weather) + case weather + when Sun; return "Sun" + when Rain; return "Rain" + when Sandstorm; return "Sandstorm" + when Hail; return "Hail" + when HarshSun; return "HarshSun" + when HeavyRain; return "HeavyRain" + when StrongWinds; return "StrongWinds" + when ShadowSky; return "ShadowSky" + end + return nil + end + end + +rescue Exception + if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset" + raise $! + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/001_PokeBattle_BattleCommon.rb b/Data/Scripts/011_Battle/003_Battle/001_PokeBattle_BattleCommon.rb new file mode 100644 index 000000000..bd9b59227 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/001_PokeBattle_BattleCommon.rb @@ -0,0 +1,230 @@ +module PokeBattle_BattleCommon + #============================================================================= + # Store caught Pokémon + #============================================================================= + def pbStorePokemon(pkmn) + # Nickname the Pokémon (unless it's a Shadow Pokémon) + if !pkmn.shadowPokemon? + if pbDisplayConfirm(_INTL("Would you like to give a nickname to {1}?",pkmn.name)) + nickname = @scene.pbNameEntry(_INTL("{1}'s nickname?",pkmn.speciesName),pkmn) + pkmn.name = nickname if nickname!="" + end + end + # Store the Pokémon + currentBox = @peer.pbCurrentBox + storedBox = @peer.pbStorePokemon(pbPlayer,pkmn) + if storedBox<0 + pbDisplayPaused(_INTL("{1} has been added to your party.",pkmn.name)) + @initialItems[0][pbPlayer.party.length-1] = pkmn.item if @initialItems + return + end + # Messages saying the Pokémon was stored in a PC box + creator = @peer.pbGetStorageCreatorName + curBoxName = @peer.pbBoxName(currentBox) + boxName = @peer.pbBoxName(storedBox) + if storedBox!=currentBox + if creator + pbDisplayPaused(_INTL("Box \"{1}\" on {2}'s PC was full.",curBoxName,creator)) + else + pbDisplayPaused(_INTL("Box \"{1}\" on someone's PC was full.",curBoxName)) + end + pbDisplayPaused(_INTL("{1} was transferred to box \"{2}\".",pkmn.name,boxName)) + else + if creator + pbDisplayPaused(_INTL("{1} was transferred to {2}'s PC.",pkmn.name,creator)) + else + pbDisplayPaused(_INTL("{1} was transferred to someone's PC.",pkmn.name)) + end + pbDisplayPaused(_INTL("It was stored in box \"{1}\".",boxName)) + end + end + + # Register all caught Pokémon in the Pokédex, and store them. + def pbRecordAndStoreCaughtPokemon + @caughtPokemon.each do |pkmn| + pbSeenForm(pkmn) # In case the form changed upon leaving battle + # Record the Pokémon's species as owned in the Pokédex + if !pbPlayer.hasOwned?(pkmn.species) + pbPlayer.setOwned(pkmn.species) + if $Trainer.pokedex + pbDisplayPaused(_INTL("{1}'s data was added to the Pokédex.",pkmn.name)) + @scene.pbShowPokedex(pkmn.species) + end + end + # Record a Shadow Pokémon's species as having been caught + if pkmn.shadowPokemon? + pbPlayer.shadowcaught = [] if !pbPlayer.shadowcaught + pbPlayer.shadowcaught[pkmn.species] = true + end + # Store caught Pokémon + pbStorePokemon(pkmn) + end + @caughtPokemon.clear + end + + #============================================================================= + # Throw a Poké Ball + #============================================================================= + def pbThrowPokeBall(idxBattler,ball,rareness=nil,showPlayer=false) + # Determine which Pokémon you're throwing the Poké Ball at + battler = nil + if opposes?(idxBattler) + battler = @battlers[idxBattler] + else + battler = @battlers[idxBattler].pbDirectOpposing(true) + end + if battler.fainted? + battler.eachAlly do |b| + battler = b + break + end + end + # Messages + itemName = PBItems.getName(ball) + if battler.fainted? + if itemName.starts_with_vowel? + pbDisplay(_INTL("{1} threw an {2}!",pbPlayer.name,itemName)) + else + pbDisplay(_INTL("{1} threw a {2}!",pbPlayer.name,itemName)) + end + pbDisplay(_INTL("But there was no target...")) + return + end + if itemName.starts_with_vowel? + pbDisplayBrief(_INTL("{1} threw an {2}!",pbPlayer.name,itemName)) + else + pbDisplayBrief(_INTL("{1} threw a {2}!",pbPlayer.name,itemName)) + end + # Animation of opposing trainer blocking Poké Balls (unless it's a Snag Ball + # at a Shadow Pokémon) + if trainerBattle? && !(pbIsSnagBall?(ball) && battler.shadowPokemon?) + @scene.pbThrowAndDeflect(ball,1) + pbDisplay(_INTL("The Trainer blocked your Poké Ball! Don't be a thief!")) + return + end + # Calculate the number of shakes (4=capture) + pkmn = battler.pokemon + @criticalCapture = false + numShakes = pbCaptureCalc(pkmn,battler,rareness,ball) + PBDebug.log("[Threw Poké Ball] #{itemName}, #{numShakes} shakes (4=capture)") + # Animation of Ball throw, absorb, shake and capture/burst out + @scene.pbThrow(ball,numShakes,@criticalCapture,battler.index,showPlayer) + # Outcome message + case numShakes + when 0 + pbDisplay(_INTL("Oh no! The Pokémon broke free!")) + BallHandlers.onFailCatch(ball,self,battler) + when 1 + pbDisplay(_INTL("Aww! It appeared to be caught!")) + BallHandlers.onFailCatch(ball,self,battler) + when 2 + pbDisplay(_INTL("Aargh! Almost had it!")) + BallHandlers.onFailCatch(ball,self,battler) + when 3 + pbDisplay(_INTL("Gah! It was so close, too!")) + BallHandlers.onFailCatch(ball,self,battler) + when 4 + pbDisplayBrief(_INTL("Gotcha! {1} was caught!",pkmn.name)) + @scene.pbThrowSuccess # Play capture success jingle + pbRemoveFromParty(battler.index,battler.pokemonIndex) + # Gain Exp + if GAIN_EXP_FOR_CAPTURE + battler.captured = true + pbGainExp + battler.captured = false + end + battler.pbReset + if trainerBattle? + @decision = 1 if pbAllFainted?(battler.index) + else + @decision = 4 if pbAllFainted?(battler.index) # Battle ended by capture + end + # Modify the Pokémon's properties because of the capture + if pbIsSnagBall?(ball) + pkmn.ot = pbPlayer.name + pkmn.trainerID = pbPlayer.id + end + BallHandlers.onCatch(ball,self,pkmn) + pkmn.ballused = pbGetBallType(ball) + pkmn.makeUnmega if pkmn.mega? + pkmn.makeUnprimal + pkmn.pbUpdateShadowMoves if pkmn.shadowPokemon? + pkmn.pbRecordFirstMoves + # Reset form + pkmn.forcedForm = nil if MultipleForms.hasFunction?(pkmn.species,"getForm") + @peer.pbOnLeavingBattle(self,pkmn,true,true) + # Make the Poké Ball and data box disappear + @scene.pbHideCaptureBall(idxBattler) + # Save the Pokémon for storage at the end of battle + @caughtPokemon.push(pkmn) + end + end + + #============================================================================= + # Calculate how many shakes a thrown Poké Ball will make (4 = capture) + #============================================================================= + def pbCaptureCalc(pkmn,battler,rareness,ball) + return 4 if $DEBUG && Input.press?(Input::CTRL) + # Get a rareness if one wasn't provided + if !rareness + rareness = pbGetSpeciesData(pkmn.species,pkmn.form,SpeciesRareness) + end + # Modify rareness depending on the Poké Ball's effect + ultraBeast = (isConst?(battler.species,PBSpecies,:NIHILEGO) || + isConst?(battler.species,PBSpecies,:BUZZWOLE) || + isConst?(battler.species,PBSpecies,:PHEROMOSA) || + isConst?(battler.species,PBSpecies,:XURKITREE) || + isConst?(battler.species,PBSpecies,:CELESTEELA) || + isConst?(battler.species,PBSpecies,:KARTANA) || + isConst?(battler.species,PBSpecies,:GUZZLORD) || + isConst?(battler.species,PBSpecies,:POIPOLE) || + isConst?(battler.species,PBSpecies,:NAGANADEL) || + isConst?(battler.species,PBSpecies,:STAKATAKA) || + isConst?(battler.species,PBSpecies,:BLACEPHALON)) + if !ultraBeast || isConst?(ball,PBItems,:BEASTBALL) + rareness = BallHandlers.modifyCatchRate(ball,rareness,self,battler,ultraBeast) + else + rareness /= 10 + end + # First half of the shakes calculation + a = battler.totalhp + b = battler.hp + x = ((3*a-2*b)*rareness.to_f)/(3*a) + # Calculation modifiers + if battler.status==PBStatuses::SLEEP || battler.status==PBStatuses::FROZEN + x *= 2.5 + elsif battler.status!=PBStatuses::NONE + x *= 1.5 + end + x = x.floor + x = 1 if x<1 + # Definite capture, no need to perform randomness checks + return 4 if x>=255 || BallHandlers.isUnconditional?(ball,self,battler) + # Second half of the shakes calculation + y = ( 65536 / ((255.0/x)**0.1875) ).floor + # Critical capture check + if ENABLE_CRITICAL_CAPTURES + c = 0 + numOwned = $Trainer.pokedexOwned + if numOwned>600; c = x*5/12 + elsif numOwned>450; c = x*4/12 + elsif numOwned>300; c = x*3/12 + elsif numOwned>150; c = x*2/12 + elsif numOwned>30; c = x/12 + end + # Calculate the number of shakes + if c>0 && pbRandom(256)pbSideSize(1)) ? (pbSideSize(0)-1)*2 : pbSideSize(1)*2-1 + end + + #============================================================================= + # Trainers and owner-related methods + #============================================================================= + def pbPlayer; return @player[0]; end + + # Given a battler index, returns the index within @player/@opponent of the + # trainer that controls that battler index. + # NOTE: You shouldn't ever have more trainers on a side than there are battler + # positions on that side. This method doesn't account for if you do. + def pbGetOwnerIndexFromBattlerIndex(idxBattler) + trainer = (opposes?(idxBattler)) ? @opponent : @player + return 0 if !trainer + case trainer.length + when 2 + n = pbSideSize(idxBattler%2) + return [0,0,1][idxBattler/2] if n==3 + return idxBattler/2 # Same as [0,1][idxBattler/2], i.e. 2 battler slots + when 3; return idxBattler/2 + end + return 0 + end + + def pbGetOwnerFromBattlerIndex(idxBattler) + idxTrainer = pbGetOwnerIndexFromBattlerIndex(idxBattler) + return (opposes?(idxBattler)) ? @opponent[idxTrainer] : @player[idxTrainer] + end + + def pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty) + ret = -1 + pbPartyStarts(idxBattler).each_with_index do |start,i| + break if start>idxParty + ret = i + end + return ret + end + + # Only used for the purpose of an error message when one trainer tries to + # switch another trainer's Pokémon. + def pbGetOwnerFromPartyIndex(idxBattler,idxParty) + idxTrainer = pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty) + return (opposes?(idxBattler)) ? @opponent[idxTrainer] : @player[idxTrainer] + end + + def pbGetOwnerName(idxBattler) + idxTrainer = pbGetOwnerIndexFromBattlerIndex(idxBattler) + return @opponent[idxTrainer].fullname if opposes?(idxBattler) # Opponent + return @player[idxTrainer].fullname if idxTrainer>0 # Ally trainer + return @player[idxTrainer].name # Player + end + + def pbGetOwnerItems(idxBattler) + return [] if !@items || !opposes?(idxBattler) + return @items[pbGetOwnerIndexFromBattlerIndex(idxBattler)] + end + + # Returns whether the battler in position idxBattler is owned by the same + # trainer that owns the Pokémon in party slot idxParty. This assumes that + # both the battler position and the party slot are from the same side. + def pbIsOwner?(idxBattler,idxParty) + idxTrainer1 = pbGetOwnerIndexFromBattlerIndex(idxBattler) + idxTrainer2 = pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty) + return idxTrainer1==idxTrainer2 + end + + def pbOwnedByPlayer?(idxBattler) + return false if opposes?(idxBattler) + return pbGetOwnerIndexFromBattlerIndex(idxBattler)==0 + end + + # Returns the number of Pokémon positions controlled by the given trainerIndex + # on the given side of battle. + def pbNumPositions(side,idxTrainer) + ret = 0 + for i in 0...pbSideSize(side) + t = pbGetOwnerIndexFromBattlerIndex(i*2+side) + next if t!=idxTrainer + ret += 1 + end + return ret + end + + #============================================================================= + # Get party information (counts all teams on the same side) + #============================================================================= + def pbParty(idxBattler) + return (opposes?(idxBattler)) ? @party2 : @party1 + end + + def pbOpposingParty(idxBattler) + return (opposes?(idxBattler)) ? @party1 : @party2 + end + + def pbPartyOrder(idxBattler) + return (opposes?(idxBattler)) ? @party2order : @party1order + end + + def pbPartyStarts(idxBattler) + return (opposes?(idxBattler)) ? @party2starts : @party1starts + end + + # Returns the player's team in its display order. Used when showing the party + # screen. + def pbPlayerDisplayParty(idxBattler=0) + partyOrders = pbPartyOrder(idxBattler) + idxStart, idxEnd = pbTeamIndexRangeFromBattlerIndex(idxBattler) + ret = [] + eachInTeamFromBattlerIndex(idxBattler) { |pkmn,i| ret[partyOrders[i]-idxStart] = pkmn } + return ret + end + + def pbAbleCount(idxBattler=0) + party = pbParty(idxBattler) + count = 0 + party.each { |pkmn| count += 1 if pkmn && pkmn.able? } + return count + end + + def pbAbleNonActiveCount(idxBattler=0) + party = pbParty(idxBattler) + inBattleIndices = [] + eachSameSideBattler(idxBattler) { |b| inBattleIndices.push(b.pokemonIndex) } + count = 0 + party.each_with_index do |pkmn,idxParty| + next if !pkmn || !pkmn.able? + next if inBattleIndices.include?(idxParty) + count += 1 + end + return count + end + + def pbAllFainted?(idxBattler=0) + return pbAbleCount(idxBattler)==0 + end + + # For the given side of the field (0=player's, 1=opponent's), returns an array + # containing the number of able Pokémon in each team. + def pbAbleTeamCounts(side) + party = pbParty(side) + partyStarts = pbPartyStarts(side) + ret = [] + idxTeam = -1 + nextStart = 0 + party.each_with_index do |pkmn,i| + if i>=nextStart + idxTeam += 1 + nextStart = (idxTeam=idxPartyStart && i=idxPartyStart && i=idxPartyEnd # Check the team only + next if !pkmn || !pkmn.able? # Can't copy a non-fainted Pokémon or egg + ret = i if partyOrders[i]>partyOrders[ret] + end + return ret + end + + # Used to calculate money gained/lost after winning/losing a battle. + def pbMaxLevelInTeam(side,idxTrainer) + ret = 1 + eachInTeam(side,idxTrainer) do |pkmn,i| + ret = pkmn.level if pkmn.level>ret + end + return ret + end + + #============================================================================= + # Iterate through battlers + #============================================================================= + def eachBattler + @battlers.each { |b| yield b if b && !b.fainted? } + end + + def eachSameSideBattler(idxBattler=0) + idxBattler = idxBattler.index if idxBattler.respond_to?("index") + @battlers.each { |b| yield b if b && !b.fainted? && !b.opposes?(idxBattler) } + end + + def eachOtherSideBattler(idxBattler=0) + idxBattler = idxBattler.index if idxBattler.respond_to?("index") + @battlers.each { |b| yield b if b && !b.fainted? && b.opposes?(idxBattler) } + end + + def pbSideBattlerCount(idxBattler=0) + ret = 0 + eachSameSideBattler(idxBattler) { |b| ret += 1 } + return ret + end + + def pbOpposingBattlerCount(idxBattler=0) + ret = 0 + eachOtherSideBattler(idxBattler) { |b| ret += 1 } + return ret + end + + # This method only counts the player's Pokémon, not a partner trainer's. + def pbPlayerBattlerCount + ret = 0 + eachSameSideBattler { |b| ret += 1 if b.pbOwnedByPlayer? } + return ret + end + + def pbCheckGlobalAbility(abil) + eachBattler { |b| return b if b.hasActiveAbility?(abil) } + return nil + end + + def pbCheckOpposingAbility(abil,idxBattler=0,nearOnly=false) + eachOtherSideBattler(idxBattler) do |b| + next if nearOnly && !b.near?(idxBattler) + return b if b.hasActiveAbility?(abil) + end + return nil + end + + # Given a battler index, and using battle side sizes, returns an array of + # battler indices from the opposing side that are in order of most "opposite". + # Used when choosing a target and pressing up/down to move the cursor to the + # opposite side, and also when deciding which target to select first for some + # moves. + def pbGetOpposingIndicesInOrder(idxBattler) + case pbSideSize(0) + when 1 + case pbSideSize(1) + when 1 # 1v1 single + return [0] if opposes?(idxBattler) + return [1] + when 2 # 1v2 + return [0] if opposes?(idxBattler) + return [3,1] + when 3 # 1v3 + return [0] if opposes?(idxBattler) + return [3,5,1] + end + when 2 + case pbSideSize(1) + when 1 # 2v1 + return [0,2] if opposes?(idxBattler) + return [1] + when 2 # 2v2 double + return [[3,1],[2,0],[1,3],[0,2]][idxBattler] + when 3 # 2v3 + return [[5,3,1],[2,0],[3,1,5]][idxBattler] if idxBattler<3 + return [0,2] + end + when 3 + case pbSideSize(1) + when 1 # 3v1 + return [2,0,4] if opposes?(idxBattler) + return [1] + when 2 # 3v2 + return [[3,1],[2,4,0],[3,1],[2,0,4],[1,3]][idxBattler] + when 3 # 3v3 triple + return [[5,3,1],[4,2,0],[3,5,1],[2,0,4],[1,3,5],[0,2,4]][idxBattler] + end + end + return [idxBattler] + end + + #============================================================================= + # Comparing the positions of two battlers + #============================================================================= + def opposes?(idxBattler1,idxBattler2=0) + idxBattler1 = idxBattler1.index if idxBattler1.respond_to?("index") + idxBattler2 = idxBattler2.index if idxBattler2.respond_to?("index") + return (idxBattler1&1)!=(idxBattler2&1) + end + + def nearBattlers?(idxBattler1,idxBattler2) + return false if idxBattler1==idxBattler2 + return true if pbSideSize(0)<=2 && pbSideSize(1)<=2 + # Get all pairs of battler positions that are not close to each other + pairsArray = [[0,4],[1,5]] # Covers 3v1 and 1v3 + case pbSideSize(0) + when 3 + case pbSideSize(1) + when 3 # 3v3 (triple) + pairsArray.push([0,1]) + pairsArray.push([4,5]) + when 2 # 3v2 + pairsArray.push([0,1]) + pairsArray.push([3,4]) + end + when 2 # 2v3 + pairsArray.push([0,1]) + pairsArray.push([2,5]) + end + # See if any pair matches the two battlers being assessed + pairsArray.each do |pair| + return false if pair.include?(idxBattler1) && pair.include?(idxBattler2) + end + return true + end + + #============================================================================= + # Altering a party or rearranging battlers + #============================================================================= + def pbRemoveFromParty(idxBattler,idxParty) + party = pbParty(idxBattler) + # Erase the Pokémon from the party + party[idxParty] = nil + # Rearrange the display order of the team to place the erased Pokémon last + # in it (to avoid gaps) + partyOrders = pbPartyOrder(idxBattler) + partyStarts = pbPartyStarts(idxBattler) + idxTrainer = pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty) + idxPartyStart = partyStarts[idxTrainer] + idxPartyEnd = (idxTrainer=idxPartyEnd # Only check the team + next if partyOrders[i]0 && user && user.itemActive? + duration = BattleHandlers.triggerWeatherExtenderItem(user.item, + @field.weather,duration,user,self) + end + @field.weatherDuration = duration + pbCommonAnimation(PBWeather.animationName(@field.weather)) if showAnim + pbHideAbilitySplash(user) if user + case @field.weather + when PBWeather::Sun; pbDisplay(_INTL("The sunlight turned harsh!")) + when PBWeather::Rain; pbDisplay(_INTL("It started to rain!")) + when PBWeather::Sandstorm; pbDisplay(_INTL("A sandstorm brewed!")) + when PBWeather::Hail; pbDisplay(_INTL("It started to hail!")) + when PBWeather::HarshSun; pbDisplay(_INTL("The sunlight turned extremely harsh!")) + when PBWeather::HeavyRain; pbDisplay(_INTL("A heavy rain began to fall!")) + when PBWeather::StrongWinds; pbDisplay(_INTL("Mysterious strong winds are protecting Flying-type Pokémon!")) + when PBWeather::ShadowSky; pbDisplay(_INTL("A shadow sky appeared!")) + end + # Check for end of primordial weather, and weather-triggered form changes + eachBattler { |b| b.pbCheckFormOnWeatherChange } + pbEndPrimordialWeather + end + + def pbEndPrimordialWeather + oldWeather = @field.weather + # End Primordial Sea, Desolate Land, Delta Stream + case @field.weather + when PBWeather::HarshSun + if !pbCheckGlobalAbility(:DESOLATELAND) + @field.weather = PBWeather::None + pbDisplay("The harsh sunlight faded!") + end + when PBWeather::HeavyRain + if !pbCheckGlobalAbility(:PRIMORDIALSEA) + @field.weather = PBWeather::None + pbDisplay("The heavy rain has lifted!") + end + when PBWeather::StrongWinds + if !pbCheckGlobalAbility(:DELTASTREAM) + @field.weather = PBWeather::None + pbDisplay("The mysterious air current has dissipated!") + end + end + # Check for form changes caused by the weather changing + if @field.weather!=oldWeather + eachBattler { |b| b.pbCheckFormOnWeatherChange } + end + end + + def defaultTerrain=(value) + @field.defaultTerrain = value + @field.terrain = value + @field.terrainDuration = -1 + end + + def pbStartTerrain(user,newTerrain,fixedDuration=true) + return if @field.terrain==newTerrain + @field.terrain = newTerrain + duration = (fixedDuration) ? 5 : -1 + if duration>0 && user && user.itemActive? + duration = BattleHandlers.triggerTerrainExtenderItem(user.item, + newTerrain,duration,user,self) + end + @field.terrainDuration = duration + pbCommonAnimation(PBBattleTerrains.animationName(@field.terrain)) + pbHideAbilitySplash(user) if user + case @field.terrain + when PBBattleTerrains::Electric + pbDisplay(_INTL("An electric current runs across the battlefield!")) + when PBBattleTerrains::Grassy + pbDisplay(_INTL("Grass grew to cover the battlefield!")) + when PBBattleTerrains::Misty + pbDisplay(_INTL("Mist swirled about the battlefield!")) + when PBBattleTerrains::Psychic + pbDisplay(_INTL("The battlefield got weird!")) + end + # Check for terrain seeds that boost stats in a terrain + eachBattler { |b| b.pbItemTerrainStatBoostCheck } + end + + #============================================================================= + # Messages and animations + #============================================================================= + def pbDisplay(msg,&block) + @scene.pbDisplayMessage(msg,&block) + end + + def pbDisplayBrief(msg) + @scene.pbDisplayMessage(msg,true) + end + + def pbDisplayPaused(msg,&block) + @scene.pbDisplayPausedMessage(msg,&block) + end + + def pbDisplayConfirm(msg) + return @scene.pbDisplayConfirmMessage(msg) + end + + def pbShowCommands(msg,commands,canCancel=true) + @scene.pbShowCommands(msg,commands,canCancel) + end + + def pbAnimation(move,user,targets,hitNum=0) + @scene.pbAnimation(move,user,targets,hitNum) if @showAnims + end + + def pbCommonAnimation(name,user=nil,targets=nil,hitNum=0) + @scene.pbCommonAnimation(name,user,targets,hitNum) if @showAnims + end + + def pbShowAbilitySplash(battler,delay=false,logTrigger=true) + PBDebug.log("[Ability triggered] #{battler.pbThis}'s #{battler.abilityName}") if logTrigger + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @scene.pbShowAbilitySplash(battler) + if delay + Graphics.frame_rate.times { @scene.pbUpdate } # 1 second + end + end + + def pbHideAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @scene.pbHideAbilitySplash(battler) + end + + def pbReplaceAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @scene.pbReplaceAbilitySplash(battler) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/003_Battle_StartAndEnd.rb b/Data/Scripts/011_Battle/003_Battle/003_Battle_StartAndEnd.rb new file mode 100644 index 000000000..2149baf8f --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/003_Battle_StartAndEnd.rb @@ -0,0 +1,537 @@ +class PokeBattle_Battle + class BattleAbortedException < Exception; end + + def pbAbort + raise BattleAbortedException.new("Battle aborted") + end + + #============================================================================= + # Makes sure all Pokémon exist that need to. Alter the type of battle if + # necessary. Will never try to create battler positions, only delete them + # (except for wild Pokémon whose number of positions are fixed). Reduces the + # size of each side by 1 and tries again. If the side sizes are uneven, only + # the larger side's size will be reduced by 1 each time, until both sides are + # an equal size (then both sides will be reduced equally). + #============================================================================= + def pbEnsureParticipants + # Prevent battles larger than 2v2 if both sides have multiple trainers + # NOTE: This is necessary to ensure that battlers can never become unable to + # hit each other due to being too far away. In such situations, + # battlers will move to the centre position at the end of a round, but + # because they cannot move into a position owned by a different + # trainer, it's possible that battlers will be unable to move close + # enough to hit each other if there are multiple trainers on each + # side. + if trainerBattle? && (@sideSizes[0]>2 || @sideSizes[1]>2) && + @player.length>1 && @opponent.length>1 + raise _INTL("Can't have battles larger than 2v2 where both sides have multiple trainers") + end + # Find out how many Pokémon each trainer has + side1counts = pbAbleTeamCounts(0) + side2counts = pbAbleTeamCounts(1) + # Change the size of the battle depending on how many wild Pokémon there are + if wildBattle? && side2counts[0]!=@sideSizes[1] + if @sideSizes[0]==@sideSizes[1] + # Even number of battlers per side, change both equally + @sideSizes = [side2counts[0],side2counts[0]] + else + # Uneven number of battlers per side, just change wild side's size + @sideSizes[1] = side2counts[0] + end + end + # Check if battle is possible, including changing the number of battlers per + # side if necessary + loop do + needsChanging = false + for side in 0...2 # Each side in turn + next if side==1 && wildBattle? # Wild side's size already checked above + sideCounts = (side==0) ? side1counts : side2counts + requireds = [] + # Find out how many Pokémon each trainer on side needs to have + for i in 0...@sideSizes[side] + idxTrainer = pbGetOwnerIndexFromBattlerIndex(i*2+side) + requireds[idxTrainer] = 0 if requireds[idxTrainer].nil? + requireds[idxTrainer] += 1 + end + # Compare the have values with the need values + if requireds.length>sideCounts.length + raise _INTL("Error: def pbGetOwnerIndexFromBattlerIndex gives invalid owner index ({1} for battle type {2}v{3}, trainers {4}v{5})", + requireds.length-1,@sideSizes[0],@sideSizes[1],side1counts.length,side2counts.length) + end + sideCounts.each_with_index do |count,i| + if !requireds[i] || requireds[i]==0 + raise _INTL("Player-side trainer {1} has no battler position for their Pokémon to go (trying {2}v{3} battle)", + i+1,@sideSizes[0],@sideSizes[1]) if side==0 + raise _INTL("Opposing trainer {1} has no battler position for their Pokémon to go (trying {2}v{3} battle)", + i+1,@sideSizes[0],@sideSizes[1]) if side==1 + end + next if requireds[i]<=sideCounts[i] # Trainer has enough Pokémon to fill their positions + if requireds[i]==1 + raise _INTL("Player-side trainer {1} has no able Pokémon",i+1) if side==0 + raise _INTL("Opposing trainer {1} has no able Pokémon",i+1) if side==1 + end + # Not enough Pokémon, try lowering the number of battler positions + needsChanging = true + break + end + break if needsChanging + end + break if !needsChanging + # Reduce one or both side's sizes by 1 and try again + if wildBattle? + PBDebug.log("#{@sideSizes[0]}v#{@sideSizes[1]} battle isn't possible " + + "(#{side1counts} player-side teams versus #{side2counts[0]} wild Pokémon)") + newSize = @sideSizes[0]-1 + else + PBDebug.log("#{@sideSizes[0]}v#{@sideSizes[1]} battle isn't possible " + + "(#{side1counts} player-side teams versus #{side2counts} opposing teams)") + newSize = @sideSizes.max-1 + end + if newSize==0 + raise _INTL("Couldn't lower either side's size any further, battle isn't possible") + end + for side in 0...2 + next if side==1 && wildBattle? # Wild Pokémon's side size is fixed + next if @sideSizes[side]==1 || newSize>@sideSizes[side] + @sideSizes[side] = newSize + end + PBDebug.log("Trying #{@sideSizes[0]}v#{@sideSizes[1]} battle instead") + end + end + + #============================================================================= + # Set up all battlers + #============================================================================= + def pbCreateBattler(idxBattler,pkmn,idxParty) + if !@battlers[idxBattler].nil? + raise _INTL("Battler index {1} already exists",idxBattler) + end + @battlers[idxBattler] = PokeBattle_Battler.new(self,idxBattler) + @positions[idxBattler] = PokeBattle_ActivePosition.new + pbClearChoice(idxBattler) + @successStates[idxBattler] = PokeBattle_SuccessState.new + @battlers[idxBattler].pbInitialize(pkmn,idxParty) + end + + def pbSetUpSides + ret = [[],[]] + for side in 0...2 + # Set up wild Pokémon + if side==1 && wildBattle? + pbParty(1).each_with_index do |pkmn,idxPkmn| + pbCreateBattler(2*idxPkmn+side,pkmn,idxPkmn) + # Changes the Pokémon's form upon entering battle (if it should) + @peer.pbOnEnteringBattle(self,pkmn,true) + pbSetSeen(@battlers[2*idxPkmn+side]) + @usedInBattle[side][idxPkmn] = true + end + next + end + # Set up player's Pokémon and trainers' Pokémon + trainer = (side==0) ? @player : @opponent + requireds = [] + # Find out how many Pokémon each trainer on side needs to have + for i in 0...@sideSizes[side] + idxTrainer = pbGetOwnerIndexFromBattlerIndex(i*2+side) + requireds[idxTrainer] = 0 if requireds[idxTrainer].nil? + requireds[idxTrainer] += 1 + end + # For each trainer in turn, find the needed number of Pokémon for them to + # send out, and initialize them + battlerNumber = 0 + trainer.each_with_index do |t,idxTrainer| + ret[side][idxTrainer] = [] + eachInTeam(side,idxTrainer) do |pkmn,idxPkmn| + next if !pkmn.able? + idxBattler = 2*battlerNumber+side + pbCreateBattler(idxBattler,pkmn,idxPkmn) + ret[side][idxTrainer].push(idxBattler) + battlerNumber += 1 + break if ret[side][idxTrainer].length>=requireds[idxTrainer] + end + end + end + return ret + end + + #============================================================================= + # Send out all battlers at the start of battle + #============================================================================= + def pbStartBattleSendOut(sendOuts) + # "Want to battle" messages + if wildBattle? + foeParty = pbParty(1) + case foeParty.length + when 1 + pbDisplayPaused(_INTL("Oh! A wild {1} appeared!",foeParty[0].name)) + when 2 + pbDisplayPaused(_INTL("Oh! A wild {1} and {2} appeared!",foeParty[0].name, + foeParty[1].name)) + when 3 + pbDisplayPaused(_INTL("Oh! A wild {1}, {2} and {3} appeared!",foeParty[0].name, + foeParty[1].name,foeParty[2].name)) + end + else # Trainer battle + case @opponent.length + when 1 + pbDisplayPaused(_INTL("You are challenged by {1}!",@opponent[0].fullname)) + when 2 + pbDisplayPaused(_INTL("You are challenged by {1} and {2}!",@opponent[0].fullname, + @opponent[1].fullname)) + when 3 + pbDisplayPaused(_INTL("You are challenged by {1}, {2} and {3}!", + @opponent[0].fullname,@opponent[1].fullname,@opponent[2].fullname)) + end + end + # Send out Pokémon (opposing trainers first) + for side in [1,0] + next if side==1 && wildBattle? + msg = "" + toSendOut = [] + trainers = (side==0) ? @player : @opponent + # Opposing trainers and partner trainers's messages about sending out Pokémon + trainers.each_with_index do |t,i| + next if side==0 && i==0 # The player's message is shown last + msg += "\r\n" if msg.length>0 + sent = sendOuts[side][i] + case sent.length + when 1 + msg += _INTL("{1} sent out {2}!",t.fullname,@battlers[sent[0]].name) + when 2 + msg += _INTL("{1} sent out {2} and {3}!",t.fullname, + @battlers[sent[0]].name,@battlers[sent[1]].name) + when 3 + msg += _INTL("{1} sent out {2}, {3} and {4}!",t.fullname, + @battlers[sent[0]].name,@battlers[sent[1]].name,@battlers[sent[2]].name) + end + toSendOut.concat(sent) + end + # The player's message about sending out Pokémon + if side==0 + msg += "\r\n" if msg.length>0 + sent = sendOuts[side][0] + case sent.length + when 1 + msg += _INTL("Go! {1}!",@battlers[sent[0]].name) + when 2 + msg += _INTL("Go! {1} and {2}!",@battlers[sent[0]].name,@battlers[sent[1]].name) + when 3 + msg += _INTL("Go! {1}, {2} and {3}!",@battlers[sent[0]].name, + @battlers[sent[1]].name,@battlers[sent[2]].name) + end + toSendOut.concat(sent) + end + pbDisplayBrief(msg) if msg.length>0 + # The actual sending out of Pokémon + animSendOuts = [] + toSendOut.each do |idxBattler| + animSendOuts.push([idxBattler,@battlers[idxBattler].pokemon]) + end + pbSendOut(animSendOuts,true) + end + end + + #============================================================================= + # Start a battle + #============================================================================= + def pbStartBattle + PBDebug.log("") + PBDebug.log("******************************************") + logMsg = "[Started battle] " + if @sideSizes[0]==1 && @sideSizes[1]==1 + logMsg += "Single " + elsif @sideSizes[0]==2 && @sideSizes[1]==2 + logMsg += "Double " + elsif @sideSizes[0]==3 && @sideSizes[1]==3 + logMsg += "Triple " + else + logMsg += "#{@sideSizes[0]}v#{@sideSizes[1]} " + end + logMsg += "wild " if wildBattle? + logMsg += "trainer " if trainerBattle? + logMsg += "battle (#{@player.length} trainer(s) vs. " + logMsg += "#{pbParty(1).length} wild Pokémon)" if wildBattle? + logMsg += "#{@opponent.length} trainer(s))" if trainerBattle? + PBDebug.log(logMsg) + pbEnsureParticipants + begin + pbStartBattleCore + rescue BattleAbortedException + @decision = 0 + @scene.pbEndBattle(@decision) + end + return @decision + end + + def pbStartBattleCore + # Set up the battlers on each side + sendOuts = pbSetUpSides + # Create all the sprites and play the battle intro animation + @scene.pbStartBattle(self) + # Show trainers on both sides sending out Pokémon + pbStartBattleSendOut(sendOuts) + # Weather announcement + pbCommonAnimation(PBWeather.animationName(@field.weather)) + case @field.weather + when PBWeather::Sun; pbDisplay(_INTL("The sunlight is strong.")) + when PBWeather::Rain; pbDisplay(_INTL("It is raining.")) + when PBWeather::Sandstorm; pbDisplay(_INTL("A sandstorm is raging.")) + when PBWeather::Hail; pbDisplay(_INTL("Hail is falling.")) + when PBWeather::HarshSun; pbDisplay(_INTL("The sunlight is extremely harsh.")) + when PBWeather::HeavyRain; pbDisplay(_INTL("It is raining heavily.")) + when PBWeather::StrongWinds; pbDisplay(_INTL("The wind is strong.")) + when PBWeather::ShadowSky; pbDisplay(_INTL("The sky is shadowy.")) + end + # Terrain announcement + pbCommonAnimation(PBBattleTerrains.animationName(@field.terrain)) + case @field.terrain + when PBBattleTerrains::Electric + pbDisplay(_INTL("An electric current runs across the battlefield!")) + when PBBattleTerrains::Grassy + pbDisplay(_INTL("Grass is covering the battlefield!")) + when PBBattleTerrains::Misty + pbDisplay(_INTL("Mist swirls about the battlefield!")) + when PBBattleTerrains::Psychic + pbDisplay(_INTL("The battlefield is weird!")) + end + # Abilities upon entering battle + pbOnActiveAll + # Main battle loop + pbBattleLoop + end + + #============================================================================= + # Main battle loop + #============================================================================= + def pbBattleLoop + @turnCount = 0 + loop do # Now begin the battle loop + PBDebug.log("") + PBDebug.log("***Round #{@turnCount+1}***") + if @debug && @turnCount>=100 + @decision = pbDecisionOnTime + PBDebug.log("") + PBDebug.log("***Undecided after 100 rounds, aborting***") + pbAbort + break + end + PBDebug.log("") + # Command phase + PBDebug.logonerr { pbCommandPhase } + break if @decision>0 + # Attack phase + PBDebug.logonerr { pbAttackPhase } + break if @decision>0 + # End of round phase + PBDebug.logonerr { pbEndOfRoundPhase } + break if @decision>0 + @turnCount += 1 + end + pbEndOfBattle + end + + #============================================================================= + # End of battle + #============================================================================= + def pbGainMoney + return if !@internalBattle || !@moneyGain + # Money rewarded from opposing trainers + if trainerBattle? + tMoney = 0 + @opponent.each_with_index do |t,i| + tMoney += pbMaxLevelInTeam(1,i)*t.moneyEarned + end + tMoney *= 2 if @field.effects[PBEffects::AmuletCoin] + tMoney *= 2 if @field.effects[PBEffects::HappyHour] + oldMoney = pbPlayer.money + pbPlayer.money += tMoney + moneyGained = pbPlayer.money-oldMoney + if moneyGained>0 + pbDisplayPaused(_INTL("You got ${1} for winning!",moneyGained.to_s_formatted)) + end + end + # Pick up money scattered by Pay Day + if @field.effects[PBEffects::PayDay]>0 + @field.effects[PBEffects::PayDay] *= 2 if @field.effects[PBEffects::AmuletCoin] + @field.effects[PBEffects::PayDay] *= 2 if @field.effects[PBEffects::HappyHour] + oldMoney = pbPlayer.money + pbPlayer.money += @field.effects[PBEffects::PayDay] + moneyGained = pbPlayer.money-oldMoney + if moneyGained>0 + pbDisplayPaused(_INTL("You picked up ${1}!",moneyGained.to_s_formatted)) + end + end + end + + def pbLoseMoney + return if !@internalBattle || !@moneyGain + return if $game_switches[NO_MONEY_LOSS] + maxLevel = pbMaxLevelInTeam(0,0) # Player's Pokémon only, not partner's + multiplier = [8,16,24,36,48,64,80,100,120] + idxMultiplier = [pbPlayer.numbadges,multiplier.length-1].min + tMoney = maxLevel*multiplier[idxMultiplier] + tMoney = pbPlayer.money if tMoney>pbPlayer.money + oldMoney = pbPlayer.money + pbPlayer.money -= tMoney + moneyLost = oldMoney-pbPlayer.money + if moneyLost>0 + if trainerBattle? + pbDisplayPaused(_INTL("You gave ${1} to the winner...",moneyLost.to_s_formatted)) + else + pbDisplayPaused(_INTL("You panicked and dropped ${1}...",moneyLost.to_s_formatted)) + end + end + end + + def pbEndOfBattle + oldDecision = @decision + @decision = 4 if @decision==1 && wildBattle? && @caughtPokemon.length>0 + case oldDecision + ##### WIN ##### + when 1 + PBDebug.log("") + PBDebug.log("***Player won***") + if trainerBattle? + @scene.pbTrainerBattleSuccess + case @opponent.length + when 1 + pbDisplayPaused(_INTL("You defeated {1}!",@opponent[0].fullname)) + when 2 + pbDisplayPaused(_INTL("You defeated {1} and {2}!",@opponent[0].fullname, + @opponent[1].fullname)) + when 3 + pbDisplayPaused(_INTL("You defeated {1}, {2} and {3}!",@opponent[0].fullname, + @opponent[1].fullname,@opponent[2].fullname)) + end + @opponent.each_with_index do |t,i| + @scene.pbShowOpponent(i) + msg = (@endSpeeches[i] && @endSpeeches[i]!="") ? @endSpeeches[i] : "..." + pbDisplayPaused(msg.gsub(/\\[Pp][Nn]/,pbPlayer.name)) + end + end + # Gain money from winning a trainer battle, and from Pay Day + pbGainMoney if @decision!=4 + # Hide remaining trainer + @scene.pbShowOpponent(@opponent.length) if trainerBattle? && @caughtPokemon.length>0 + ##### LOSE, DRAW ##### + when 2, 5 + PBDebug.log("") + PBDebug.log("***Player lost***") if @decision==2 + PBDebug.log("***Player drew with opponent***") if @decision==5 + if @internalBattle + pbDisplayPaused(_INTL("You have no more Pokémon that can fight!")) + if trainerBattle? + case @opponent.length + when 1 + pbDisplayPaused(_INTL("You lost against {1}!",@opponent[0].fullname)) + when 2 + pbDisplayPaused(_INTL("You lost against {1} and {2}!", + @opponent[0].fullname,@opponent[1].fullname)) + when 3 + pbDisplayPaused(_INTL("You lost against {1}, {2} and {3}!", + @opponent[0].fullname,@opponent[1].fullname,@opponent[2].fullname)) + end + end + # Lose money from losing a battle + pbLoseMoney + pbDisplayPaused(_INTL("You blacked out!")) if !@canLose + elsif @decision==2 + if @opponent + @opponent.each_with_index do |t,i| + @scene.pbShowOpponent(i) + msg = (@endSpeechesWin[i] && @endSpeechesWin[i]!="") ? @endSpeechesWin[i] : "..." + pbDisplayPaused(msg.gsub(/\\[Pp][Nn]/,pbPlayer.name)) + end + end + end + ##### CAUGHT WILD POKÉMON ##### + when 4 + @scene.pbWildBattleSuccess if !GAIN_EXP_FOR_CAPTURE + end + # Register captured Pokémon in the Pokédex, and store them + pbRecordAndStoreCaughtPokemon + # Collect Pay Day money in a wild battle that ended in a capture + pbGainMoney if @decision==4 + # Pass on Pokérus within the party + if @internalBattle + infected = [] + $Trainer.party.each_with_index do |pkmn,i| + infected.push(i) if pkmn.pokerusStage==1 + end + infected.each do |idxParty| + strain = $Trainer.party[idxParty].pokerusStrain + if idxParty>0 && $Trainer.party[idxParty-1].pokerusStage==0 + $Trainer.party[idxParty-1].givePokerus(strain) if rand(3)==0 # 33% + end + if idxParty<$Trainer.party.length-1 && $Trainer.party[idxParty+1].pokerusStage==0 + $Trainer.party[idxParty+1].givePokerus(strain) if rand(3)==0 # 33% + end + end + end + # Clean up battle stuff + @scene.pbEndBattle(@decision) + @battlers.each do |b| + next if !b + pbCancelChoice(b.index) # Restore unused items to Bag + BattleHandlers.triggerAbilityOnSwitchOut(b.ability,b,true) if b.abilityActive? + end + pbParty(0).each_with_index do |pkmn,i| + next if !pkmn + @peer.pbOnLeavingBattle(self,pkmn,@usedInBattle[0][i],true) # Reset form + pkmn.setItem(@initialItems[0][i] || 0) + end + return @decision + end + + #============================================================================= + # Judging + #============================================================================= + def pbJudgeCheckpoint(user,move=nil); end + + def pbDecisionOnTime + counts = [0,0] + hpTotals = [0,0] + for side in 0...2 + pbParty(side).each do |pkmn| + next if !pkmn || !pkmn.able? + counts[side] += 1 + hpTotals[side] += pkmn.hp + end + end + return 1 if counts[0]>counts[1] # Win (player has more able Pokémon) + return 2 if counts[0]hpTotals[1] # Win (player has more HP in total) + return 2 if hpTotals[0]1 + end + return 1 if counts[0]>counts[1] # Win (player has more able Pokémon) + return 2 if counts[0]hpTotals[1] # Win (player has a bigger average HP %) + return 2 if hpTotals[0]0 || expShare.length>0 || expAll + # Gain EVs and Exp for participants + eachInTeam(0,0) do |pkmn,i| + next if !pkmn.able? + next unless b.participants.include?(i) || expShare.include?(i) + pbGainEVsOne(i,b) + pbGainExpOne(i,b,numPartic,expShare,expAll) + end + # Gain EVs and Exp for all other Pokémon because of Exp All + if expAll + showMessage = true + eachInTeam(0,0) do |pkmn,i| + next if !pkmn.able? + next if b.participants.include?(i) || expShare.include?(i) + pbDisplayPaused(_INTL("Your party Pokémon in waiting also got Exp. Points!")) if showMessage + showMessage = false + pbGainEVsOne(i,b) + pbGainExpOne(i,b,numPartic,expShare,expAll,false) + end + end + end + # Clear the participants array + b.participants = [] + end + end + + def pbGainEVsOne(idxParty,defeatedBattler) + pkmn = pbParty(0)[idxParty] # The Pokémon gaining EVs from defeatedBattler + evYield = defeatedBattler.pokemon.evYield + # Num of effort points pkmn already has + evTotal = 0 + PBStats.eachStat { |s| evTotal += pkmn.ev[s] } + # Modify EV yield based on pkmn's held item + if !BattleHandlers.triggerEVGainModifierItem(pkmn.item,pkmn,evYield) + BattleHandlers.triggerEVGainModifierItem(@initialItems[0][idxParty],pkmn,evYield) + end + # Double EV gain because of Pokérus + if pkmn.pokerusStage>=1 # Infected or cured + evYield.collect! { |a| a*2 } + end + # Gain EVs for each stat in turn + PBStats.eachStat do |s| + evGain = evYield[s] + # Can't exceed overall limit + if evTotal+evGain>PokeBattle_Pokemon::EV_LIMIT + evGain = PokeBattle_Pokemon::EV_LIMIT-evTotal + end + # Can't exceed individual stat limit + if pkmn.ev[s]+evGain>PokeBattle_Pokemon::EV_STAT_LIMIT + evGain = PokeBattle_Pokemon::EV_STAT_LIMIT-pkmn.ev[s] + end + # Add EV gain + pkmn.ev[s] += evGain + evTotal += evGain + end + end + + def pbGainExpOne(idxParty,defeatedBattler,numPartic,expShare,expAll,showMessages=true) + pkmn = pbParty(0)[idxParty] # The Pokémon gaining EVs from defeatedBattler + growthRate = pkmn.growthrate + # Don't bother calculating if gainer is already at max Exp + if pkmn.exp>=PBExperience.pbGetMaxExperience(growthRate) + pkmn.calcStats # To ensure new EVs still have an effect + return + end + isPartic = defeatedBattler.participants.include?(idxParty) + hasExpShare = expShare.include?(idxParty) + level = defeatedBattler.level + # Main Exp calculation + exp = 0 + a = level*defeatedBattler.pokemon.baseExp + if expShare.length>0 && (isPartic || hasExpShare) + if numPartic==0 # No participants, all Exp goes to Exp Share holders + exp = a/(SPLIT_EXP_BETWEEN_GAINERS ? expShare.length : 1) + elsif SPLIT_EXP_BETWEEN_GAINERS # Gain from participating and/or Exp Share + exp = a/(2*numPartic) if isPartic + exp += a/(2*expShare.length) if hasExpShare + else # Gain from participating and/or Exp Share (Exp not split) + exp = (isPartic) ? a : a/2 + end + elsif isPartic # Participated in battle, no Exp Shares held by anyone + exp = a/(SPLIT_EXP_BETWEEN_GAINERS ? numPartic : 1) + elsif expAll # Didn't participate in battle, gaining Exp due to Exp All + # NOTE: Exp All works like the Exp Share from Gen 6+, not like the Exp All + # from Gen 1, i.e. Exp isn't split between all Pokémon gaining it. + exp = a/2 + end + return if exp<=0 + # Pokémon gain more Exp from trainer battles + exp = (exp*1.5).floor if trainerBattle? + # Scale the gained Exp based on the gainer's level (or not) + if SCALED_EXP_FORMULA + exp /= 5 + levelAdjust = (2*level+10.0)/(pkmn.level+level+10.0) + levelAdjust = levelAdjust**5 + levelAdjust = Math.sqrt(levelAdjust) + exp *= levelAdjust + exp = exp.floor + exp += 1 if isPartic || hasExpShare + else + exp /= 7 + end + # Foreign Pokémon gain more Exp + isOutsider = (pkmn.trainerID!=pbPlayer.id || + (pkmn.language!=0 && pkmn.language!=pbPlayer.language)) + if isOutsider + if pkmn.language!=0 && pkmn.language!=pbPlayer.language + exp = (exp*1.7).floor + else + exp = (exp*1.5).floor + end + end + # Modify Exp gain based on pkmn's held item + i = BattleHandlers.triggerExpGainModifierItem(pkmn.item,pkmn,exp) + if i<0 + i = BattleHandlers.triggerExpGainModifierItem(@initialItems[0][idxParty],pkmn,exp) + end + exp = i if i>=0 + # Make sure Exp doesn't exceed the maximum + expFinal = PBExperience.pbAddExperience(pkmn.exp,exp,growthRate) + expGained = expFinal-pkmn.exp + return if expGained<=0 + # "Exp gained" message + if showMessages + if isOutsider + pbDisplayPaused(_INTL("{1} got a boosted {2} Exp. Points!",pkmn.name,expGained)) + else + pbDisplayPaused(_INTL("{1} got {2} Exp. Points!",pkmn.name,expGained)) + end + end + curLevel = pkmn.level + newLevel = PBExperience.pbGetLevelFromExperience(expFinal,growthRate) + if newLevelnewLevel + # Gained all the Exp now, end the animation + pkmn.calcStats + battler.pbUpdate(false) if battler + @scene.pbRefreshOne(battler.index) if battler + break + end + # Levelled up + pbCommonAnimation("LevelUp",battler) if battler + oldTotalHP = pkmn.totalhp + oldAttack = pkmn.attack + oldDefense = pkmn.defense + oldSpAtk = pkmn.spatk + oldSpDef = pkmn.spdef + oldSpeed = pkmn.speed + if battler && battler.pokemon + battler.pokemon.changeHappiness("levelup") + end + pkmn.calcStats + battler.pbUpdate(false) if battler + @scene.pbRefreshOne(battler.index) if battler + pbDisplayPaused(_INTL("{1} grew to Lv. {2}!",pkmn.name,curLevel)) + @scene.pbLevelUp(pkmn,battler,oldTotalHP,oldAttack,oldDefense, + oldSpAtk,oldSpDef,oldSpeed) + # Learn all moves learned at this level + moveList = pkmn.getMoveList + moveList.each { |m| pbLearnMove(idxParty,m[1]) if m[0]==curLevel } + end + end + + #============================================================================= + # Learning a move + #============================================================================= + def pbLearnMove(idxParty,newMove) + pkmn = pbParty(0)[idxParty] + return if !pkmn + pkmnName = pkmn.name + battler = pbFindBattler(idxParty) + moveName = PBMoves.getName(newMove) + # Find a space for the new move in pkmn's moveset and learn it + pkmn.moves.each_with_index do |m,i| + return if m.id==newMove # Already knows the new move + next if m.id!=0 # Not a blank move slot + pkmn.moves[i] = PBMove.new(newMove) + battler.moves[i] = PokeBattle_Move.pbFromPBMove(self,pkmn.moves[i]) if battler + pbDisplay(_INTL("{1} learned {2}!",pkmnName,moveName)) { pbSEPlay("Pkmn move learnt") } + battler.pbCheckFormOnMovesetChange if battler + return + end + # pkmn already knows four moves, need to forget one to learn newMove + loop do + pbDisplayPaused(_INTL("{1} wants to learn {2}, but it already knows four moves.",pkmnName,moveName)) + if pbDisplayConfirm(_INTL("Forget a move to learn {1}?",moveName)) + pbDisplayPaused(_INTL("Which move should be forgotten?")) + forgetMove = @scene.pbForgetMove(pkmn,newMove) + if forgetMove>=0 + oldMoveName = PBMoves.getName(pkmn.moves[forgetMove].id) + pkmn.moves[forgetMove] = PBMove.new(newMove) # Replaces current/total PP + battler.moves[forgetMove] = PokeBattle_Move.pbFromPBMove(self,pkmn.moves[forgetMove]) if battler + pbDisplayPaused(_INTL("1, 2, and... ... ... Ta-da!")) + pbDisplayPaused(_INTL("{1} forgot how to use {2}. And...",pkmnName,oldMoveName)) + pbDisplay(_INTL("{1} learned {2}!",pkmnName,moveName)) { pbSEPlay("Pkmn move learnt") } + battler.pbCheckFormOnMovesetChange if battler + break + elsif pbDisplayConfirm(_INTL("Give up on learning {1}?",moveName)) + pbDisplay(_INTL("{1} did not learn {2}.",pkmnName,moveName)) + break + end + elsif pbDisplayConfirm(_INTL("Give up on learning {1}?",moveName)) + pbDisplay(_INTL("{1} did not learn {2}.",pkmnName,moveName)) + break + end + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/005_Battle_Action_AttacksPriority.rb b/Data/Scripts/011_Battle/003_Battle/005_Battle_Action_AttacksPriority.rb new file mode 100644 index 000000000..c4b7f5b5b --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/005_Battle_Action_AttacksPriority.rb @@ -0,0 +1,254 @@ +class PokeBattle_Battle + #============================================================================= + # Choosing a move/target + #============================================================================= + def pbCanChooseMove?(idxBattler,idxMove,showMessages,sleepTalk=false) + battler = @battlers[idxBattler] + move = battler.moves[idxMove] + return false unless move && move.id>0 + if move.pp==0 && move.totalpp>0 && !sleepTalk + pbDisplayPaused(_INTL("There's no PP left for this move!")) if showMessages + return false + end + if battler.effects[PBEffects::Encore]>0 + idxEncoredMove = battler.pbEncoredMoveIndex + return false if idxEncoredMove>=0 && idxMove!=idxEncoredMove + end + return battler.pbCanChooseMove?(move,true,showMessages,sleepTalk) + end + + def pbCanChooseAnyMove?(idxBattler,sleepTalk=false) + battler = @battlers[idxBattler] + battler.eachMoveWithIndex do |m,i| + next if m.pp==0 && m.totalpp>0 && !sleepTalk + if battler.effects[PBEffects::Encore]>0 + idxEncoredMove = battler.pbEncoredMoveIndex + next if idxEncoredMove>=0 && i!=idxEncoredMove + end + next if !battler.pbCanChooseMove?(m,true,false,sleepTalk) + return true + end + return false + end + + # Called when the Pokémon is Encored, or if it can't use any of its moves. + # Makes the Pokémon use the Encored move (if Encored), or Struggle. + def pbAutoChooseMove(idxBattler,showMessages=true) + battler = @battlers[idxBattler] + if battler.fainted? + pbClearChoice(idxBattler) + return true + end + # Encore + idxEncoredMove = battler.pbEncoredMoveIndex + if idxEncoredMove>=0 && pbCanChooseMove?(idxBattler,idxEncoredMove,false) + encoreMove = battler.moves[idxEncoredMove] + @choices[idxBattler][0] = :UseMove # "Use move" + @choices[idxBattler][1] = idxEncoredMove # Index of move to be used + @choices[idxBattler][2] = encoreMove # PokeBattle_Move object + @choices[idxBattler][3] = -1 # No target chosen yet + return true if singleBattle? + if pbOwnedByPlayer?(idxBattler) + if showMessages + pbDisplayPaused(_INTL("{1} has to use {2}!",battler.name,encoreMove.name)) + end + return pbChooseTarget(battler,encoreMove) + end + return true + end + # Struggle + if pbOwnedByPlayer?(idxBattler) && showMessages + pbDisplayPaused(_INTL("{1} has no moves left!",battler.name)) + end + @choices[idxBattler][0] = :UseMove # "Use move" + @choices[idxBattler][1] = -1 # Index of move to be used + @choices[idxBattler][2] = @struggle # Struggle PokeBattle_Move object + @choices[idxBattler][3] = -1 # No target chosen yet + return true + end + + def pbRegisterMove(idxBattler,idxMove,showMessages=true) + battler = @battlers[idxBattler] + move = battler.moves[idxMove] + return false if !pbCanChooseMove?(idxBattler,idxMove,showMessages) + @choices[idxBattler][0] = :UseMove # "Use move" + @choices[idxBattler][1] = idxMove # Index of move to be used + @choices[idxBattler][2] = move # PokeBattle_Move object + @choices[idxBattler][3] = -1 # No target chosen yet + return true + end + + def pbChoseMove?(idxBattler,moveID) + return false if !@battlers[idxBattler] || @battlers[idxBattler].fainted? + idxMove = @choices[idxBattler][1] + if @choices[idxBattler][0]==:UseMove && idxMove>=0 + chosenMoveID = @battlers[idxBattler].moves[idxMove].id + return isConst?(chosenMoveID,PBMoves,moveID) + end + return false + end + + def pbChoseMoveFunctionCode?(idxBattler,code) + return false if @battlers[idxBattler].fainted? + idxMove = @choices[idxBattler][1] + if @choices[idxBattler][0]==:UseMove && idxMove>=0 + return @battlers[idxBattler].moves[idxMove].function==code + end + return false + end + + def pbRegisterTarget(idxBattler,idxTarget) + @choices[idxBattler][3] = idxTarget # Set target of move + end + + # Returns whether the idxTarget will be targeted by a move with targetType + # used by a battler in idxUser. + def pbMoveCanTarget?(idxUser,idxTarget,targetType) + return false if PBTargets.noTargets?(targetType) + case targetType + when PBTargets::NearAlly + return false if opposes?(idxUser,idxTarget) + return false if !nearBattlers?(idxUser,idxTarget) + when PBTargets::UserOrNearAlly + return true if idxUser==idxTarget + return false if opposes?(idxUser,idxTarget) + return false if !nearBattlers?(idxUser,idxTarget) + when PBTargets::NearFoe, PBTargets::AllNearFoes, PBTargets::RandomNearFoe + return false if !opposes?(idxUser,idxTarget) + return false if !nearBattlers?(idxUser,idxTarget) + when PBTargets::Foe + return false if !opposes?(idxUser,idxTarget) + when PBTargets::NearOther, PBTargets::AllNearOthers + return false if !nearBattlers?(idxUser,idxTarget) + when PBTargets::Other + return false if idxUser==idxTarget + when PBTargets::UserAndAllies + return false if opposes?(idxUser,idxTarget) + when PBTargets::AllFoes + return false if !opposes?(idxUser,idxTarget) + end + return true + end + + #============================================================================= + # Turn order calculation (priority) + #============================================================================= + def pbCalculatePriority(fullCalc=false,indexArray=nil) + needRearranging = false + if fullCalc + @priorityTrickRoom = (@field.effects[PBEffects::TrickRoom]>0) + # Recalculate everything from scratch + randomOrder = Array.new(maxBattlerIndex+1) { |i| i } + (randomOrder.length-1).times do |i| # Can't use shuffle! here + r = i+pbRandom(randomOrder.length-i) + randomOrder[i], randomOrder[r] = randomOrder[r], randomOrder[i] + end + @priority.clear + for i in 0..maxBattlerIndex + b = @battlers[i] + next if !b + # [battler, speed, sub-priority, priority, tie-breaker order] + bArray = [b,b.pbSpeed,0,0,randomOrder[i]] + if @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + # Calculate move's priority + if @choices[b.index][0]==:UseMove + move = @choices[b.index][2] + pri = move.priority + if b.abilityActive? + pri = BattleHandlers.triggerPriorityChangeAbility(b.ability,b,move,pri) + end + bArray[3] = pri + @choices[b.index][4] = pri + end + # Calculate sub-priority (first/last within priority bracket) + # NOTE: Going fast beats going slow. A Pokémon with Stall and Quick + # Claw will go first in its priority bracket if Quick Claw + # triggers, regardless of Stall. + subPri = 0 + # Abilities (Stall) + if b.abilityActive? + newSubPri = BattleHandlers.triggerPriorityBracketChangeAbility(b.ability, + b,subPri,self) + if subPri!=newSubPri + subPri = newSubPri + b.effects[PBEffects::PriorityAbility] = true + b.effects[PBEffects::PriorityItem] = false + end + end + # Items (Quick Claw, Custap Berry, Lagging Tail, Full Incense) + if b.itemActive? + newSubPri = BattleHandlers.triggerPriorityBracketChangeItem(b.item, + b,subPri,self) + if subPri!=newSubPri + subPri = newSubPri + b.effects[PBEffects::PriorityAbility] = false + b.effects[PBEffects::PriorityItem] = true + end + end + bArray[2] = subPri + end + @priority.push(bArray) + end + needRearranging = true + else + if (@field.effects[PBEffects::TrickRoom]>0)!=@priorityTrickRoom + needRearranging = true + @priorityTrickRoom = (@field.effects[PBEffects::TrickRoom]>0) + end + # Just recheck all battler speeds + @priority.each do |orderArray| + next if !orderArray + next if indexArray && !indexArray.include?(orderArray[0].index) + oldSpeed = orderArray[1] + orderArray[1] = orderArray[0].pbSpeed + needRearranging = true if orderArray[1]!=oldSpeed + end + end + # Reorder the priority array + if needRearranging + @priority.sort! { |a,b| + if a[3]!=b[3] + # Sort by priority (highest value first) + b[3]<=>a[3] + elsif a[2]!=b[2] + # Sort by sub-priority (highest value first) + b[2]<=>a[2] + elsif @priorityTrickRoom + # Sort by speed (lowest first), and use tie-breaker if necessary + (a[1]==b[1]) ? b[4]<=>a[4] : a[1]<=>b[1] + else + # Sort by speed (highest first), and use tie-breaker if necessary + (a[1]==b[1]) ? b[4]<=>a[4] : b[1]<=>a[1] + end + } + # Write the priority order to the debug log + logMsg = (fullCalc) ? "[Round order] " : "[Round order recalculated] " + comma = false + @priority.each do |orderArray| + logMsg += ", " if comma + logMsg += "#{orderArray[0].pbThis(comma)} (#{orderArray[0].index})" + comma = true + end + PBDebug.log(logMsg) + end + end + + # NOTE: In Gen 8, battler speeds stored in priority are recalculated far more + # frequently than they used to be. There are several quoted-out calls to + # pbCalculatePriority in these scripts which do this; just unquote them. + def pbPriority(onlySpeedSort=false) + ret = [] + if onlySpeedSort + # Sort battlers by their speed stats and tie-breaker order only. + tempArray = [] + @priority.each { |pArray| tempArray.push([pArray[0],pArray[1],pArray[4]]) } + tempArray.sort! { |a,b| (a[1]==b[1]) ? b[2]<=>a[2] : b[1]<=>a[1] } + tempArray.each { |tArray| ret.push(tArray[0]) } + else + # Sort battlers by priority, sub-priority and their speed. Ties are + # resolved in the same way each time this method is called in a round. + @priority.each { |pArray| ret.push(pArray[0]) if !pArray[0].fainted? } + end + return ret + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/006_Battle_Action_Switching.rb b/Data/Scripts/011_Battle/003_Battle/006_Battle_Action_Switching.rb new file mode 100644 index 000000000..3ef5dc5b4 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/006_Battle_Action_Switching.rb @@ -0,0 +1,411 @@ +class PokeBattle_Battle + #============================================================================= + # Choosing Pokémon to switch + #============================================================================= + # Checks whether the replacement Pokémon (at party index idxParty) can enter + # battle. + # NOTE: Messages are only shown while in the party screen when choosing a + # command for the next round. + def pbCanSwitchLax?(idxBattler,idxParty,partyScene=nil) + return true if idxParty<0 + party = pbParty(idxBattler) + return false if idxParty>=party.length + return false if !party[idxParty] + if party[idxParty].egg? + partyScene.pbDisplay(_INTL("An Egg can't battle!")) if partyScene + return false + end + if !pbIsOwner?(idxBattler,idxParty) + owner = pbGetOwnerFromPartyIndex(idxBattler,idxParty) + partyScene.pbDisplay(_INTL("You can't switch {1}'s Pokémon with one of yours!", + owner.name)) if partyScene + return false + end + if party[idxParty].fainted? + partyScene.pbDisplay(_INTL("{1} has no energy left to battle!", + party[idxParty].name)) if partyScene + return false + end + if pbFindBattler(idxParty,idxBattler) + partyScene.pbDisplay(_INTL("{1} is already in battle!", + party[idxParty].name)) if partyScene + return false + end + return true + end + + # Check whether the currently active Pokémon (at battler index idxBattler) can + # switch out (and that its replacement at party index idxParty can switch in). + # NOTE: Messages are only shown while in the party screen when choosing a + # command for the next round. + def pbCanSwitch?(idxBattler,idxParty=-1,partyScene=nil) + # Check whether party Pokémon can switch in + return false if !pbCanSwitchLax?(idxBattler,idxParty,partyScene) + # Make sure another battler isn't already choosing to switch to the party + # Pokémon + eachSameSideBattler(idxBattler) do |b| + next if choices[b.index][0]!=:SwitchOut || choices[b.index][1]!=idxParty + partyScene.pbDisplay(_INTL("{1} has already been selected.", + pbParty(idxBattler)[idxParty].name)) if partyScene + return false + end + # Check whether battler can switch out + battler = @battlers[idxBattler] + return true if battler.fainted? + # Ability/item effects that allow switching no matter what + if battler.abilityActive? + if BattleHandlers.triggerCertainSwitchingUserAbility(battler.ability,battler,self) + return true + end + end + if battler.itemActive? + if BattleHandlers.triggerCertainSwitchingUserItem(battler.item,battler,self) + return true + end + end + # Other certain switching effects + return true if NEWEST_BATTLE_MECHANICS && battler.pbHasType?(:GHOST) + # Other certain trapping effects + if battler.effects[PBEffects::Trapping]>0 || + battler.effects[PBEffects::MeanLook]>=0 || + battler.effects[PBEffects::Ingrain] || + @field.effects[PBEffects::FairyLock]>0 + partyScene.pbDisplay(_INTL("{1} can't be switched out!",battler.pbThis)) if partyScene + return false + end + # Trapping abilities/items + eachOtherSideBattler(idxBattler) do |b| + next if !b.abilityActive? + if BattleHandlers.triggerTrappingTargetAbility(b.ability,battler,b,self) + partyScene.pbDisplay(_INTL("{1}'s {2} prevents switching!", + b.pbThis,b.abilityName)) if partyScene + return false + end + end + eachOtherSideBattler(idxBattler) do |b| + next if !b.itemActive? + if BattleHandlers.triggerTrappingTargetItem(b.item,battler,b,self) + partyScene.pbDisplay(_INTL("{1}'s {2} prevents switching!", + b.pbThis,b.itemName)) if partyScene + return false + end + end + return true + end + + def pbCanChooseNonActive?(idxBattler) + pbParty(idxBattler).each_with_index do |pkmn,i| + return true if pbCanSwitchLax?(idxBattler,i) + end + return false + end + + def pbRegisterSwitch(idxBattler,idxParty) + return false if !pbCanSwitch?(idxBattler,idxParty) + @choices[idxBattler][0] = :SwitchOut + @choices[idxBattler][1] = idxParty # Party index of Pokémon to switch in + @choices[idxBattler][2] = nil + return true + end + + #============================================================================= + # Open the party screen and potentially pick a replacement Pokémon (or AI + # chooses replacement) + #============================================================================= + # Open party screen and potentially choose a Pokémon to switch with. Used in + # all instances where the party screen is opened. + def pbPartyScreen(idxBattler,checkLaxOnly=false,canCancel=false,shouldRegister=false) + ret = -1 + @scene.pbPartyScreen(idxBattler,canCancel) { |idxParty,partyScene| + if checkLaxOnly + next false if !pbCanSwitchLax?(idxBattler,idxParty,partyScene) + else + next false if !pbCanSwitch?(idxBattler,idxParty,partyScene) + end + if shouldRegister + next false if idxParty<0 || !pbRegisterSwitch(idxBattler,idxParty) + end + ret = idxParty + next true + } + return ret + end + + # For choosing a replacement Pokémon when prompted in the middle of other + # things happening (U-turn, Baton Pass, in def pbSwitch). + def pbSwitchInBetween(idxBattler,checkLaxOnly=false,canCancel=false) + return pbPartyScreen(idxBattler,checkLaxOnly,canCancel) if pbOwnedByPlayer?(idxBattler) + return @battleAI.pbDefaultChooseNewEnemy(idxBattler,pbParty(idxBattler)) + end + + #============================================================================= + # Switching Pokémon + #============================================================================= + # General switching method that checks if any Pokémon need to be sent out and, + # if so, does. Called at the end of each round. + def pbEORSwitch(favorDraws=false) + return if @decision>0 && !favorDraws + return if @decision==5 && favorDraws + pbJudge + return if @decision>0 + # Check through each fainted battler to see if that spot can be filled. + switched = [] + loop do + switched.clear + @battlers.each do |b| + next if !b || !b.fainted? + idxBattler = b.index + next if !pbCanChooseNonActive?(idxBattler) + if !pbOwnedByPlayer?(idxBattler) # Opponent/ally is switching in + next if wildBattle? && opposes?(idxBattler) # Wild Pokémon can't switch + idxPartyNew = pbSwitchInBetween(idxBattler) + opponent = pbGetOwnerFromBattlerIndex(idxBattler) + # NOTE: The player is only offered the chance to switch their own + # Pokémon when an opponent replaces a fainted Pokémon in single + # battles. In double battles, etc. there is no such offer. + if @internalBattle && @switchStyle && trainerBattle? && pbSideSize(0)==1 && + opposes?(idxBattler) && !@battlers[0].fainted? && pbCanChooseNonActive?(0) && + @battlers[0].effects[PBEffects::Outrage]==0 + idxPartyForName = idxPartyNew + enemyParty = pbParty(idxBattler) + if isConst?(enemyParty[idxPartyNew].ability,PBAbilities,:ILLUSION) + idxPartyForName = pbGetLastPokeInTeam(idxBattler) + end + if pbDisplayConfirm(_INTL("{1} is about to send in {2}. Will you switch your Pokémon?", + opponent.fullname,enemyParty[idxPartyForName].name)) + idxPlayerPartyNew = pbSwitchInBetween(0,false,true) + if idxPlayerPartyNew>=0 + pbMessageOnRecall(@battlers[0]) + pbRecallAndReplace(0,idxPlayerPartyNew) + switched.push(0) + end + end + end + pbRecallAndReplace(idxBattler,idxPartyNew) + switched.push(idxBattler) + elsif trainerBattle? # Player switches in in a trainer battle + idxPlayerPartyNew = pbGetReplacementPokemonIndex(idxBattler) # Owner chooses + pbRecallAndReplace(idxBattler,idxPlayerPartyNew) + switched.push(idxBattler) + else # Player's Pokémon has fainted in a wild battle + switch = false + if !pbDisplayConfirm(_INTL("Use next Pokémon?")) + switch = (pbRun(idxBattler,true)<=0) + else + switch = true + end + if switch + idxPlayerPartyNew = pbGetReplacementPokemonIndex(idxBattler) # Owner chooses + pbRecallAndReplace(idxBattler,idxPlayerPartyNew) + switched.push(idxBattler) + end + end + end + break if switched.length==0 + pbPriority(true).each do |b| + b.pbEffectsOnSwitchIn(true) if switched.include?(b.index) + end + end + end + + def pbGetReplacementPokemonIndex(idxBattler,random=false) + if random + return -1 if !pbCanSwitch?(idxBattler) # Can battler switch out? + choices = [] # Find all Pokémon that can switch in + eachInTeamFromBattlerIndex(idxBattler) do |pkmn,i| + choices.push(i) if pbCanSwitchLax?(idxBattler,i) + end + return -1 if choices.length==0 + return choices[pbRandom(choices.length)] + else + return pbSwitchInBetween(idxBattler,true) + end + end + + # Actually performs the recalling and sending out in all situations. + def pbRecallAndReplace(idxBattler,idxParty,batonPass=false) + @scene.pbRecall(idxBattler) if !@battlers[idxBattler].fainted? + @battlers[idxBattler].pbAbilitiesOnSwitchOut # Inc. primordial weather check + @scene.pbShowPartyLineup(idxBattler&1) if pbSideSize(idxBattler)==1 + pbMessagesOnReplace(idxBattler,idxParty) + pbReplace(idxBattler,idxParty,batonPass) + end + + def pbMessageOnRecall(battler) + if battler.pbOwnedByPlayer? + if battler.hp<=battler.totalhp/4 + pbDisplayBrief(_INTL("Good job, {1}! Come back!",battler.name)) + elsif battler.hp<=battler.totalhp/2 + pbDisplayBrief(_INTL("OK, {1}! Come back!",battler.name)) + elsif battler.turnCount>=5 + pbDisplayBrief(_INTL("{1}, that’s enough! Come back!",battler.name)) + elsif battler.turnCount>=2 + pbDisplayBrief(_INTL("{1}, come back!",battler.name)) + else + pbDisplayBrief(_INTL("{1}, switch out! Come back!",battler.name)) + end + else + owner = pbGetOwnerName(b.index) + pbDisplayBrief(_INTL("{1} withdrew {2}!",owner,battler.name)) + end + end + + # Only called from def pbRecallAndReplace and Battle Arena's def pbSwitch. + def pbMessagesOnReplace(idxBattler,idxParty) + party = pbParty(idxBattler) + newPkmnName = party[idxParty].name + if isConst?(party[idxParty].ability,PBAbilities,:ILLUSION) + newPkmnName = party[pbLastInTeam(idxBattler)].name + end + if pbOwnedByPlayer?(idxBattler) + opposing = @battlers[idxBattler].pbDirectOpposing + if opposing.fainted? || opposing.hp==opposing.totalhp + pbDisplayBrief(_INTL("You're in charge, {1}!",newPkmnName)) + elsif opposing.hp>=opposing.totalhp/2 + pbDisplayBrief(_INTL("Go for it, {1}!",newPkmnName)) + elsif opposing.hp>=opposing.totalhp/4 + pbDisplayBrief(_INTL("Just a little more! Hang in there, {1}!",newPkmnName)) + else + pbDisplayBrief(_INTL("Your opponent's weak! Get 'em, {1}!",newPkmnName)) + end + else + owner = pbGetOwnerFromBattlerIndex(idxBattler) + pbDisplayBrief(_INTL("{1} sent out {2}!",owner.fullname,newPkmnName)) + end + end + + # Only called from def pbRecallAndReplace above and Battle Arena's def + # pbSwitch. + def pbReplace(idxBattler,idxParty,batonPass=false) + party = pbParty(idxBattler) + idxPartyOld = @battlers[idxBattler].pokemonIndex + # Initialise the new Pokémon + @battlers[idxBattler].pbInitialize(party[idxParty],idxParty,batonPass) + # Reorder the party for this battle + partyOrder = pbPartyOrder(idxBattler) + partyOrder[idxParty],partyOrder[idxPartyOld] = partyOrder[idxPartyOld],partyOrder[idxParty] + # Send out the new Pokémon + pbSendOut([[idxBattler,party[idxParty]]]) +# pbCalculatePriority(false,[idxBattler]) if NEWEST_BATTLE_MECHANICS + end + + # Called from def pbReplace above and at the start of battle. + # sendOuts is an array; each element is itself an array: [idxBattler,pkmn] + def pbSendOut(sendOuts,startBattle=false) + sendOuts.each { |b| @peer.pbOnEnteringBattle(self,b[1]) } + @scene.pbSendOutBattlers(sendOuts,startBattle) + sendOuts.each do |b| + @scene.pbResetMoveIndex(b[0]) + pbSetSeen(@battlers[b[0]]) + @usedInBattle[b[0]&1][b[0]/2] = true + end + end + + #============================================================================= + # Effects upon a Pokémon entering battle + #============================================================================= + # Called at the start of battle only. + def pbOnActiveAll + # Weather-inducing abilities, Trace, Imposter, etc. + pbCalculatePriority(true) + pbPriority(true).each { |b| b.pbEffectsOnSwitchIn(true) } + pbCalculatePriority + # Check forms are correct + eachBattler { |b| b.pbCheckForm } + end + + # Called when a Pokémon switches in (entry effects, entry hazards). + def pbOnActiveOne(battler) + return false if battler.fainted? + # Introduce Shadow Pokémon + if battler.opposes? && battler.shadowPokemon? + pbCommonAnimation("Shadow",battler) + pbDisplay(_INTL("Oh!\nA Shadow Pokémon!")) + end + # Record money-doubling effect of Amulet Coin/Luck Incense + if !battler.opposes? && (isConst?(battler.item,PBItems,:AMULETCOIN) || + isConst?(battler.item,PBItems,:LUCKINCENSE)) + @field.effects[PBEffects::AmuletCoin] = true + end + # Update battlers' participants (who will gain Exp/EVs when a battler faints) + eachBattler { |b| b.pbUpdateParticipants } + # Healing Wish + if @positions[battler.index].effects[PBEffects::HealingWish] + pbCommonAnimation("HealingWish",battler) + pbDisplay(_INTL("The healing wish came true for {1}!",battler.pbThis(true))) + battler.pbRecoverHP(battler.totalhp) + battler.pbCureStatus(false) + @positions[battler.index].effects[PBEffects::HealingWish] = false + end + # Lunar Dance + if @positions[battler.index].effects[PBEffects::LunarDance] + pbCommonAnimation("LunarDance",battler) + pbDisplay(_INTL("{1} became cloaked in mystical moonlight!",battler.pbThis)) + battler.pbRecoverHP(battler.totalhp) + battler.pbCureStatus(false) + battler.eachMove { |m| m.pp = m.totalpp } + @positions[battler.index].effects[PBEffects::LunarDance] = false + end + # Entry hazards + # Stealth Rock + if battler.pbOwnSide.effects[PBEffects::StealthRock] && battler.takesIndirectDamage? + aType = getConst(PBTypes,:ROCK) || 0 + bTypes = battler.pbTypes(true) + eff = PBTypes.getCombinedEffectiveness(aType,bTypes[0],bTypes[1],bTypes[2]) + if !PBTypes.ineffective?(eff) + eff = eff.to_f/PBTypeEffectiveness::NORMAL_EFFECTIVE + oldHP = battler.hp + battler.pbReduceHP(battler.totalhp*eff/8,false) + pbDisplay(_INTL("Pointed stones dug into {1}!",battler.pbThis)) + battler.pbItemHPHealCheck + if battler.pbAbilitiesOnDamageTaken(oldHP) # Switched out + return pbOnActiveOne(battler) # For replacement battler + end + end + end + # Spikes + if battler.pbOwnSide.effects[PBEffects::Spikes]>0 && battler.takesIndirectDamage? && + !battler.airborne? + spikesDiv = [8,6,4][battler.pbOwnSide.effects[PBEffects::Spikes]-1] + oldHP = battler.hp + battler.pbReduceHP(battler.totalhp/spikesDiv,false) + pbDisplay(_INTL("{1} is hurt by the spikes!",battler.pbThis)) + battler.pbItemHPHealCheck + if battler.pbAbilitiesOnDamageTaken(oldHP) # Switched out + return pbOnActiveOne(battler) # For replacement battler + end + end + # Toxic Spikes + if battler.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 && !battler.fainted? && + !battler.airborne? + if battler.pbHasType?(:POISON) + battler.pbOwnSide.effects[PBEffects::ToxicSpikes] = 0 + pbDisplay(_INTL("{1} absorbed the poison spikes!",battler.pbThis)) + elsif battler.pbCanPoison?(nil,false) + if battler.pbOwnSide.effects[PBEffects::ToxicSpikes]==2 + battler.pbPoison(nil,_INTL("{1} was badly poisoned by the poison spikes!",battler.pbThis),true) + else + battler.pbPoison(nil,_INTL("{1} was poisoned by the poison spikes!",battler.pbThis)) + end + end + end + # Sticky Web + if battler.pbOwnSide.effects[PBEffects::StickyWeb] && !battler.fainted? && + !battler.airborne? + pbDisplay(_INTL("{1} was caught in a sticky web!",battler.pbThis)) + if battler.pbCanLowerStatStage?(PBStats::SPEED) + battler.pbLowerStatStage(PBStats::SPEED,1,nil) + battler.pbItemStatRestoreCheck + end + end + # Battler faints if it is knocked out because of an entry hazard above + if battler.fainted? + battler.pbFaint + pbGainExp + pbJudge + return false + end + battler.pbCheckForm + return true + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/007_Battle_Action_UseItem.rb b/Data/Scripts/011_Battle/003_Battle/007_Battle_Action_UseItem.rb new file mode 100644 index 000000000..29ee51f57 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/007_Battle_Action_UseItem.rb @@ -0,0 +1,144 @@ +class PokeBattle_Battle + #============================================================================= + # Choosing to use an item + #============================================================================= + def pbCanUseItemOnPokemon?(item,pkmn,battler,scene,showMessages=true) + if pkmn.egg? + scene.pbDisplay(_INTL("It won't have any effect.")) if showMessages + return false + end + # Embargo + if battler && battler.effects[PBEffects::Embargo]>0 + scene.pbDisplay(_INTL("Embargo's effect prevents the item's use on {1}!", + battler.pbThis(true))) if showMessages + return false + end + return true + end + + # NOTE: Using a Poké Ball consumes all your actions for the round. The method + # below is one half of making this happen; the other half is in the + # ItemHandlers::CanUseInBattle for Poké Balls. + def pbItemUsesAllActions?(item) + return true if pbIsPokeBall?(item) + return false + end + + def pbRegisterItem(idxBattler,item,idxTarget=nil,idxMove=nil) + # Register for use of item on a Pokémon in the party + @choices[idxBattler][0] = :UseItem + @choices[idxBattler][1] = item # ID of item to be used + @choices[idxBattler][2] = idxTarget # Party index of Pokémon to use item on + @choices[idxBattler][3] = idxMove # Index of move to recharge (Ethers) + # Delete the item from the Bag. If it turns out it will have no effect, it + # will be re-added to the Bag later. + pbConsumeItemInBag(item,idxBattler) + return true + end + + #============================================================================= + # Using an item + #============================================================================= + def pbConsumeItemInBag(item,idxBattler) + return if item==0 + useType = pbGetItemData(item,ITEM_BATTLE_USE) + return if !useType || useType==0 || (useType>=6 && useType<=10) # Not consumed upon use + if pbOwnedByPlayer?(idxBattler) + if !$PokemonBag.pbDeleteItem(item) + raise _INTL("Tried to consume item that wasn't in the Bag somehow.") + end + else + items = pbGetOwnerItems(idxBattler) + items.each_with_index do |thisItem,i| + next if thisItem!=item + items[i] = nil + break + end + items.compact! + end + end + + def pbReturnUnusedItemToBag(item,idxBattler) + return if item==0 + useType = pbGetItemData(item,ITEM_BATTLE_USE) + return if !useType || useType==0 || (useType>=6 && useType<=10) # Not consumed upon use + if pbOwnedByPlayer?(idxBattler) + if $PokemonBag && $PokemonBag.pbCanStore?(item) + $PokemonBag.pbStoreItem(item) + else + raise _INTL("Couldn't return unused item to Bag somehow.") + end + else + items = pbGetOwnerItems(idxBattler) + items.push(item) if items + end + end + + def pbUseItemMessage(item,trainerName) + itemName = PBItems.getName(item) + if itemName.starts_with_vowel? + pbDisplayBrief(_INTL("{1} used an {2}.",trainerName,itemName)) + else + pbDisplayBrief(_INTL("{1} used a {2}.",trainerName,itemName)) + end + end + + # Uses an item on a Pokémon in the player's party. + def pbUseItemOnPokemon(item,idxParty,userBattler) + trainerName = pbGetOwnerName(userBattler.index) + pbUseItemMessage(item,trainerName) + pkmn = pbParty(userBattler.index)[idxParty] + battler = pbFindBattler(idxParty,userBattler.index) + ch = @choices[userBattler.index] + if ItemHandlers.triggerCanUseInBattle(item,pkmn,battler,ch[3],true,self,@scene,false) + ItemHandlers.triggerBattleUseOnPokemon(item,pkmn,battler,ch,@scene) + ch[1] = 0 # Delete item from choice + return + end + pbDisplay(_INTL("But it had no effect!")) + # Return unused item to Bag + pbReturnUnusedItemToBag(item,userBattler.index) + end + + # Uses an item on a Pokémon in battle that belongs to the player. + def pbUseItemOnBattler(item,idxParty,userBattler) + trainerName = pbGetOwnerName(userBattler.index) + pbUseItemMessage(item,trainerName) + pkmn = pbParty(userBattler.index)[idxParty] + battler = pbFindBattler(idxParty,userBattler.index) + ch = @choices[userBattler.index] + if ItemHandlers.triggerCanUseInBattle(item,pkmn,battler,ch[3],true,self,@scene,false) + ItemHandlers.triggerBattleUseOnBattler(item,battler,@scene) + ch[1] = 0 # Delete item from choice + return + end + pbDisplay(_INTL("But it's not where this item can be used!")) + # Return unused item to Bag + pbReturnUnusedItemToBag(item,userBattler.index) + end + + # Uses a Poké Ball in battle directly. + def pbUsePokeBallInBattle(item,idxBattler,userBattler) + idxBattler = userBattler.index if idxBattler<0 + battler = @battlers[idxBattler] + ItemHandlers.triggerUseInBattle(item,battler,self) + @choices[userBattler.index][1] = 0 # Delete item from choice + end + + # Uses an item in battle directly. + def pbUseItemInBattle(item,idxBattler,userBattler) + trainerName = pbGetOwnerName(userBattler.index) + pbUseItemMessage(item,trainerName) + battler = (idxBattler<0) ? userBattler : @battlers[idxBattler] + pkmn = battler.pokemon + ch = @choices[userBattler.index] + if ItemHandlers.triggerCanUseInBattle(item,pkmn,battler,ch[3],true,self,@scene,false) + ItemHandlers.triggerUseInBattle(item,battler,self) + ch[1] = 0 # Delete item from choice + return + end + pbDisplay(_INTL("But it had no effect!")) + # Return unused item to Bag + pbReturnUnusedItemToBag(item,userBattler.index) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/008_Battle_Action_Running.rb b/Data/Scripts/011_Battle/003_Battle/008_Battle_Action_Running.rb new file mode 100644 index 000000000..01ed9f073 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/008_Battle_Action_Running.rb @@ -0,0 +1,145 @@ +class PokeBattle_Battle + #============================================================================= + # Running from battle + #============================================================================= + def pbCanRun?(idxBattler) + return false if trainerBattle? + battler = @battlers[idxBattler] + return false if !@canRun && !battler.opposes? + return true if battler.pbHasType?(:GHOST) && NEWEST_BATTLE_MECHANICS + return true if battler.abilityActive? && + BattleHandlers.triggerRunFromBattleAbility(battler.ability,battler) + return true if battler.itemActive? && + BattleHandlers.triggerRunFromBattleItem(battler.item,battler) + return false if battler.effects[PBEffects::Trapping]>0 || + battler.effects[PBEffects::MeanLook]>=0 || + battler.effects[PBEffects::Ingrain] || + @field.effects[PBEffects::FairyLock]>0 + eachOtherSideBattler(idxBattler) do |b| + return false if b.abilityActive? && + BattleHandlers.triggerTrappingTargetAbility(b.ability,battler,b,self) + return false if b.itemActive? && + BattleHandlers.triggerTrappingTargetItem(b.item,battler,b,self) + end + return true + end + + # Return values: + # -1: Failed fleeing + # 0: Wasn't possible to attempt fleeing, continue choosing action for the round + # 1: Succeeded at fleeing, battle will end + # duringBattle is true for replacing a fainted Pokémon during the End Of Round + # phase, and false for choosing the Run command. + def pbRun(idxBattler,duringBattle=false) + battler = @battlers[idxBattler] + if battler.opposes? + return 0 if trainerBattle? + @choices[idxBattler][0] = :Run + @choices[idxBattler][1] = 0 + @choices[idxBattler][2] = nil + return -1 + end + # Fleeing from trainer battles + if trainerBattle? + if $DEBUG && Input.press?(Input::CTRL) + if pbDisplayConfirm(_INTL("Treat this battle as a win?")) + @decision = 1 + return 1 + elsif pbDisplayConfirm(_INTL("Treat this battle as a loss?")) + @decision = 2 + return 1 + end + elsif @internalBattle + pbDisplayPaused(_INTL("No! There's no running from a Trainer battle!")) + elsif pbDisplayConfirm(_INTL("Would you like to forfeit the match and quit now?")) + pbDisplay(_INTL("{1} forfeited the match!",self.pbPlayer.name)) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + return 0 + end + # Fleeing from wild battles + if $DEBUG && Input.press?(Input::CTRL) + pbDisplayPaused(_INTL("You got away safely!")) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + if !@canRun + pbDisplayPaused(_INTL("You can't escape!")) + return 0 + end + if !duringBattle + if battler.pbHasType?(:GHOST) && NEWEST_BATTLE_MECHANICS + pbDisplayPaused(_INTL("You got away safely!")) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + # Abilities that guarantee escape + if battler.abilityActive? + if BattleHandlers.triggerRunFromBattleAbility(battler.ability,battler) + pbShowAbilitySplash(battler,true) + pbHideAbilitySplash(battler) + pbDisplayPaused(_INTL("You got away safely!")) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + end + # Held items that guarantee escape + if battler.itemActive? + if BattleHandlers.triggerRunFromBattleItem(battler.item,battler) + pbDisplayPaused(_INTL("{1} fled using its {2}!", + battler.pbThis,battler.itemName)) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + end + # Other certain trapping effects + if battler.effects[PBEffects::Trapping]>0 || + battler.effects[PBEffects::MeanLook]>=0 || + battler.effects[PBEffects::Ingrain] || + @field.effects[PBEffects::FairyLock]>0 + pbDisplayPaused(_INTL("You can't escape!")) + return 0 + end + # Trapping abilities/items + eachOtherSideBattler(idxBattler) do |b| + next if !b.abilityActive? + if BattleHandlers.triggerTrappingTargetAbility(b.ability,battler,b,self) + pbDisplayPaused(_INTL("{1} prevents escape with {2}!",b.pbThis,b.abilityName)) + return 0 + end + end + eachOtherSideBattler(idxBattler) do |b| + next if !b.itemActive? + if BattleHandlers.triggerTrappingTargetItem(b.item,battler,b,self) + pbDisplayPaused(_INTL("{1} prevents escape with {2}!",b.pbThis,b.itemName)) + return 0 + end + end + end + # Fleeing calculation + # Get the speeds of the Pokémon fleeing and the fastest opponent + # NOTE: Not pbSpeed, because using unmodified Speed. + @runCommand += 1 if !duringBattle # Make it easier to flee next time + speedPlayer = @battlers[idxBattler].speed + speedEnemy = 1 + eachOtherSideBattler(idxBattler) do |b| + speed = b.speed + speedEnemy = speed if speedEnemyspeedEnemy + rate = 256 + else + rate = (speedPlayer*128)/speedEnemy + rate += @runCommand*30 + end + if rate>=256 || @battleAI.pbAIRandom(256)=0 + return false if !pbHasMegaRing?(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + return @megaEvolution[side][owner]==-1 + end + + def pbRegisterMegaEvolution(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + @megaEvolution[side][owner] = idxBattler + end + + def pbUnregisterMegaEvolution(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + @megaEvolution[side][owner] = -1 if @megaEvolution[side][owner]==idxBattler + end + + def pbToggleRegisteredMegaEvolution(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + if @megaEvolution[side][owner]==idxBattler + @megaEvolution[side][owner] = -1 + else + @megaEvolution[side][owner] = idxBattler + end + end + + def pbRegisteredMegaEvolution?(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + return @megaEvolution[side][owner]==idxBattler + end + + #============================================================================= + # Mega Evolving a battler + #============================================================================= + def pbMegaEvolve(idxBattler) + battler = @battlers[idxBattler] + return if !battler || !battler.pokemon + return if !battler.hasMega? || battler.mega? + trainerName = pbGetOwnerName(idxBattler) + # Break Illusion + if battler.hasActiveAbility?(:ILLUSION) + BattleHandlers.triggerTargetAbilityOnHit(battler.ability,nil,battler,nil,self) + end + # Mega Evolve + case battler.pokemon.megaMessage + when 1 # Rayquaza + pbDisplay(_INTL("{1}'s fervent wish has reached {2}!",trainerName,battler.pbThis)) + else + pbDisplay(_INTL("{1}'s {2} is reacting to {3}'s {4}!", + battler.pbThis,battler.itemName,trainerName,pbGetMegaRingName(idxBattler))) + end + pbCommonAnimation("MegaEvolution",battler) + battler.pokemon.makeMega + battler.form = battler.pokemon.form + battler.pbUpdate(true) + @scene.pbChangePokemon(battler,battler.pokemon) + @scene.pbRefreshOne(idxBattler) + pbCommonAnimation("MegaEvolution2",battler) + megaName = battler.pokemon.megaName + if !megaName || megaName=="" + megaName = _INTL("Mega {1}",PBSpecies.getName(battler.pokemon.species)) + end + pbDisplay(_INTL("{1} has Mega Evolved into {2}!",battler.pbThis,megaName)) + side = battler.idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + @megaEvolution[side][owner] = -2 + if isConst?(battler.species,PBSpecies,:GENGAR) && battler.mega? + battler.effects[PBEffects::Telekinesis] = 0 + end + pbCalculatePriority(false,[idxBattler]) if NEWEST_BATTLE_MECHANICS + # Trigger ability + battler.pbEffectsOnSwitchIn + end + + #============================================================================= + # Primal Reverting a battler + #============================================================================= + def pbPrimalReversion(idxBattler) + battler = @battlers[idxBattler] + return if !battler || !battler.pokemon + return if !battler.hasPrimal? || battler.primal? + if isConst?(battler.pokemon.species,PBSpecies,:KYOGRE) + pbCommonAnimation("PrimalKyogre",battler) + elsif isConst?(battler.pokemon.species,PBSpecies,:GROUDON) + pbCommonAnimation("PrimalGroudon",battler) + end + battler.pokemon.makePrimal + battler.form = battler.pokemon.form + battler.pbUpdate(true) + @scene.pbChangePokemon(battler,battler.pokemon) + @scene.pbRefreshOne(idxBattler) + if isConst?(battler.pokemon.species,PBSpecies,:KYOGRE) + pbCommonAnimation("PrimalKyogre2",battler) + elsif isConst?(battler.pokemon.species,PBSpecies,:GROUDON) + pbCommonAnimation("PrimalGroudon2",battler) + end + pbDisplay(_INTL("{1}'s Primal Reversion!\nIt reverted to its primal form!",battler.pbThis)) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/010_Battle_Phase_Command.rb b/Data/Scripts/011_Battle/003_Battle/010_Battle_Phase_Command.rb new file mode 100644 index 000000000..cebeff5fa --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/010_Battle_Phase_Command.rb @@ -0,0 +1,250 @@ +class PokeBattle_Battle + #============================================================================= + # Clear commands + #============================================================================= + def pbClearChoice(idxBattler) + @choices[idxBattler] = [] if !@choices[idxBattler] + @choices[idxBattler][0] = :None + @choices[idxBattler][1] = 0 + @choices[idxBattler][2] = nil + @choices[idxBattler][3] = -1 + end + + def pbCancelChoice(idxBattler) + # If idxBattler's choice was to use an item, return that item to the Bag + if @choices[idxBattler][0]==:UseItem + item = @choices[idxBattler][1] + pbReturnUnusedItemToBag(item,idxBattler) if item && item>0 + end + # If idxBattler chose to Mega Evolve, cancel it + pbUnregisterMegaEvolution(idxBattler) + # Clear idxBattler's choice + pbClearChoice(idxBattler) + end + + #============================================================================= + # Use main command menu (Fight/Pokémon/Bag/Run) + #============================================================================= + def pbCommandMenu(idxBattler,firstAction) + return @scene.pbCommandMenu(idxBattler,firstAction) + end + + #============================================================================= + # Check whether actions can be taken + #============================================================================= + def pbCanShowCommands?(idxBattler) + battler = @battlers[idxBattler] + return false if !battler || battler.fainted? + return false if battler.usingMultiTurnAttack? + return true + end + + def pbCanShowFightMenu?(idxBattler) + battler = @battlers[idxBattler] + # Encore + return false if battler.effects[PBEffects::Encore]>0 + # No moves that can be chosen (will Struggle instead) + usable = false + battler.eachMoveWithIndex do |m,i| + next if !pbCanChooseMove?(idxBattler,i,false) + usable = true + break + end + return usable + end + + #============================================================================= + # Use sub-menus to choose an action, and register it if is allowed + #============================================================================= + # Returns true if a choice was made, false if cancelled. + def pbFightMenu(idxBattler) + # Auto-use Encored move or no moves choosable, so auto-use Struggle + return pbAutoChooseMove(idxBattler) if !pbCanShowFightMenu?(idxBattler) + # Battle Palace only + return true if pbAutoFightMenu(idxBattler) + # Regular move selection + ret = false + @scene.pbFightMenu(idxBattler,pbCanMegaEvolve?(idxBattler)) { |cmd| + case cmd + when -1 # Cancel + when -2 # Toggle Mega Evolution + pbToggleRegisteredMegaEvolution(idxBattler) + next false + when -3 # Shift + pbUnregisterMegaEvolution(idxBattler) + pbRegisterShift(idxBattler) + ret = true + else # Chose a move to use + next false if cmd<0 || !@battlers[idxBattler].moves[cmd] || + @battlers[idxBattler].moves[cmd].id<=0 + next false if !pbRegisterMove(idxBattler,cmd) + next false if !singleBattle? && + !pbChooseTarget(@battlers[idxBattler],@battlers[idxBattler].moves[cmd]) + ret = true + end + next true + } + return ret + end + + def pbAutoFightMenu(idxBattler); return false; end + + def pbChooseTarget(battler,move) + targetType = move.pbTarget(battler) + idxTarget = @scene.pbChooseTarget(battler.index,targetType) + return false if idxTarget<0 + pbRegisterTarget(battler.index,idxTarget) + return true + end + + def pbItemMenu(idxBattler,firstAction) + if !@internalBattle + pbDisplay(_INTL("Items can't be used here.")) + return false + end + ret = false + @scene.pbItemMenu(idxBattler,firstAction) { |item,useType,idxPkmn,idxMove,itemScene| + next false if item<0 + battler = pkmn = nil + case useType + when 1, 2, 6, 7 # Use on Pokémon/Pokémon's move + next false if !ItemHandlers.hasBattleUseOnPokemon(item) + battler = pbFindBattler(idxPkmn,idxBattler) + pkmn = pbParty(idxBattler)[idxPkmn] + next false if !pbCanUseItemOnPokemon?(item,pkmn,battler,itemScene) + when 3, 8 # Use on battler + next false if !ItemHandlers.hasBattleUseOnBattler(item) + battler = pbFindBattler(idxPkmn,idxBattler) + pkmn = battler.pokemon if battler + next false if !pbCanUseItemOnPokemon?(item,pkmn,battler,itemScene) + when 4, 9 # Poké Balls + next false if idxPkmn<0 + battler = @battlers[idxPkmn] + pkmn = battler.pokemon if battler + when 5, 10 # No target (Poké Doll, Guard Spec., Launcher items) + battler = @battlers[idxBattler] + pkmn = battler.pokemon if battler + else + next false + end + next false if !pkmn + next false if !ItemHandlers.triggerCanUseInBattle(item, + pkmn,battler,idxMove,firstAction,self,itemScene) + next false if !pbRegisterItem(idxBattler,item,idxPkmn,idxMove) + ret = true + next true + } + return ret + end + + def pbPartyMenu(idxBattler) + ret = -1 + if @debug + ret = @battleAI.pbDefaultChooseNewEnemy(idxBattler,pbParty(idxBattler)) + else + ret = pbPartyScreen(idxBattler,false,true,true) + end + return ret>=0 + end + + def pbRunMenu(idxBattler) + # Regardless of succeeding or failing to run, stop choosing actions + return pbRun(idxBattler)!=0 + end + + def pbCallMenu(idxBattler) + return pbRegisterCall(idxBattler) + end + + def pbDebugMenu + # NOTE: This doesn't do anything yet. Maybe you can write your own debugging + # options! + end + + #============================================================================= + # Command phase + #============================================================================= + def pbCommandPhase + @scene.pbBeginCommandPhase + # Reset choices if commands can be shown + @battlers.each_with_index do |b,i| + next if !b + pbClearChoice(i) if pbCanShowCommands?(i) + end + # Reset choices to perform Mega Evolution if it wasn't done somehow + for side in 0...2 + @megaEvolution[side].each_with_index do |megaEvo,i| + @megaEvolution[side][i] = -1 if megaEvo>=0 + end + end + # Choose actions for the round (player first, then AI) + pbCommandPhaseLoop(true) # Player chooses their actions + return if @decision!=0 # Battle ended, stop choosing actions + pbCommandPhaseLoop(false) # AI chooses their actions + end + + def pbCommandPhaseLoop(isPlayer) + # NOTE: Doing some things (e.g. running, throwing a Poké Ball) takes up all + # your actions in a round. + actioned = [] + idxBattler = -1 + loop do + break if @decision!=0 # Battle ended, stop choosing actions + idxBattler += 1 + break if idxBattler>=@battlers.length + next if !@battlers[idxBattler] || pbOwnedByPlayer?(idxBattler)!=isPlayer + next if @choices[idxBattler][0]!=:None # Action is forced, can't choose one + next if !pbCanShowCommands?(idxBattler) # Action is forced, can't choose one + # AI controls this battler + if @controlPlayer || !pbOwnedByPlayer?(idxBattler) + @battleAI.pbDefaultChooseEnemyCommand(idxBattler) + next + end + # Player chooses an action + actioned.push(idxBattler) + commandsEnd = false # Whether to cancel choosing all other actions this round + loop do + cmd = pbCommandMenu(idxBattler,actioned.length==1) + # If being Sky Dropped, can't do anything except use a move + if cmd>0 && @battlers[idxBattler].effects[PBEffects::SkyDrop]>=0 + pbDisplay(_INTL("Sky Drop won't let {1} go!",@battlers[idxBattler].pbThis(true))) + next + end + case cmd + when 0 # Fight + break if pbFightMenu(idxBattler) + when 1 # Bag + if pbItemMenu(idxBattler,actioned.length==1) + commandsEnd = true if pbItemUsesAllActions?(@choices[idxBattler][1]) + break + end + when 2 # Pokémon + break if pbPartyMenu(idxBattler) + when 3 # Run + # NOTE: "Run" is only an available option for the first battler the + # player chooses an action for in a round. Attempting to run + # from battle prevents you from choosing any other actions in + # that round. + if pbRunMenu(idxBattler) + commandsEnd = true + break + end + when 4 # Call + break if pbCallMenu(idxBattler) + when -2 # Debug + pbDebugMenu + next + when -1 # Go back to previous battler's action choice + next if actioned.length<=1 + actioned.pop # Forget this battler was done + idxBattler = actioned.last-1 + pbCancelChoice(idxBattler+1) # Clear the previous battler's choice + actioned.pop # Forget the previous battler was done + break + end + pbCancelChoice(idxBattler) + end + break if commandsEnd + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/011_Battle_Phase_Attack.rb b/Data/Scripts/011_Battle/003_Battle/011_Battle_Phase_Attack.rb new file mode 100644 index 000000000..d1b7e89fd --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/011_Battle_Phase_Attack.rb @@ -0,0 +1,190 @@ +class PokeBattle_Battle + #============================================================================= + # Attack phase actions + #============================================================================= + # Quick Claw, Custap Berry's "X let it move first!" message. + def pbAttackPhasePriorityChangeMessages + pbPriority.each do |b| + if b.effects[PBEffects::PriorityAbility] && b.abilityActive? + BattleHandlers.triggerPriorityBracketUseAbility(b.ability,b,self) + elsif b.effects[PBEffects::PriorityItem] && b.itemActive? + BattleHandlers.triggerPriorityBracketUseItem(b.item,b,self) + end + end + end + + def pbAttackPhaseCall + pbPriority.each do |b| + next unless @choices[b.index][0]==:Call && !b.fainted? + b.lastMoveFailed = false # Counts as a successful move for Stomping Tantrum + pbCall(b.index) + end + end + + def pbPursuit(idxSwitcher) + @switching = true + pbPriority.each do |b| + next if b.fainted? || !b.opposes?(idxSwitcher) # Shouldn't hit an ally + next if b.movedThisRound? || !pbChoseMoveFunctionCode?(b.index,"088") # Pursuit + # Check whether Pursuit can be used + next unless pbMoveCanTarget?(b.index,idxSwitcher,@choices[b.index][2].target) + next unless pbCanChooseMove?(b.index,@choices[b.index][1],false) + next if b.status==PBStatuses::SLEEP || b.status==PBStatuses::FROZEN + next if b.effects[PBEffects::SkyDrop]>=0 + next if b.hasActiveAbility?(:TRUANT) && b.effects[PBEffects::Truant] + # Mega Evolve + if !wildBattle? || !b.opposes? + owner = pbGetOwnerIndexFromBattlerIndex(b.index) + pbMegaEvolve(b.index) if @megaEvolution[b.idxOwnSide][owner]==b.index + end + # Use Pursuit + @choices[b.index][3] = idxSwitcher # Change Pursuit's target + if b.pbProcessTurn(@choices[b.index],false) + b.effects[PBEffects::Pursuit] = true + end + break if @decision>0 || @battlers[idxSwitcher].fainted? + end + @switching = false + end + + def pbAttackPhaseSwitch + pbPriority.each do |b| + next unless @choices[b.index][0]==:SwitchOut && !b.fainted? + idxNewPkmn = @choices[b.index][1] # Party index of Pokémon to switch to + b.lastMoveFailed = false # Counts as a successful move for Stomping Tantrum + @lastMoveUser = b.index + # Switching message + pbMessageOnRecall(b) + # Pursuit interrupts switching + pbPursuit(b.index) + return if @decision>0 + # Switch Pokémon + pbRecallAndReplace(b.index,idxNewPkmn) + b.pbEffectsOnSwitchIn(true) + end + end + + def pbAttackPhaseItems + pbPriority.each do |b| + next unless @choices[b.index][0]==:UseItem && !b.fainted? + b.lastMoveFailed = false # Counts as a successful move for Stomping Tantrum + item = @choices[b.index][1] + next if !item || item<=0 + useType = pbGetItemData(item,ITEM_BATTLE_USE) + next if !useType + case useType + when 1, 2, 6, 7 # Use on Pokémon/Pokémon's move + pbUseItemOnPokemon(item,@choices[b.index][2],b) if @choices[b.index][2]>=0 + when 3, 8 # Use on battler + pbUseItemOnBattler(item,@choices[b.index][2],b) + when 4, 9 # Use Poké Ball + pbUsePokeBallInBattle(item,@choices[b.index][2],b) + when 5, 10 # Use directly + pbUseItemInBattle(item,@choices[b.index][2],b) + end + return if @decision>0 + end +# pbCalculatePriority if NEWEST_BATTLE_MECHANICS + end + + def pbAttackPhaseMegaEvolution + pbPriority.each do |b| + next if wildBattle? && b.opposes? + next unless @choices[b.index][0]==:UseMove && !b.fainted? + owner = pbGetOwnerIndexFromBattlerIndex(b.index) + next if @megaEvolution[b.idxOwnSide][owner]!=b.index + pbMegaEvolve(b.index) + end + end + + def pbAttackPhaseMoves + # Show charging messages (Focus Punch) + pbPriority.each do |b| + next unless @choices[b.index][0]==:UseMove && !b.fainted? + next if b.movedThisRound? + @choices[b.index][2].pbDisplayChargeMessage(b) + end + # Main move processing loop + loop do + priority = pbPriority + # Forced to go next + advance = false + priority.each do |b| + next unless b.effects[PBEffects::MoveNext] && !b.fainted? + next unless @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + next if b.movedThisRound? + advance = b.pbProcessTurn(@choices[b.index]) + break if advance + end + return if @decision>0 + next if advance + # Regular priority order + priority.each do |b| + next if b.effects[PBEffects::Quash]>0 || b.fainted? + next unless @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + next if b.movedThisRound? + advance = b.pbProcessTurn(@choices[b.index]) + break if advance + end + return if @decision>0 + next if advance + # Quashed + quashLevel = 0 + loop do + quashLevel += 1 + moreQuash = false + priority.each do |b| + moreQuash = true if b.effects[PBEffects::Quash]>quashLevel + next unless b.effects[PBEffects::Quash]==quashLevel && !b.fainted? + next unless @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + next if b.movedThisRound? + advance = b.pbProcessTurn(@choices[b.index]) + break + end + break if advance || !moreQuash + end + return if @decision>0 + next if advance + # Check for all done + priority.each do |b| + if !b.fainted? && !b.movedThisRound? + advance = true if @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + end + break if advance + end + next if advance + # All Pokémon have moved; end the loop + break + end + end + + #============================================================================= + # Attack phase + #============================================================================= + def pbAttackPhase + @scene.pbBeginAttackPhase + # Reset certain effects + @battlers.each_with_index do |b,i| + next if !b + b.turnCount += 1 if !b.fainted? + @successStates[i].clear + if @choices[i][0]!=:UseMove && @choices[i][0]!=:Shift && @choices[i][0]!=:SwitchOut + b.effects[PBEffects::DestinyBond] = false + b.effects[PBEffects::Grudge] = false + end + b.effects[PBEffects::Rage] = false if !pbChoseMoveFunctionCode?(i,"093") # Rage + end + PBDebug.log("") + # Calculate move order for this round + pbCalculatePriority(true) + # Perform actions + pbAttackPhasePriorityChangeMessages + pbAttackPhaseCall + pbAttackPhaseSwitch + return if @decision>0 + pbAttackPhaseItems + return if @decision>0 + pbAttackPhaseMegaEvolution + pbAttackPhaseMoves + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/012_Battle_Phase_EndOfRound.rb b/Data/Scripts/011_Battle/003_Battle/012_Battle_Phase_EndOfRound.rb new file mode 100644 index 000000000..04fb7e061 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/012_Battle_Phase_EndOfRound.rb @@ -0,0 +1,667 @@ +class PokeBattle_Battle + #============================================================================= + # Decrement effect counters + #============================================================================= + def pbEORCountDownBattlerEffect(priority,effect) + priority.each do |b| + next if b.fainted? || b.effects[effect]==0 + b.effects[effect] -= 1 + yield b if block_given? && b.effects[effect]==0 + end + end + + def pbEORCountDownSideEffect(side,effect,msg) + if @sides[side].effects[effect]>0 + @sides[side].effects[effect] -= 1 + pbDisplay(msg) if @sides[side].effects[effect]==0 + end + end + + def pbEORCountDownFieldEffect(effect,msg) + if @field.effects[effect]>0 + @field.effects[effect] -= 1 + if @field.effects[effect]==0 + pbDisplay(msg) + if effect==PBEffects::MagicRoom + pbPriority(true).each { |b| b.pbItemTerrainStatBoostCheck } + end + end + end + end + + #============================================================================= + # End Of Round weather + #============================================================================= + def pbEORWeather(priority) + # NOTE: Primordial weather doesn't need to be checked here, because if it + # could wear off here, it will have worn off already. + # Count down weather duration + @field.weatherDuration -= 1 if @field.weatherDuration>0 + # Weather wears off + if @field.weatherDuration==0 + case @field.weather + when PBWeather::Sun + pbDisplay(_INTL("The sunlight faded.")) + when PBWeather::Rain + pbDisplay(_INTL("The rain stopped.")) + when PBWeather::Sandstorm + pbDisplay(_INTL("The sandstorm subsided.")) + when PBWeather::Hail + pbDisplay(_INTL("The hail stopped.")) + when PBWeather::ShadowSky + pbDisplay(_INTL("The shadow sky faded.")) + end + @field.weather = PBWeather::None + # Check for form changes caused by the weather changing + eachBattler { |b| b.pbCheckFormOnWeatherChange } + # Start up the default weather + pbStartWeather(nil,@field.defaultWeather) if @field.defaultWeather!=PBWeather::None + return if @field.weather==PBWeather::None + end + # Weather continues + pbCommonAnimation(PBWeather.animationName(@field.weather)) + case @field.weather +# when PBWeather::Sun; pbDisplay(_INTL("The sunlight is strong.")) +# when PBWeather::Rain; pbDisplay(_INTL("Rain continues to fall.")) + when PBWeather::Sandstorm; pbDisplay(_INTL("The sandstorm is raging.")) + when PBWeather::Hail; pbDisplay(_INTL("The hail is crashing down.")) +# when PBWeather::HarshSun; pbDisplay(_INTL("The sunlight is extremely harsh.")) +# when PBWeather::HeavyRain; pbDisplay(_INTL("It is raining heavily.")) +# when PBWeather::StrongWinds; pbDisplay(_INTL("The wind is strong.")) + when PBWeather::ShadowSky; pbDisplay(_INTL("The shadow sky continues.")); + end + # Effects due to weather + curWeather = pbWeather + priority.each do |b| + # Weather-related abilities + if b.abilityActive? + BattleHandlers.triggerEORWeatherAbility(b.ability,curWeather,b,self) + b.pbFaint if b.fainted? + end + # Weather damage + # NOTE: + case curWeather + when PBWeather::Sandstorm + next if !b.takesSandstormDamage? + pbDisplay(_INTL("{1} is buffeted by the sandstorm!",b.pbThis)) + @scene.pbDamageAnimation(b) + b.pbReduceHP(b.totalhp/16,false) + b.pbItemHPHealCheck + b.pbFaint if b.fainted? + when PBWeather::Hail + next if !b.takesHailDamage? + pbDisplay(_INTL("{1} is buffeted by the hail!",b.pbThis)) + @scene.pbDamageAnimation(b) + b.pbReduceHP(b.totalhp/16,false) + b.pbItemHPHealCheck + b.pbFaint if b.fainted? + when PBWeather::ShadowSky + next if !b.takesShadowSkyDamage? + pbDisplay(_INTL("{1} is hurt by the shadow sky!",b.pbThis)) + @scene.pbDamageAnimation(b) + b.pbReduceHP(b.totalhp/16,false) + b.pbItemHPHealCheck + b.pbFaint if b.fainted? + end + end + end + + #============================================================================= + # End Of Round terrain + #============================================================================= + def pbEORTerrain + # Count down terrain duration + @field.terrainDuration -= 1 if @field.terrainDuration>0 + # Terrain wears off + if @field.terrain!=PBBattleTerrains::None && @field.terrainDuration==0 + case @field.terrain + when PBBattleTerrains::Electric + pbDisplay(_INTL("The electric current disappeared from the battlefield!")) + when PBBattleTerrains::Grassy + pbDisplay(_INTL("The grass disappeared from the battlefield!")) + when PBBattleTerrains::Misty + pbDisplay(_INTL("The mist disappeared from the battlefield!")) + when PBBattleTerrains::Psychic + pbDisplay(_INTL("The weirdness disappeared from the battlefield!")) + end + @field.terrain = PBBattleTerrains::None + # Start up the default terrain + pbStartTerrain(nil,@field.defaultTerrain,false) if @field.defaultTerrain!=PBBattleTerrains::None + return if @field.terrain==PBBattleTerrains::None + end + # Terrain continues + pbCommonAnimation(PBBattleTerrains.animationName(@field.terrain)) + case @field.terrain + when PBBattleTerrains::Electric; pbDisplay(_INTL("An electric current is running across the battlefield.")) + when PBBattleTerrains::Grassy; pbDisplay(_INTL("Grass is covering the battlefield.")) + when PBBattleTerrains::Misty; pbDisplay(_INTL("Mist is swirling about the battlefield.")) + when PBBattleTerrains::Psychic; pbDisplay(_INTL("The battlefield is weird.")) + end + end + + #============================================================================= + # End Of Round shift distant battlers to middle positions + #============================================================================= + def pbEORShiftDistantBattlers + # Move battlers around if none are near to each other + # NOTE: This code assumes each side has a maximum of 3 battlers on it, and + # is not generalised to larger side sizes. + if !singleBattle? + swaps = [] # Each element is an array of two battler indices to swap + for side in 0...2 + next if pbSideSize(side)==1 # Only battlers on sides of size 2+ need to move + # Check if any battler on this side is near any battler on the other side + anyNear = false + eachSameSideBattler(side) do |b| + eachOtherSideBattler(b) do |otherB| + next if !nearBattlers?(otherB.index,b.index) + anyNear = true + break + end + break if anyNear + end + break if anyNear + # No battlers on this side are near any battlers on the other side; try + # to move them + # NOTE: If we get to here (assuming both sides are of size 3 or less), + # there is definitely only 1 able battler on this side, so we + # don't need to worry about multiple battlers trying to move into + # the same position. If you add support for a side of size 4+, + # this code will need revising to account for that, as well as to + # add more complex code to ensure battlers will end up near each + # other. + eachSameSideBattler(side) do |b| + # Get the position to move to + pos = -1 + case pbSideSize(side) + when 2; pos = [2,3,0,1][b.index] # The unoccupied position + when 3; pos = (side==0) ? 2 : 3 # The centre position + end + next if pos<0 + # Can't move if the same trainer doesn't control both positions + idxOwner = pbGetOwnerIndexFromBattlerIndex(b.index) + next if pbGetOwnerIndexFromBattlerIndex(pos)!=idxOwner + swaps.push([b.index,pos]) + end + end + # Move battlers around + swaps.each do |pair| + next if pbSideSize(pair[0])==2 && swaps.length>1 + next if !pbSwapBattlers(pair[0],pair[1]) + case pbSideSize(side) + when 2 + pbDisplay(_INTL("{1} moved across!",@battlers[pair[1]].pbThis)) + when 3 + pbDisplay(_INTL("{1} moved to the center!",@battlers[pair[1]].pbThis)) + end + end + end + end + + #============================================================================= + # End Of Round phase + #============================================================================= + def pbEndOfRoundPhase + PBDebug.log("") + PBDebug.log("[End of round]") + @endOfRound = true + @scene.pbBeginEndOfRoundPhase + pbCalculatePriority # recalculate speeds + priority = pbPriority(true) # in order of fastest -> slowest speeds only + # Weather + pbEORWeather(priority) + # Future Sight/Doom Desire + @positions.each_with_index do |pos,idxPos| + next if !pos || pos.effects[PBEffects::FutureSightCounter]==0 + pos.effects[PBEffects::FutureSightCounter] -= 1 + next if pos.effects[PBEffects::FutureSightCounter]>0 + next if !@battlers[idxPos] || @battlers[idxPos].fainted? # No target + moveUser = nil + eachBattler do |b| + next if b.opposes?(pos.effects[PBEffects::FutureSightUserIndex]) + next if b.pokemonIndex!=pos.effects[PBEffects::FutureSightUserPartyIndex] + moveUser = b + break + end + next if moveUser && moveUser.index==idxPos # Target is the user + if !moveUser # User isn't in battle, get it from the party + party = pbParty(pos.effects[PBEffects::FutureSightUserIndex]) + pkmn = party[pos.effects[PBEffects::FutureSightUserPartyIndex]] + if pkmn && pkmn.able? + moveUser = PokeBattle_Battler.new(self,pos.effects[PBEffects::FutureSightUserIndex]) + moveUser.pbInitDummyPokemon(pkmn,pos.effects[PBEffects::FutureSightUserPartyIndex]) + end + end + next if !moveUser # User is fainted + move = pos.effects[PBEffects::FutureSightMove] + pbDisplay(_INTL("{1} took the {2} attack!",@battlers[idxPos].pbThis,PBMoves.getName(move))) + # NOTE: Future Sight failing against the target here doesn't count towards + # Stomping Tantrum. + userLastMoveFailed = moveUser.lastMoveFailed + @futureSight = true + moveUser.pbUseMoveSimple(move,idxPos) + @futureSight = false + moveUser.lastMoveFailed = userLastMoveFailed + @battlers[idxPos].pbFaint if @battlers[idxPos].fainted? + pos.effects[PBEffects::FutureSightCounter] = 0 + pos.effects[PBEffects::FutureSightMove] = 0 + pos.effects[PBEffects::FutureSightUserIndex] = -1 + pos.effects[PBEffects::FutureSightUserPartyIndex] = -1 + end + # Wish + @positions.each_with_index do |pos,idxPos| + next if !pos || pos.effects[PBEffects::Wish]==0 + pos.effects[PBEffects::Wish] -= 1 + next if pos.effects[PBEffects::Wish]>0 + next if !@battlers[idxPos] || !@battlers[idxPos].canHeal? + wishMaker = pbThisEx(idxPos,pos.effects[PBEffects::WishMaker]) + @battlers[idxPos].pbRecoverHP(pos.effects[PBEffects::WishAmount]) + pbDisplay(_INTL("{1}'s wish came true!",wishMaker)) + end + # Sea of Fire damage (Fire Pledge + Grass Pledge combination) + curWeather = pbWeather + for side in 0...2 + next if sides[side].effects[PBEffects::SeaOfFire]==0 + next if curWeather==PBWeather::Rain || curWeather==PBWeather::HeavyRain + @battle.pbCommonAnimation("SeaOfFire") if side==0 + @battle.pbCommonAnimation("SeaOfFireOpp") if side==1 + priority.each do |b| + next if b.opposes?(side) + next if !b.takesIndirectDamage? || b.pbHasType?(:FIRE) + oldHP = b.hp + @scene.pbDamageAnimation(b) + b.pbReduceHP(b.totalhp/8,false) + pbDisplay(_INTL("{1} is hurt by the sea of fire!",b.pbThis)) + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + end + # Status-curing effects/abilities and HP-healing items + priority.each do |b| + next if b.fainted? + # Grassy Terrain (healing) + if @field.terrain==PBBattleTerrains::Grassy && b.affectedByTerrain? && b.canHeal? + PBDebug.log("[Lingering effect] Grassy Terrain heals #{b.pbThis(true)}") + b.pbRecoverHP(b.totalhp/16) + pbDisplay(_INTL("{1}'s HP was restored.",b.pbThis)) + end + # Healer, Hydration, Shed Skin + BattleHandlers.triggerEORHealingAbility(b.ability,b,self) if b.abilityActive? + # Black Sludge, Leftovers + BattleHandlers.triggerEORHealingItem(b.item,b,self) if b.itemActive? + end + # Aqua Ring + priority.each do |b| + next if !b.effects[PBEffects::AquaRing] + next if !b.canHeal? + hpGain = b.totalhp/16 + hpGain = (hpGain*1.3).floor if b.hasActiveItem?(:BIGROOT) + hpGain = b.pbRecoverHP(hpGain) + pbDisplay(_INTL("Aqua Ring restored {1}'s HP!",b.pbThis(true))) + end + # Ingrain + priority.each do |b| + next if !b.effects[PBEffects::Ingrain] + next if !b.canHeal? + hpGain = b.totalhp/16 + hpGain = (hpGain*1.3).floor if b.hasActiveItem?(:BIGROOT) + hpGain = b.pbRecoverHP(hpGain) + pbDisplay(_INTL("{1} absorbed nutrients with its roots!",b.pbThis)) + end + # Leech Seed + priority.each do |b| + next if b.effects[PBEffects::LeechSeed]<0 + next if !b.takesIndirectDamage? + recipient = @battlers[b.effects[PBEffects::LeechSeed]] + next if !recipient || recipient.fainted? + oldHP = b.hp + oldHPRecipient = recipient.hp + pbCommonAnimation("LeechSeed",recipient,b) + hpLoss = b.pbReduceHP(b.totalhp/8) + recipient.pbRecoverHPFromDrain(hpLoss,b, + _INTL("{1}'s health is sapped by Leech Seed!",b.pbThis)) + recipient.pbAbilitiesOnDamageTaken(oldHPRecipient) if recipient.hp0 + b.effects[PBEffects::Toxic] += 1 + b.effects[PBEffects::Toxic] = 15 if b.effects[PBEffects::Toxic]>15 + end + if b.hasActiveAbility?(:POISONHEAL) + if b.canHeal? + pbCommonAnimation("Poison",b) + pbShowAbilitySplash(b) + b.pbRecoverHP(b.totalhp/8) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + pbDisplay(_INTL("{1}'s HP was restored.",b.pbThis)) + else + pbDisplay(_INTL("{1}'s {2} restored its HP.",b.pbThis,b.abilityName)) + end + pbHideAbilitySplash(b) + end + elsif b.takesIndirectDamage? + oldHP = b.hp + dmg = (b.statusCount==0) ? b.totalhp/8 : b.totalhp*b.effects[PBEffects::Toxic]/16 + b.pbContinueStatus { b.pbReduceHP(dmg,false) } + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + end + # Damage from burn + priority.each do |b| + next if b.status!=PBStatuses::BURN || !b.takesIndirectDamage? + oldHP = b.hp + dmg = (NEWEST_BATTLE_MECHANICS) ? b.totalhp/16 : b.totalhp/8 + dmg = (dmg/2.0).round if b.hasActiveAbility?(:HEATPROOF) + b.pbContinueStatus { b.pbReduceHP(dmg,false) } + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + # Damage from sleep (Nightmare) + priority.each do |b| + b.effects[PBEffects::Nightmare] = false if !b.asleep? + next if !b.effects[PBEffects::Nightmare] || !b.takesIndirectDamage? + oldHP = b.hp + b.pbReduceHP(b.totalhp/4) + pbDisplay(_INTL("{1} is locked in a nightmare!",b.pbThis)) + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + # Curse + priority.each do |b| + next if !b.effects[PBEffects::Curse] || !b.takesIndirectDamage? + oldHP = b.hp + b.pbReduceHP(b.totalhp/4) + pbDisplay(_INTL("{1} is afflicted by the curse!",b.pbThis)) + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + # Trapping attacks (Bind/Clamp/Fire Spin/Magma Storm/Sand Tomb/Whirlpool/Wrap) + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::Trapping]==0 + b.effects[PBEffects::Trapping] -= 1 + moveName = PBMoves.getName(b.effects[PBEffects::TrappingMove]) + if b.effects[PBEffects::Trapping]==0 + pbDisplay(_INTL("{1} was freed from {2}!",b.pbThis,moveName)) + else + trappingMove = b.effects[PBEffects::TrappingMove] + if isConst?(trappingMove,PBMoves,:BIND); pbCommonAnimation("Bind",b) + elsif isConst?(trappingMove,PBMoves,:CLAMP); pbCommonAnimation("Clamp",b) + elsif isConst?(trappingMove,PBMoves,:FIRESPIN); pbCommonAnimation("FireSpin",b) + elsif isConst?(trappingMove,PBMoves,:MAGMASTORM); pbCommonAnimation("MagmaStorm",b) + elsif isConst?(trappingMove,PBMoves,:SANDTOMB); pbCommonAnimation("SandTomb",b) + elsif isConst?(trappingMove,PBMoves,:WRAP); pbCommonAnimation("Wrap",b) + elsif isConst?(trappingMove,PBMoves,:INFESTATION); pbCommonAnimation("Infestation",b) + else; pbCommonAnimation("Wrap",b) + end + if b.takesIndirectDamage? + hpLoss = (NEWEST_BATTLE_MECHANICS) ? b.totalhp/8 : b.totalhp/16 + if @battlers[b.effects[PBEffects::TrappingUser]].hasActiveItem?(:BINDINGBAND) + hpLoss = (NEWEST_BATTLE_MECHANICS) ? b.totalhp/6 : b.totalhp/8 + end + @scene.pbDamageAnimation(b) + b.pbReduceHP(hpLoss,false) + pbDisplay(_INTL("{1} is hurt by {2}!",b.pbThis,moveName)) + b.pbItemHPHealCheck + # NOTE: No need to call pbAbilitiesOnDamageTaken as b can't switch out. + b.pbFaint if b.fainted? + end + end + end + # Taunt + pbEORCountDownBattlerEffect(priority,PBEffects::Taunt) { |battler| + pbDisplay(_INTL("{1}'s taunt wore off!",battler.pbThis)) + } + # Encore + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::Encore]==0 + idxEncoreMove = b.pbEncoredMoveIndex + if idxEncoreMove>=0 + b.effects[PBEffects::Encore] -= 1 + if b.effects[PBEffects::Encore]==0 || b.moves[idxEncoreMove].pp==0 + b.effects[PBEffects::Encore] = 0 + pbDisplay(_INTL("{1}'s encore ended!",b.pbThis)) + end + else + PBDebug.log("[End of effect] #{b.pbThis}'s encore ended (encored move no longer known)") + b.effects[PBEffects::Encore] = 0 + b.effects[PBEffects::EncoreMove] = 0 + end + end + # Disable/Cursed Body + pbEORCountDownBattlerEffect(priority,PBEffects::Disable) { |battler| + battler.effects[PBEffects::DisableMove] = 0 + pbDisplay(_INTL("{1} is no longer disabled!",battler.pbThis)) + } + # Magnet Rise + pbEORCountDownBattlerEffect(priority,PBEffects::MagnetRise) { |battler| + pbDisplay(_INTL("{1}'s electromagnetism wore off!",battler.pbThis)) + } + # Telekinesis + pbEORCountDownBattlerEffect(priority,PBEffects::Telekinesis) { |battler| + pbDisplay(_INTL("{1} was freed from the telekinesis!",battler.pbThis)) + } + # Heal Block + pbEORCountDownBattlerEffect(priority,PBEffects::HealBlock) { |battler| + pbDisplay(_INTL("{1}'s Heal Block wore off!",battler.pbThis)) + } + # Embargo + pbEORCountDownBattlerEffect(priority,PBEffects::Embargo) { |battler| + pbDisplay(_INTL("{1} can use items again!",battler.pbThis)) + battler.pbItemTerrainStatBoostCheck + } + # Yawn + pbEORCountDownBattlerEffect(priority,PBEffects::Yawn) { |battler| + if battler.pbCanSleepYawn? + PBDebug.log("[Lingering effect] #{battler.pbThis} fell asleep because of Yawn") + battler.pbSleep + end + } + # Perish Song + perishSongUsers = [] + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::PerishSong]==0 + b.effects[PBEffects::PerishSong] -= 1 + pbDisplay(_INTL("{1}'s perish count fell to {2}!",b.pbThis,b.effects[PBEffects::PerishSong])) + if b.effects[PBEffects::PerishSong]==0 + perishSongUsers.push(b.effects[PBEffects::PerishSongUser]) + b.pbReduceHP(b.hp) + end + b.pbItemHPHealCheck + b.pbFaint if b.fainted? + end + if perishSongUsers.length>0 + # If all remaining Pokemon fainted by a Perish Song triggered by a single side + if (perishSongUsers.find_all { |idxBattler| opposes?(idxBattler) }.length==perishSongUsers.length) || + (perishSongUsers.find_all { |idxBattler| !opposes?(idxBattler) }.length==perishSongUsers.length) + pbJudgeCheckpoint(@battlers[perishSongUsers[0]]) + end + end + # Check for end of battle + if @decision>0 + pbGainExp + return + end + for side in 0...2 + # Reflect + pbEORCountDownSideEffect(side,PBEffects::Reflect, + _INTL("{1}'s Reflect wore off!",@battlers[side].pbTeam)) + # Light Screen + pbEORCountDownSideEffect(side,PBEffects::LightScreen, + _INTL("{1}'s Light Screen wore off!",@battlers[side].pbTeam)) + # Safeguard + pbEORCountDownSideEffect(side,PBEffects::Safeguard, + _INTL("{1} is no longer protected by Safeguard!",@battlers[side].pbTeam)) + # Mist + pbEORCountDownSideEffect(side,PBEffects::Mist, + _INTL("{1} is no longer protected by mist!",@battlers[side].pbTeam)) + # Tailwind + pbEORCountDownSideEffect(side,PBEffects::Tailwind, + _INTL("{1}'s Tailwind petered out!",@battlers[side].pbTeam)) + # Lucky Chant + pbEORCountDownSideEffect(side,PBEffects::LuckyChant, + _INTL("{1}'s Lucky Chant wore off!",@battlers[side].pbTeam)) + # Pledge Rainbow + pbEORCountDownSideEffect(side,PBEffects::Rainbow, + _INTL("The rainbow on {1}'s side disappeared!",@battlers[side].pbTeam(true))) + # Pledge Sea of Fire + pbEORCountDownSideEffect(side,PBEffects::SeaOfFire, + _INTL("The sea of fire around {1} disappeared!",@battlers[side].pbTeam(true))) + # Pledge Swamp + pbEORCountDownSideEffect(side,PBEffects::Swamp, + _INTL("The swamp around {1} disappeared!",@battlers[side].pbTeam(true))) + # Aurora Veil + pbEORCountDownSideEffect(side,PBEffects::AuroraVeil, + _INTL("{1}'s Aurora Veil wore off!",@battlers[side].pbTeam(true))) + end + # Trick Room + pbEORCountDownFieldEffect(PBEffects::TrickRoom, + _INTL("The twisted dimensions returned to normal!")) + # Gravity + pbEORCountDownFieldEffect(PBEffects::Gravity, + _INTL("Gravity returned to normal!")) + # Water Sport + pbEORCountDownFieldEffect(PBEffects::WaterSportField, + _INTL("The effects of Water Sport have faded.")) + # Mud Sport + pbEORCountDownFieldEffect(PBEffects::MudSportField, + _INTL("The effects of Mud Sport have faded.")) + # Wonder Room + pbEORCountDownFieldEffect(PBEffects::WonderRoom, + _INTL("Wonder Room wore off, and Defense and Sp. Def stats returned to normal!")) + # Magic Room + pbEORCountDownFieldEffect(PBEffects::MagicRoom, + _INTL("Magic Room wore off, and held items' effects returned to normal!")) + # End of terrains + pbEORTerrain + priority.each do |b| + next if b.fainted? + # Hyper Mode (Shadow Pokémon) + if b.inHyperMode? + if pbRandom(100)<10 + b.pokemon.hypermode = false + b.pokemon.adjustHeart(-50) + pbDisplay(_INTL("{1} came to its senses!",b.pbThis)) + else + pbDisplay(_INTL("{1} is in Hyper Mode!",b.pbThis)) + end + end + # Uproar + if b.effects[PBEffects::Uproar]>0 + b.effects[PBEffects::Uproar] -= 1 + if b.effects[PBEffects::Uproar]==0 + pbDisplay(_INTL("{1} calmed down.",b.pbThis)) + else + pbDisplay(_INTL("{1} is making an uproar!",b.pbThis)) + end + end + # Slow Start's end message + if b.effects[PBEffects::SlowStart]>0 + b.effects[PBEffects::SlowStart] -= 1 + if b.effects[PBEffects::SlowStart]==0 + pbDisplay(_INTL("{1} finally got its act together!",b.pbThis)) + end + end + # Bad Dreams, Moody, Speed Boost + BattleHandlers.triggerEOREffectAbility(b.ability,b,self) if b.abilityActive? + # Flame Orb, Sticky Barb, Toxic Orb + BattleHandlers.triggerEOREffectItem(b.item,b,self) if b.itemActive? + # Harvest, Pickup + BattleHandlers.triggerEORGainItemAbility(b.ability,b,self) if b.abilityActive? + end + pbGainExp + return if @decision>0 + # Form checks + priority.each { |b| b.pbCheckForm(true) } + # Switch Pokémon in if possible + pbEORSwitch + return if @decision>0 + # In battles with at least one side of size 3+, move battlers around if none + # are near to any foes + pbEORShiftDistantBattlers + # Try to make Trace work, check for end of primordial weather + priority.each { |b| b.pbContinualAbilityChecks } + # Reset/count down battler-specific effects (no messages) + eachBattler do |b| + b.effects[PBEffects::BanefulBunker] = false + b.effects[PBEffects::Charge] -= 1 if b.effects[PBEffects::Charge]>0 + b.effects[PBEffects::Counter] = -1 + b.effects[PBEffects::CounterTarget] = -1 + b.effects[PBEffects::Electrify] = false + b.effects[PBEffects::Endure] = false + b.effects[PBEffects::FirstPledge] = 0 + b.effects[PBEffects::Flinch] = false + b.effects[PBEffects::FocusPunch] = false + b.effects[PBEffects::FollowMe] = 0 + b.effects[PBEffects::HelpingHand] = false + b.effects[PBEffects::HyperBeam] -= 1 if b.effects[PBEffects::HyperBeam]>0 + b.effects[PBEffects::KingsShield] = false + b.effects[PBEffects::LaserFocus] -= 1 if b.effects[PBEffects::LaserFocus]>0 + if b.effects[PBEffects::LockOn]>0 # Also Mind Reader + b.effects[PBEffects::LockOn] -= 1 + b.effects[PBEffects::LockOnPos] = -1 if b.effects[PBEffects::LockOn]==0 + end + b.effects[PBEffects::MagicBounce] = false + b.effects[PBEffects::MagicCoat] = false + b.effects[PBEffects::MirrorCoat] = -1 + b.effects[PBEffects::MirrorCoatTarget] = -1 + b.effects[PBEffects::Powder] = false + b.effects[PBEffects::Prankster] = false + b.effects[PBEffects::PriorityAbility] = false + b.effects[PBEffects::PriorityItem] = false + b.effects[PBEffects::Protect] = false + b.effects[PBEffects::RagePowder] = false + b.effects[PBEffects::Roost] = false + b.effects[PBEffects::Snatch] = 0 + b.effects[PBEffects::SpikyShield] = false + b.effects[PBEffects::Spotlight] = 0 + b.effects[PBEffects::ThroatChop] -= 1 if b.effects[PBEffects::ThroatChop]>0 + b.lastHPLost = 0 + b.lastHPLostFromFoe = 0 + b.tookDamage = false + b.tookPhysicalHit = false + b.lastRoundMoveFailed = b.lastMoveFailed + b.lastAttacker.clear + b.lastFoeAttacker.clear + end + # Reset/count down side-specific effects (no messages) + for side in 0...2 + @sides[side].effects[PBEffects::CraftyShield] = false + if !@sides[side].effects[PBEffects::EchoedVoiceUsed] + @sides[side].effects[PBEffects::EchoedVoiceCounter] = 0 + end + @sides[side].effects[PBEffects::EchoedVoiceUsed] = false + @sides[side].effects[PBEffects::MatBlock] = false + @sides[side].effects[PBEffects::QuickGuard] = false + @sides[side].effects[PBEffects::Round] = false + @sides[side].effects[PBEffects::WideGuard] = false + end + # Reset/count down field-specific effects (no messages) + @field.effects[PBEffects::IonDeluge] = false + @field.effects[PBEffects::FairyLock] -= 1 if @field.effects[PBEffects::FairyLock]>0 + @field.effects[PBEffects::FusionBolt] = false + @field.effects[PBEffects::FusionFlare] = false + @endOfRound = false + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_PBBattleTerrains.rb b/Data/Scripts/011_Battle/003_PBBattleTerrains.rb new file mode 100644 index 000000000..5410ae558 --- /dev/null +++ b/Data/Scripts/011_Battle/003_PBBattleTerrains.rb @@ -0,0 +1,25 @@ +# These are in-battle terrain effects caused by moves like Electric Terrain. +begin + module PBBattleTerrains + None = 0 + Electric = 1 + Grassy = 2 + Misty = 3 + Psychic = 4 + + def self.animationName(terrain) + case terrain + when Electric; return "ElectricTerrain" + when Grassy; return "GrassyTerrain" + when Misty; return "MistyTerrain" + when Psychic; return "PsychicTerrain" + end + return nil + end + end + +rescue Exception + if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset" + raise $! + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb b/Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb new file mode 100644 index 000000000..fd206b723 --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb @@ -0,0 +1,69 @@ +# AI skill levels: +# 0: Wild Pokémon +# 1-31: Basic trainer (young/inexperienced) +# 32-47: Some skill +# 48-99: High skill +# 100+: Best trainers (Gym Leaders, Elite Four, Champion) +# NOTE: A trainer's skill value can range from 0-255, but by default only four +# distinct skill levels exist. The skill value is typically the same as +# the trainer's base money value. +module PBTrainerAI + # Minimum skill level to be in each AI category. + def self.minimumSkill; return 1; end + def self.mediumSkill; return 32; end + def self.highSkill; return 48; end + def self.bestSkill; return 100; end +end + + + +class PokeBattle_AI + def initialize(battle) + @battle = battle + end + + def pbAIRandom(x); return rand(x); end + + def pbStdDev(choices) + sum = 0 + n = 0 + choices.each do |c| + sum += c[1] + n += 1 + end + return 0 if n<2 + mean = sum.to_f/n.to_f + varianceTimesN = 0 + choices.each do |c| + next if c[1]<=0 + deviation = c[1].to_f-mean + varianceTimesN += deviation*deviation + end + # Using population standard deviation + # [(n-1) makes it a sample std dev, would be 0 with only 1 sample] + return Math.sqrt(varianceTimesN/n) + end + + #============================================================================= + # Decide whether the opponent should Mega Evolve their Pokémon + #============================================================================= + def pbEnemyShouldMegaEvolve?(idxBattler) + battler = @battle.battlers[idxBattler] + if @battle.pbCanMegaEvolve?(idxBattler) # Simple "always should if possible" + PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will Mega Evolve") + return true + end + return false + end + + #============================================================================= + # Choose an action + #============================================================================= + def pbDefaultChooseEnemyCommand(idxBattler) + return if pbEnemyShouldUseItem?(idxBattler) + return if pbEnemyShouldWithdraw?(idxBattler) + return if @battle.pbAutoFightMenu(idxBattler) + @battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?(idxBattler) + pbChooseMoves(idxBattler) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/002_AI_Item.rb b/Data/Scripts/011_Battle/004_AI/002_AI_Item.rb new file mode 100644 index 000000000..62c857d26 --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/002_AI_Item.rb @@ -0,0 +1,182 @@ +class PokeBattle_AI + #============================================================================= + # Decide whether the opponent should use an item on the Pokémon + #============================================================================= + def pbEnemyShouldUseItem?(idxBattler) + user = @battle.battlers[idxBattler] + item, idxTarget = pbEnemyItemToUse(idxBattler) + return false if item==0 + # Determine target of item (always the Pokémon choosing the action) + useType = pbGetItemData(item,ITEM_BATTLE_USE) + if useType && (useType==1 || useType==6) # Use on Pokémon + idxTarget = @battle.battlers[idxTarget].pokemonIndex # Party Pokémon + end + # Register use of item + @battle.pbRegisterItem(idxBattler,item,idxTarget) + PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use item #{PBItems.getName(item)}") + return true + end + + # NOTE: The AI will only consider using an item on the Pokémon it's currently + # choosing an action for. + def pbEnemyItemToUse(idxBattler) + return 0 if !@internalBattle + items = @battle.pbGetOwnerItems(idxBattler) + return 0 if !items || items.length==0 + # Determine target of item (always the Pokémon choosing the action) + idxTarget = idxBattler # Battler using the item + battler = @battle.battlers[idxTarget] + pkmn = battler.pokemon + # Item categories + hpItems = { + :POTION => 20, + :SUPERPOTION => 50, + :HYPERPOTION => 200, + :MAXPOTION => 999, + :BERRYJUICE => 20, + :SWEETHEART => 20, + :FRESHWATER => 50, + :SODAPOP => 60, + :LEMONADE => 80, + :MOOMOOMILK => 100, + :ORANBERRY => 10, + :SITRUSBERRY => battler.totalhp/4, + :ENERGYPOWDER => 50, + :ENERGYROOT => 200 + } + hpItems[:RAGECANDYBAR] = 20 if !NEWEST_BATTLE_MECHANICS + fullRestoreItems = [ + :FULLRESTORE + ] + oneStatusItems = [ # Preferred over items that heal all status problems + :AWAKENING,:CHESTOBERRY,:BLUEFLUTE, + :ANTIDOTE,:PECHABERRY, + :BURNHEAL,:RAWSTBERRY, + :PARALYZEHEAL,:PARLYZHEAL,:CHERIBERRY, + :ICEHEAL,:ASPEARBERRY + ] + allStatusItems = [ + :FULLHEAL,:LAVACOOKIE,:OLDGATEAU,:CASTELIACONE,:LUMIOSEGALETTE, + :SHALOURSABLE,:BIGMALASADA,:LUMBERRY,:HEALPOWDER + ] + allStatusItems.push(:RAGECANDYBAR) if NEWEST_BATTLE_MECHANICS + xItems = { + :XATTACK => [PBStats::ATTACK,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XATTACK2 => [PBStats::ATTACK,2], + :XATTACK3 => [PBStats::ATTACK,3], + :XATTACK6 => [PBStats::ATTACK,6], + :XDEFENSE => [PBStats::DEFENSE,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XDEFENSE2 => [PBStats::DEFENSE,2], + :XDEFENSE3 => [PBStats::DEFENSE,3], + :XDEFENSE6 => [PBStats::DEFENSE,6], + :XDEFEND => [PBStats::DEFENSE,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XDEFEND2 => [PBStats::DEFENSE,2], + :XDEFEND3 => [PBStats::DEFENSE,3], + :XDEFEND6 => [PBStats::DEFENSE,6], + :XSPATK => [PBStats::SPATK,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XSPATK2 => [PBStats::SPATK,2], + :XSPATK3 => [PBStats::SPATK,3], + :XSPATK6 => [PBStats::SPATK,6], + :XSPECIAL => [PBStats::SPATK,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XSPECIAL2 => [PBStats::SPATK,2], + :XSPECIAL3 => [PBStats::SPATK,3], + :XSPECIAL6 => [PBStats::SPATK,6], + :XSPDEF => [PBStats::SPDEF,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XSPDEF2 => [PBStats::SPDEF,2], + :XSPDEF3 => [PBStats::SPDEF,3], + :XSPDEF6 => [PBStats::SPDEF,6], + :XSPEED => [PBStats::SPEED,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XSPEED2 => [PBStats::SPEED,2], + :XSPEED3 => [PBStats::SPEED,3], + :XSPEED6 => [PBStats::SPEED,6], + :XACCURACY => [PBStats::ACCURACY,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XACCURACY2 => [PBStats::ACCURACY,2], + :XACCURACY3 => [PBStats::ACCURACY,3], + :XACCURACY6 => [PBStats::ACCURACY,6] + } + losthp = battler.totalhp-battler.hp + preferFullRestore = (battler.hp<=battler.totalhp*2/3 && + (battler.status!=PBStatuses::NONE || battler.effects[PBEffects::Confusion]>0)) + # Find all usable items + usableHPItems = [] + usableStatusItems = [] + usableXItems = [] + items.each do |i| + next if !i || i==0 + next if !@battle.pbCanUseItemOnPokemon?(i,pkmn,battler,@battle.scene,false) + next if !ItemHandlers.triggerCanUseInBattle(i,pkmn,battler,nil, + false,self,@battle.scene,false) + checkedItem = false + # Log HP healing items + if losthp>0 + hpItems.each do |item, power| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableHPItems.push([i,5,power]) + end + next if checkedItem + end + # Log Full Restores (HP healer and status curer) + if losthp>0 || battler.status!=PBStatuses::NONE + fullRestoreItems.each do |item| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableHPItems.push([i,(preferFullRestore) ? 3 : 7,999]) + usableStatusItems.push([i,(preferFullRestore) ? 3 : 9]) + end + next if checkedItem + end + # Log single status-curing items + if battler.status!=PBStatuses::NONE + oneStatusItems.each do |item| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableStatusItems.push([i,5]) + end + next if checkedItem + # Log Full Heal-type items + allStatusItems.each do |item| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableStatusItems.push([i,7]) + end + next if checkedItem + end + # Log stat-raising items + xItems.each do |item, data| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableXItems.push([i,battler.stages[data[0]],data[1]]) + end + next if checkedItem + end + # Prioritise using a HP restoration item + if usableHPItems.length>0 && (battler.hp<=battler.totalhp/4 || + (battler.hp<=battler.totalhp/2 && pbAIRandom(100)<30)) + usableHPItems.sort! { |a,b| (a[1]==b[1]) ? a[2]<=>b[2] : a[1]<=>b[1] } + prevItem = nil + usableHPItems.each do |i| + return i[0],idxTarget if i[2]>=losthp + prevItem = i + end + return prevItem[0],idxTarget + end + # Next prioritise using a status-curing item + if usableStatusItems.length>0 && pbAIRandom(100)<40 + usableStatusItems.sort! { |a,b| a[1]<=>b[1] } + return usableStatusItems[0][0],idxTarget + end + # Next try using an X item + if usableXItems.length>0 && pbAIRandom(100)<30 + usableXItems.sort! { |a,b| (a[1]==b[1]) ? a[2]<=>b[2] : a[1]<=>b[1] } + prevItem = nil + usableXItems.each do |i| + break if prevItem && i[1]>prevItem[1] + return i[0],idxTarget if i[1]+i[2]>=6 + prevItem = i + end + return prevItem[0],idxTarget + end + return 0 + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb b/Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb new file mode 100644 index 000000000..98d194863 --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb @@ -0,0 +1,182 @@ +class PokeBattle_AI + #============================================================================= + # Decide whether the opponent should switch Pokémon + #============================================================================= + def pbEnemyShouldWithdraw?(idxBattler) + return pbEnemyShouldWithdrawEx?(idxBattler,false) + end + + def pbEnemyShouldWithdrawEx?(idxBattler,forceSwitch) + return false if @battle.wildBattle? + shouldSwitch = forceSwitch + batonPass = -1 + moveType = -1 + skill = @battle.pbGetOwnerFromBattlerIndex(idxBattler).skill || 0 + battler = @battle.battlers[idxBattler] + # If Pokémon is within 6 levels of the foe, and foe's last move was + # super-effective and powerful + if !shouldSwitch && battler.turnCount>0 && skill>=PBTrainerAI.highSkill + target = battler.pbDirectOpposing(true) + if !target.fainted? && target.lastMoveUsed>0 && + (target.level-battler.level).abs<=6 + moveData = pbGetMoveData(target.lastMoveUsed) + moveType = moveData[MOVE_TYPE] + typeMod = pbCalcTypeMod(moveType,target,battler) + if PBTypes.superEffective?(typeMod) && moveData[MOVE_BASE_DAMAGE]>50 + switchChance = (moveData[MOVE_BASE_DAMAGE]>70) ? 30 : 20 + shouldSwitch = (pbAIRandom(100)=5 + shouldSwitch = true + end + # Pokémon is Perish Songed and has Baton Pass + if skill>=PBTrainerAI.highSkill && battler.effects[PBEffects::PerishSong]==1 + battler.eachMoveWithIndex do |m,i| + next if m.function!="0ED" # Baton Pass + next if !@battle.pbCanChooseMove?(idxBattler,i,false) + batonPass = i + break + end + end + # Pokémon will faint because of bad poisoning at the end of this round, but + # would survive at least one more round if it were regular poisoning instead + if battler.status==PBStatuses::POISON && battler.statusCount>0 && + skill>=PBTrainerAI.highSkill + toxicHP = battler.totalhp/16 + nextToxicHP = toxicHP*(battler.effects[PBEffects::Toxic]+1) + if battler.hp<=nextToxicHP && battler.hp>toxicHP*2 + shouldSwitch = true if pbAIRandom(100)<80 + end + end + # Pokémon is Encored into an unfavourable move + if battler.effects[PBEffects::Encore]>0 && skill>=PBTrainerAI.mediumSkill + idxEncoredMove = battler.pbEncoredMoveIndex + if idxEncoredMove>=0 + scoreSum = 0 + scoreCount = 0 + battler.eachOpposing do |b| + scoreSum += pbGetMoveScore(battler.moves[idxEncoredMove],battler,b,skill) + scoreCount += 1 + end + if scoreCount>0 && scoreSum/scoreCount<=20 + shouldSwitch = true if pbAIRandom(100)<80 + end + end + end + # If there is a single foe and it is resting after Hyper Beam or is + # Truanting (i.e. free turn) + if @battle.pbSideSize(battler.index+1)==1 && + !battler.pbDirectOpposing.fainted? && skill>=PBTrainerAI.highSkill + opp = battler.pbDirectOpposing + if opp.effects[PBEffects::HyperBeam]>0 || + (opp.hasActiveAbility?(:TRUANT) && opp.effects[PBEffects::Truant]) + shouldSwitch = false if pbAIRandom(100)<80 + end + end + # Sudden Death rule - I'm not sure what this means + if @battle.rules["suddendeath"] && battler.turnCount>0 + if battler.hp<=battler.totalhp/4 && pbAIRandom(100)<30 + shouldSwitch = true + elsif battler.hp<=battler.totalhp/2 && pbAIRandom(100)<80 + shouldSwitch = true + end + end + # Pokémon is about to faint because of Perish Song + if battler.effects[PBEffects::PerishSong]==1 + shouldSwitch = true + end + if shouldSwitch + list = [] + @battle.pbParty(idxBattler).each_with_index do |pkmn,i| + next if !@battle.pbCanSwitch?(idxBattler,i) + # If perish count is 1, it may be worth it to switch + # even with Spikes, since Perish Song's effect will end + if battler.effects[PBEffects::PerishSong]!=1 + # Will contain effects that recommend against switching + spikes = battler.pbOwnSide.effects[PBEffects::Spikes] + # Don't switch to this if too little HP + if spikes>0 + spikesDmg = [8,6,4][spikes-1] + if pkmn.hp<=pkmn.totalhp/spikesDmg + next if !pkmn.hasType?(:FLYING) && !pkmn.hasActiveAbility?(:LEVITATE) + end + end + end + # moveType is the type of the target's last used move + if moveType>=0 && PBTypes.ineffective?(pbCalcTypeMod(moveType,battler,battler)) + weight = 65 + typeMod = pbCalcTypeModPokemon(pkmn,battler.pbDirectOpposing(true)) + if PBTypes.superEffective?(typeMod.to_f/PBTypeEffectivenesss::NORMAL_EFFECTIVE) + # Greater weight if new Pokemon's type is effective against target + weight = 85 + end + list.unshift(i) if pbAIRandom(100)=0 && PBTypes.resistant?(pbCalcTypeMod(moveType,battler,battler)) + weight = 40 + typeMod = pbCalcTypeModPokemon(pkmn,battler.pbDirectOpposing(true)) + if PBTypes.superEffective?(typeMod.to_f/PBTypeEffectivenesss::NORMAL_EFFECTIVE) + # Greater weight if new Pokemon's type is effective against target + weight = 60 + end + list.unshift(i) if pbAIRandom(100)0 + if batonPass>=0 && @battle.pbRegisterMove(idxBattler,batonPass,false) + PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will use Baton Pass to avoid Perish Song") + return true + end + if @battle.pbRegisterSwitch(idxBattler,list[0]) + PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will switch with " + + "#{@battle.pbParty(idxBattler)[list[0]].name}") + return + end + end + end + return false + end + + #============================================================================= + # Choose a replacement Pokémon + #============================================================================= + def pbDefaultChooseNewEnemy(idxBattler,party) + enemies = [] + party.each_with_index do |p,i| + enemies.push(i) if @battle.pbCanSwitchLax?(idxBattler,i) + end + return -1 if enemies.length==0 + return pbChooseBestNewEnemy(idxBattler,party,enemies) + end + + def pbChooseBestNewEnemy(idxBattler,party,enemies) + return -1 if !enemies || enemies.length==0 + best = -1 + bestSum = 0 + movesData = pbLoadMovesData + enemies.each do |i| + pkmn = party[i] + sum = 0 + pkmn.moves.each do |m| + next if m.id==0 + moveData = movesData[m.id] + next if moveData[MOVE_BASE_DAMAGE]==0 + @battle.battlers[idxBattler].eachOpposing do |b| + bTypes = b.pbTypes(true) + sum += PBTypes.getCombinedEffectiveness(moveData[MOVE_TYPE], + bTypes[0],bTypes[1],bTypes[2]) + end + end + if best==-1 || sum>bestSum + best = i + bestSum = sum + end + end + return best + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/004_AI_Move.rb b/Data/Scripts/011_Battle/004_AI/004_AI_Move.rb new file mode 100644 index 000000000..60a938905 --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/004_AI_Move.rb @@ -0,0 +1,287 @@ +class PokeBattle_AI + #============================================================================= + # Main move-choosing method (moves with higher scores are more likely to be + # chosen) + #============================================================================= + def pbChooseMoves(idxBattler) + user = @battle.battlers[idxBattler] + wildBattler = (@battle.wildBattle? && @battle.opposes?(idxBattler)) + skill = 0 + if !wildBattler + skill = @battle.pbGetOwnerFromBattlerIndex(user.index).skill || 0 + end + # Get scores and targets for each move + # NOTE: A move is only added to the choices array if it has a non-zero + # score. + choices = [] + user.eachMoveWithIndex do |m,i| + next if !@battle.pbCanChooseMove?(idxBattler,i,false) + if wildBattler + pbRegisterMoveWild(user,i,choices) + else + pbRegisterMoveTrainer(user,i,choices,skill) + end + end + # Figure out useful information about the choices + totalScore = 0 + maxScore = 0 + choices.each do |c| + totalScore += c[1] + maxScore = c[1] if maxScore=0 + logMsg += ", " if i=PBTrainerAI.highSkill && maxScore>100 + stDev = pbStdDev(choices) + if stDev>=40 && pbAIRandom(100)<90 + preferredMoves = [] + choices.each do |c| + next if c[1]<200 && c[1]0 + m = preferredMoves[pbAIRandom(preferredMoves.length)] + PBDebug.log("[AI] #{user.pbThis} (#{user.index}) prefers #{user.moves[m[0]].name}") + @battle.pbRegisterMove(idxBattler,m[0],false) + @battle.pbRegisterTarget(idxBattler,m[2]) if m[2]>=0 + return + end + end + end + # Decide whether all choices are bad, and if so, try switching instead + if !wildBattler && skill>=PBTrainerAI.highSkill + badMoves = false + if (maxScore<=20 && user.turnCount>2) || + (maxScore<=40 && user.turnCount>5) + badMoves = true if pbAIRandom(100)<80 + end + if !badMoves && totalScore<100 && user.turnCount>1 + badMoves = true + choices.each do |c| + next if !user.moves[c[0]].damagingMove? + badMoves = false + break + end + badMoves = false if badMoves && pbAIRandom(100)<10 + end + if badMoves && pbEnemyShouldWithdrawEx?(idxBattler,true) + if $INTERNAL + PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will switch due to terrible moves") + end + return + end + end + # Randomly choose a move to use + if choices.length==0 + # If there are no calculated choices, use Struggle (or an Encored move) + @battle.pbAutoChooseMove(idxBattler) + else + # Randomly choose a move from the choices and register it + randNum = pbAIRandom(totalScore) + choices.each do |c| + randNum -= c[1] + next if randNum>=0 + @battle.pbRegisterMove(idxBattler,c[0],false) + @battle.pbRegisterTarget(idxBattler,c[2]) if c[2]>=0 + break + end + end + # Log the result + if @battle.choices[idxBattler][2] + PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use #{@battle.choices[user.index][2].name}") + end + end + + #============================================================================= + # Get scores for the given move against each possible target + #============================================================================= + # Wild Pokémon choose their moves randomly. + def pbRegisterMoveWild(user,idxMove,choices) + choices.push([idxMove,100,-1]) # Move index, score, target + end + + # Trainer Pokémon calculate how much they want to use each of their moves. + def pbRegisterMoveTrainer(user,idxMove,choices,skill) + move = user.moves[idxMove] + targetType = move.pbTarget(user) + if PBTargets.multipleTargets?(targetType) + # If move affects multiple battlers and you don't choose a particular one + totalScore = 0 + @battle.eachBattler do |b| + next if !@battle.pbMoveCanTarget?(user.index,b.index,targetType) + score = pbGetMoveScore(move,user,b,skill) + totalScore += ((user.opposes?(b)) ? score : -score) + end + choices.push([idxMove,totalScore,-1]) if totalScore>0 + elsif PBTargets.noTargets?(targetType) + # If move has no targets, affects the user, a side or the whole field + score = pbGetMoveScore(move,user,user,skill) + choices.push([idxMove,score,-1]) if score>0 + else + # If move affects one battler and you have to choose which one + scoresAndTargets = [] + @battle.eachBattler do |b| + next if !@battle.pbMoveCanTarget?(user.index,b.index,targetType) + next if PBTargets.canChooseFoeTarget?(targetType) && !user.opposes?(b) + score = pbGetMoveScore(move,user,b,skill) + scoresAndTargets.push([score,b.index]) if score>0 + end + if scoresAndTargets.length>0 + # Get the one best target for the move + scoresAndTargets.sort! { |a,b| b[0]<=>a[0] } + choices.push([idxMove,scoresAndTargets[0][0],scoresAndTargets[0][1]]) + end + end + end + + #============================================================================= + # Get a score for the given move being used against the given target + #============================================================================= + def pbGetMoveScore(move,user,target,skill=100) + skill = PBTrainerAI.minimumSkill if skill=PBTrainerAI.mediumSkill + # Prefer damaging moves if AI has no more Pokémon or AI is less clever + if @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + if !(skill>=PBTrainerAI.highSkill && @battle.pbAbleNonActiveCount(target.idxOwnSide)>0) + if move.statusMove? + score /= 1.5 + elsif target.hp<=target.totalhp/2 + score *= 1.5 + end + end + end + # Don't prefer attacking the target if they'd be semi-invulnerable + if skill>=PBTrainerAI.highSkill && move.accuracy>0 && + (target.semiInvulnerable? || target.effects[PBEffects::SkyDrop]>=0) + miss = true + miss = false if user.hasActiveAbility?(:NOGUARD) || target.hasActiveAbility?(:NOGUARD) + if miss && pbRoughStat(user,PBStats::SPEED,skill)>pbRoughStat(target,PBStats::SPEED,skill) + # Knows what can get past semi-invulnerability + if target.effects[PBEffects::SkyDrop]>=0 + miss = false if move.hitsFlyingTargets? + else + if target.inTwoTurnAttack?("0C9","0CC","0CE") # Fly, Bounce, Sky Drop + miss = false if move.hitsFlyingTargets? + elsif target.inTwoTurnAttack?("0CA") # Dig + miss = false if move.hitsDiggingTargets? + elsif target.inTwoTurnAttack?("0CB") # Dive + miss = false if move.hitsDivingTargets? + end + end + end + score -= 80 if miss + end + # Pick a good move for the Choice items + if user.hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF]) + if move.baseDamage>=60; score += 60 + elsif move.damagingMove?; score += 30 + elsif move.function=="0F2"; score += 70 # Trick + else; score -= 60 + end + end + # If user is asleep, prefer moves that are usable while asleep + if user.status==PBStatuses::SLEEP && !move.usableWhenAsleep? + hasSleepMove = false + user.eachMove do |m| + next unless m.usableWhenAsleep? + score -= 60 + break + end + end + # If user is frozen, prefer a move that can thaw the user + if user.status==PBStatuses::FROZEN + if move.thawsUser? + score += 40 + else + user.eachMove do |m| + next unless m.thawsUser? + score -= 60 + break + end + end + end + # If target is frozen, don't prefer moves that could thaw them + if target.status==PBStatuses::FROZEN + user.eachMove do |m| + next if m.thawsUser? + score -= 60 + break + end + end + end + # Adjust score based on how much damage it can deal + if move.damagingMove? + score = pbGetMoveScoreDamage(score,move,user,target,skill) + else # Status moves + # Don't prefer attacks which don't deal damage + score -= 10 + # Account for accuracy of move + accuracy = pbRoughAccuracy(move,user,target,skill) + score *= accuracy/100.0 + score = 0 if score<=10 && skill>=PBTrainerAI.highSkill + end + score = score.to_i + score = 0 if score<0 + return score + end + + #============================================================================= + # Add to a move's score based on how much damage it will deal (as a percentage + # of the target's current HP) + #============================================================================= + def pbGetMoveScoreDamage(score,move,user,target,skill) + # Don't prefer moves that are ineffective because of abilities or effects + return 0 if score<=0 || pbCheckMoveImmunity(score,move,user,target,skill) + # Calculate how much damage the move will do (roughly) + baseDmg = pbMoveBaseDamage(move,user,target,skill) + realDamage = pbRoughDamage(move,user,target,skill,baseDmg) + # Account for accuracy of move + accuracy = pbRoughAccuracy(move,user,target,skill) + realDamage *= accuracy/100.0 + # Two-turn attacks waste 2 turns to deal one lot of damage + if move.chargingTurnMove? || move.function=="0C2" # Hyper Beam + realDamage *= 2/3 # Not halved because semi-invulnerable during use or hits first turn + end + # Prefer flinching external effects (note that move effects which cause + # flinching are dealt with in the function code part of score calculation) + if skill>=PBTrainerAI.mediumSkill + if !target.hasActiveAbility?(:INNERFOCUS) && + !target.hasActiveAbility?(:SHIELDDUST) && + target.effects[PBEffects::Substitute]==0 + canFlinch = false + if move.canKingsRock? && user.hasActiveItem?([:KINGSROCK,:RAZORFANG]) + canFlinch = true + end + if user.hasActiveAbility?(:STENCH) && !move.flinchingMove? + canFlinch = true + end + realDamage *= 1.3 if canFlinch + end + end + # Convert damage to percentage of target's remaining HP + damagePercentage = realDamage*100.0/target.hp + # Don't prefer weak attacks +# damagePercentage /= 2 if damagePercentage<20 + # Prefer damaging attack if level difference is significantly high + damagePercentage *= 1.2 if user.level-10>target.level + # Adjust score + damagePercentage = 120 if damagePercentage>120 # Treat all lethal moves the same + damagePercentage += 40 if damagePercentage>100 # Prefer moves likely to be lethal + score += damagePercentage.to_i + return score + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb b/Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb new file mode 100644 index 000000000..4658dc36c --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb @@ -0,0 +1,3075 @@ +class PokeBattle_AI + #============================================================================= + # Get a score for the given move based on its effect + #============================================================================= + def pbGetMoveScoreFunctionCode(score,move,user,target,skill=100) + case move.function + #--------------------------------------------------------------------------- + when "000" # No extra effect + #--------------------------------------------------------------------------- + when "001" + score -= 95 + score = 0 if skill>=PBTrainerAI.highSkill + #--------------------------------------------------------------------------- + when "002" # Struggle + #--------------------------------------------------------------------------- + when "003" + if target.pbCanSleep?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + score -= 30 if target.effects[PBEffects::Yawn]>0 + end + if skill>=PBTrainerAI.highSkill + score -= 30 if target.hasActiveAbility?(:MARVELSCALE) + end + if skill>=PBTrainerAI.bestSkill + if target.pbHasMoveFunction?("011","0B4") # Snore, Sleep Talk + score -= 50 + end + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "004" + if target.effects[PBEffects::Yawn]>0 || !target.pbCanSleep?(user,false) + score -= 90 if skill>=PBTrainerAI.mediumSkill + else + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 30 if target.hasActiveAbility?(:MARVELSCALE) + end + if skill>=PBTrainerAI.bestSkill + if target.pbHasMoveFunction?("011","0B4") # Snore, Sleep Talk + score -= 50 + end + end + end + #--------------------------------------------------------------------------- + when "005", "006", "0BE" + if target.pbCanPoison?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + score += 30 if target.hp<=target.totalhp/4 + score += 50 if target.hp<=target.totalhp/8 + score -= 40 if target.effects[PBEffects::Yawn]>0 + end + if skill>=PBTrainerAI.highSkill + score += 10 if pbRoughStat(target,PBStats::DEFENSE,skill)>100 + score += 10 if pbRoughStat(target,PBStats::SPDEF,skill)>100 + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:TOXICBOOST]) + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "007", "008", "009", "0C5" + if target.pbCanParalyze?(user,false) && + !(skill>=PBTrainerAI.mediumSkill && + isConst?(move.id,PBMoves,:THUNDERWAVE) && + PBTypes.ineffective?(pbCalcTypeMod(move.type,user,target))) + score += 30 + if skill>=PBTrainerAI.mediumSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeedospeed + score -= 40 + end + end + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET]) + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "00A", "00B", "0C6" + if target.pbCanBurn?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET,:FLAREBOOST]) + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "00C", "00D", "00E" + if target.pbCanFreeze?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 20 if target.hasActiveAbility?(:MARVELSCALE) + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "00F" + score += 30 + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + #--------------------------------------------------------------------------- + when "010" + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + score += 30 if target.effects[PBEffects::Minimize] + #--------------------------------------------------------------------------- + when "011" + if user.asleep? + score += 100 # Because it can only be used while asleep + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + else + score -= 90 # Because it will fail here + score = 0 if skill>=PBTrainerAI.bestSkill + end + #--------------------------------------------------------------------------- + when "012" + if user.turnCount==0 + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + else + score -= 90 # Because it will fail here + score = 0 if skill>=PBTrainerAI.bestSkill + end + #--------------------------------------------------------------------------- + when "013", "014", "015" + if target.pbCanConfuse?(user,false) + score += 30 + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "016" + canattract = true + agender = user.gender + ogender = target.gender + if agender==2 || ogender==2 || agender==ogender + score -= 90; canattract = false + elsif target.effects[PBEffects::Attract]>=0 + score -= 80; canattract = false + elsif skill>=PBTrainerAI.bestSkill && target.hasActiveAbility?(:OBLIVIOUS) + score -= 80; canattract = false + end + if skill>=PBTrainerAI.highSkill + if canattract && target.hasActiveItem?(:DESTINYKNOT) && + user.pbCanAttract?(target,false) + score -= 30 + end + end + #--------------------------------------------------------------------------- + when "017" + score += 30 if target.status==PBStatuses::NONE + #--------------------------------------------------------------------------- + when "018" + case user.status + when PBStatuses::POISON + score += 40 + if skill>=PBTrainerAI.mediumSkill + if user.hp=PBTrainerAI.highSkill && + user.hp<(user.effects[PBEffects::Toxic]+1)*user.totalhp/16 + score += 60 + end + end + when PBStatuses::BURN, PBStatuses::PARALYSIS + score += 40 + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "019" + statuses = 0 + @battle.pbParty(user.index).each do |pkmn| + statuses += 1 if pkmn && pkmn.status!=PBStatuses::NONE + end + if statuses==0 + score -= 80 + else + score += 20*statuses + end + #--------------------------------------------------------------------------- + when "01A" + if user.pbOwnSide.effects[PBEffects::Safeguard]>0 + score -= 80 + elsif user.status!=0 + score -= 40 + else + score += 30 + end + #--------------------------------------------------------------------------- + when "01B" + if user.status==PBStatuses::NONE + score -= 90 + else + score += 40 + end + #--------------------------------------------------------------------------- + when "01C" + if move.statusMove? + if user.statStageAtMax?(PBStats::ATTACK) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 20 if user.stages[PBStats::ATTACK]<0 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score += 20 if hasPhysicalAttack + end + end + #--------------------------------------------------------------------------- + when "01D", "01E", "0C8" + if move.statusMove? + if user.statStageAtMax?(PBStats::DEFENSE) + score -= 90 + else + score -= user.stages[PBStats::DEFENSE]*20 + end + else + score += 20 if user.stages[PBStats::DEFENSE]<0 + end + #--------------------------------------------------------------------------- + when "01F" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + else + score += 20 if user.stages[PBStats::SPEED]<0 + end + #--------------------------------------------------------------------------- + when "020" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPATK) + score -= 90 + else + score -= user.stages[PBStats::SPATK]*20 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 20 if user.stages[PBStats::SPATK]<0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 20 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "021" + foundMove = false + user.eachMove do |m| + next if !isConst?(m.type,PBTypes,:ELECTRIC) || !m.damagingMove? + foundMove = true + break + end + score += 20 if foundMove + if move.statusMove? + if user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score -= user.stages[PBStats::SPDEF]*20 + end + else + score += 20 if user.stages[PBStats::SPDEF]<0 + end + #--------------------------------------------------------------------------- + when "022" + if move.statusMove? + if user.statStageAtMax?(PBStats::EVASION) + score -= 90 + else + score -= user.stages[PBStats::EVASION]*10 + end + else + score += 20 if user.stages[PBStats::EVASION]<0 + end + #--------------------------------------------------------------------------- + when "023" + if move.statusMove? + if user.effects[PBEffects::FocusEnergy]>=2 + score -= 80 + else + score += 30 + end + else + score += 30 if user.effects[PBEffects::FocusEnergy]<2 + end + #--------------------------------------------------------------------------- + when "024" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::DEFENSE) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::DEFENSE]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "025" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::DEFENSE) && + user.statStageAtMax?(PBStats::ACCURACY) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::DEFENSE]*10 + score -= user.stages[PBStats::ACCURACY]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "026" + score += 40 if user.turnCount==0 # Dragon Dance tends to be popular + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 20 if aspeedospeed + end + end + #--------------------------------------------------------------------------- + when "027", "028" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::SPATK) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPATK]*10 + if skill>=PBTrainerAI.mediumSkill + hasDamagingAttack = false + user.eachMove do |m| + next if !m.damagingMove? + hasDamagingAttack = true + break + end + if hasDamagingAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if move.function=="028" # Growth + score += 20 if @battle.pbWeather==PBWeather::Sun || + @battle.pbWeather==PBWeather::HarshSun + end + end + #--------------------------------------------------------------------------- + when "029" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::ACCURACY) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::ACCURACY]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "02A" + if user.statStageAtMax?(PBStats::DEFENSE) && + user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score -= user.stages[PBStats::DEFENSE]*10 + score -= user.stages[PBStats::SPDEF]*10 + end + #--------------------------------------------------------------------------- + when "02B" + if user.statStageAtMax?(PBStats::SPEED) && + user.statStageAtMax?(PBStats::SPATK) && + user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score -= user.stages[PBStats::SPATK]*10 + score -= user.stages[PBStats::SPDEF]*10 + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeedospeed + score += 20 + end + end + end + #--------------------------------------------------------------------------- + when "02C" + if user.statStageAtMax?(PBStats::SPATK) && + user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score += 40 if user.turnCount==0 # Calm Mind tends to be popular + score -= user.stages[PBStats::SPATK]*10 + score -= user.stages[PBStats::SPDEF]*10 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "02D" + PBStats.eachMainBattleStat { |s| score += 10 if user.stages[s]<0 } + if skill>=PBTrainerAI.mediumSkill + hasDamagingAttack = false + user.eachMove do |m| + next if !m.damagingMove? + hasDamagingAttack = true + break + end + score += 20 if hasDamagingAttack + end + #--------------------------------------------------------------------------- + when "02E" + if move.statusMove? + if user.statStageAtMax?(PBStats::ATTACK) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::ATTACK]<0 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score += 20 if hasPhysicalAttack + end + end + #--------------------------------------------------------------------------- + when "02F" + if move.statusMove? + if user.statStageAtMax?(PBStats::DEFENSE) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::DEFENSE]*20 + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::DEFENSE]<0 + end + #--------------------------------------------------------------------------- + when "030", "031" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score += 20 if user.turnCount==0 + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::SPEED]<0 + end + #--------------------------------------------------------------------------- + when "032" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPATK) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::SPATK]*20 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::SPATK]<0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 20 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "033" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::SPDEF]*20 + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::SPDEF]<0 + end + #--------------------------------------------------------------------------- + when "034" + if move.statusMove? + if user.statStageAtMax?(PBStats::EVASION) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::EVASION]*10 + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::EVASION]<0 + end + #--------------------------------------------------------------------------- + when "035" + score -= user.stages[PBStats::ATTACK]*20 + score -= user.stages[PBStats::SPEED]*20 + score -= user.stages[PBStats::SPATK]*20 + score += user.stages[PBStats::DEFENSE]*10 + score += user.stages[PBStats::SPDEF]*10 + if skill>=PBTrainerAI.mediumSkill + hasDamagingAttack = false + user.eachMove do |m| + next if !m.damagingMove? + hasDamagingAttack = true + break + end + score += 20 if hasDamagingAttack + end + #--------------------------------------------------------------------------- + when "036" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + #--------------------------------------------------------------------------- + when "037" + avgStat = 0; canChangeStat = false + PBStats.eachBattleStat do |s| + next if target.statStageAtMax?(s) + avgStat -= target.stages[s] + canChangeStat = true + end + if canChangeStat + avgStat = avgStat/2 if avgStat<0 # More chance of getting even better + score += avgStat*10 + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "038" + if move.statusMove? + if user.statStageAtMax?(PBStats::DEFENSE) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::DEFENSE]*30 + end + else + score += 10 if user.turnCount==0 + score += 30 if user.stages[PBStats::DEFENSE]<0 + end + #--------------------------------------------------------------------------- + when "039" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPATK) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::SPATK]*30 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 30 if user.stages[PBStats::SPATK]<0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 30 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "03A" + if user.statStageAtMax?(PBStats::ATTACK) || + user.hp<=user.totalhp/2 + score -= 100 + else + score += (6-user.stages[PBStats::ATTACK])*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 40 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "03B" + avg = user.stages[PBStats::ATTACK]*10 + avg += user.stages[PBStats::DEFENSE]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "03C" + avg = user.stages[PBStats::DEFENSE]*10 + avg += user.stages[PBStats::SPDEF]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "03D" + avg = user.stages[PBStats::DEFENSE]*10 + avg += user.stages[PBStats::SPEED]*10 + avg += user.stages[PBStats::SPDEF]*10 + score += (avg/3).floor + #--------------------------------------------------------------------------- + when "03E" + score += user.stages[PBStats::SPEED]*10 + #--------------------------------------------------------------------------- + when "03F" + score += user.stages[PBStats::SPATK]*10 + #--------------------------------------------------------------------------- + when "040" + if !target.pbCanConfuse?(user,false) + score -= 90 + else + score += 30 if target.stages[PBStats::SPATK]<0 + end + #--------------------------------------------------------------------------- + when "041" + if !target.pbCanConfuse?(user,false) + score -= 90 + else + score += 30 if target.stages[PBStats::ATTACK]<0 + end + #--------------------------------------------------------------------------- + when "042" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::ATTACK,user) + score -= 90 + else + score += target.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 20 if target.stages[PBStats::ATTACK]>0 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score += 20 if hasPhysicalAttack + end + end + #--------------------------------------------------------------------------- + when "043" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::DEFENSE,user) + score -= 90 + else + score += target.stages[PBStats::DEFENSE]*20 + end + else + score += 20 if target.stages[PBStats::DEFENSE]>0 + end + #--------------------------------------------------------------------------- + when "044" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPEED,user) + score -= 90 + else + score += target.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + else + score += 20 if user.stages[PBStats::SPEED]>0 + end + #--------------------------------------------------------------------------- + when "045" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPATK,user) + score -= 90 + else + score += user.stages[PBStats::SPATK]*20 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + target.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 20 if user.stages[PBStats::SPATK]>0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + target.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 20 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "046" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPDEF,user) + score -= 90 + else + score += target.stages[PBStats::SPDEF]*20 + end + else + score += 20 if target.stages[PBStats::SPDEF]>0 + end + #--------------------------------------------------------------------------- + when "047" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::ACCURACY,user) + score -= 90 + else + score += target.stages[PBStats::ACCURACY]*10 + end + else + score += 20 if target.stages[PBStats::ACCURACY]>0 + end + #--------------------------------------------------------------------------- + when "048" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::EVASION,user) + score -= 90 + else + score += target.stages[PBStats::EVASION]*10 + end + else + score += 20 if target.stages[PBStats::EVASION]>0 + end + #--------------------------------------------------------------------------- + when "049" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::EVASION,user) + score -= 90 + else + score += target.stages[PBStats::EVASION]*10 + end + else + score += 20 if target.stages[PBStats::EVASION]>0 + end + score += 30 if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 || + target.pbOwnSide.effects[PBEffects::Reflect]>0 || + target.pbOwnSide.effects[PBEffects::LightScreen]>0 || + target.pbOwnSide.effects[PBEffects::Mist]>0 || + target.pbOwnSide.effects[PBEffects::Safeguard]>0 + score -= 30 if target.pbOwnSide.effects[PBEffects::Spikes]>0 || + target.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 || + target.pbOwnSide.effects[PBEffects::StealthRock] + #--------------------------------------------------------------------------- + when "04A" + avg = target.stages[PBStats::ATTACK]*10 + avg += target.stages[PBStats::DEFENSE]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "04B" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::ATTACK,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 20 if target.stages[PBStats::ATTACK]>0 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score += 20 if hasPhysicalAttack + end + end + #--------------------------------------------------------------------------- + when "04C" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::DEFENSE,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::DEFENSE]*20 + end + else + score += 10 if user.turnCount==0 + score += 20 if target.stages[PBStats::DEFENSE]>0 + end + #--------------------------------------------------------------------------- + when "04D" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPEED,user) + score -= 90 + else + score += 20 if user.turnCount==0 + score += target.stages[PBStats::SPEED]*20 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + else + score += 10 if user.turnCount==0 + score += 30 if target.stages[PBStats::SPEED]>0 + end + #--------------------------------------------------------------------------- + when "04E" + if user.gender==2 || target.gender==2 || user.gender==target.gender || + target.hasActiveAbility?(:OBLIVIOUS) + score -= 90 + elsif move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPATK,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::SPATK]*20 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + target.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 20 if target.stages[PBStats::SPATK]>0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + target.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 30 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "04F" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPDEF,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::SPDEF]*20 + end + else + score += 10 if user.turnCount==0 + score += 20 if target.stages[PBStats::SPDEF]>0 + end + #--------------------------------------------------------------------------- + when "050" + if target.effects[PBEffects::Substitute]>0 + score -= 90 + else + avg = 0; anyChange = false + PBStats.eachBattleStat do |s| + next if target.stages[s]==0 + avg += target.stages[s] + anyChange = true + end + if anyChange + score += avg*10 + else + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "051" + if skill>=PBTrainerAI.mediumSkill + stages = 0 + @battle.eachBattler do |b| + totalStages = 0 + PBStats.eachBattleStat { |s| totalStages += b.stages[s] } + if b.opposes?(user) + stages += totalStages + else + stages -= totalStages + end + end + score += stages*10 + end + #--------------------------------------------------------------------------- + when "052" + if skill>=PBTrainerAI.mediumSkill + aatk = user.stages[PBStats::ATTACK] + aspa = user.stages[PBStats::SPATK] + oatk = target.stages[PBStats::ATTACK] + ospa = target.stages[PBStats::SPATK] + if aatk>=oatk && aspa>=ospa + score -= 80 + else + score += (oatk-aatk)*10 + score += (ospa-aspa)*10 + end + else + score -= 50 + end + #--------------------------------------------------------------------------- + when "053" + if skill>=PBTrainerAI.mediumSkill + adef = user.stages[PBStats::DEFENSE] + aspd = user.stages[PBStats::SPDEF] + odef = target.stages[PBStats::DEFENSE] + ospd = target.stages[PBStats::SPDEF] + if adef>=odef && aspd>=ospd + score -= 80 + else + score += (odef-adef)*10 + score += (ospd-aspd)*10 + end + else + score -= 50 + end + #--------------------------------------------------------------------------- + when "054" + if skill>=PBTrainerAI.mediumSkill + userStages = 0; targetStages = 0 + PBStats.eachBattleStat do |s| + userStages += user.stages[s] + targetStages += target.stages[s] + end + score += (targetStages-userStages)*10 + else + score -= 50 + end + #--------------------------------------------------------------------------- + when "055" + if skill>=PBTrainerAI.mediumSkill + equal = true + PBStats.eachBattleStat do |s| + stagediff = target.stages[s]-user.stages[s] + score += stagediff*10 + equal = false if stagediff!=0 + end + score -= 80 if equal + else + score -= 50 + end + #--------------------------------------------------------------------------- + when "056" + score -= 80 if user.pbOwnSide.effects[PBEffects::Mist]>0 + #--------------------------------------------------------------------------- + when "057" + if skill>=PBTrainerAI.mediumSkill + aatk = pbRoughStat(user,PBStats::ATTACK,skill) + adef = pbRoughStat(user,PBStats::DEFENSE,skill) + if aatk==adef || + user.effects[PBEffects::PowerTrick] # No flip-flopping + score -= 90 + elsif adef>aatk # Prefer a higher Attack + score += 30 + else + score -= 30 + end + else + score -= 30 + end + #--------------------------------------------------------------------------- + when "058" + if skill>=PBTrainerAI.mediumSkill + aatk = pbRoughStat(user,PBStats::ATTACK,skill) + aspatk = pbRoughStat(user,PBStats::SPATK,skill) + oatk = pbRoughStat(target,PBStats::ATTACK,skill) + ospatk = pbRoughStat(target,PBStats::SPATK,skill) + if aatk=PBTrainerAI.mediumSkill + adef = pbRoughStat(user,PBStats::DEFENSE,skill) + aspdef = pbRoughStat(user,PBStats::SPDEF,skill) + odef = pbRoughStat(target,PBStats::DEFENSE,skill) + ospdef = pbRoughStat(target,PBStats::SPDEF,skill) + if adef0 + score -= 90 + elsif user.hp>=(user.hp+target.hp)/2 + score -= 90 + else + score += 40 + end + #--------------------------------------------------------------------------- + when "05B" + score -= 90 if user.pbOwnSide.effects[PBEffects::Tailwind]>0 + #--------------------------------------------------------------------------- + when "05C" + moveBlacklist = [ + "002", # Struggle + "014", # Chatter + "05C", # Mimic + "05D", # Sketch + "0B6" # Metronome + ] + lastMoveData = pbGetMoveData(target.lastRegularMoveUsed) + if user.effects[PBEffects::Transform] || + target.lastRegularMoveUsed<=0 || + moveBlacklist.include?(lastMoveData[MOVE_FUNCTION_CODE]) || + isConst?(lastMoveData[MOVE_TYPE],PBTypes,:SHADOW) + score -= 90 + end + user.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + score -= 90 + break + end + #--------------------------------------------------------------------------- + when "05D" + moveBlacklist = [ + "002", # Struggle + "014", # Chatter + "05D" # Sketch + ] + lastMoveData = pbGetMoveData(target.lastRegularMoveUsed) + if user.effects[PBEffects::Transform] || + target.lastRegularMoveUsed<=0 || + moveBlacklist.include?(lastMoveData[MOVE_FUNCTION_CODE]) || + isConst?(lastMoveData[MOVE_TYPE],PBTypes,:SHADOW) + score -= 90 + end + user.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + score -= 90 # User already knows the move that will be Sketched + break + end + #--------------------------------------------------------------------------- + when "05E" + if isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + else + types = [] + user.eachMove do |m| + next if m.id==@id + next if PBTypes.isPseudoType?(m.type) + next if user.pbHasType?(m.type) + types.push(m.type) if !types.include?(m.type) + end + score -= 90 if types.length==0 + end + #--------------------------------------------------------------------------- + when "05F" + if isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + elsif target.lastMoveUsed<=0 || + PBTypes.isPseudoType?(pbGetMoveData(target.lastMoveUsed,MOVE_TYPE)) + score -= 90 + else + aType = -1 + target.eachMove do |m| + next if m.id!=target.lastMoveUsed + aType = m.pbCalcType(user) + break + end + if aType<0 + score -= 90 + else + types = [] + for i in 0..PBTypes.maxValue + next if user.pbHasType?(i) + types.push(i) if PBTypes.resistant?(aType,i) + end + score -= 90 if types.length==0 + end + end + #--------------------------------------------------------------------------- + when "060" + if isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + envtypes = [ + :NORMAL, # None + :GRASS, # Grass + :GRASS, # Tall grass + :WATER, # Moving water + :WATER, # Still water + :WATER, # Underwater + :ROCK, # Rock + :ROCK, # Cave + :GROUND # Sand + ] + type = envtypes[@environment] + score -= 90 if user.pbHasType?(type) + end + #--------------------------------------------------------------------------- + when "061" + if target.effects[PBEffects::Substitute]>0 || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + elsif target.pbHasType?(:WATER) + score -= 90 + end + #--------------------------------------------------------------------------- + when "062" + if isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + elsif user.pbHasType?(target.type1) && + user.pbHasType?(target.type2) && + target.pbHasType?(user.type1) && + target.pbHasType?(user.type2) + score -= 90 + end + #--------------------------------------------------------------------------- + when "063" + if target.effects[PBEffects::Substitute]>0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + if isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:SIMPLE) || + isConst?(target.ability,PBAbilities,:TRUANT) + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "064" + if target.effects[PBEffects::Substitute]>0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + if isConst?(target.ability,PBAbilities,:INSOMNIA) || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:TRUANT) + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "065" + score -= 40 # don't prefer this move + if skill>=PBTrainerAI.mediumSkill + if target.ability==0 || user.ability==target.ability || + isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:FLOWERGIFT) || + isConst?(target.ability,PBAbilities,:FORECAST) || + isConst?(target.ability,PBAbilities,:ILLUSION) || + isConst?(target.ability,PBAbilities,:IMPOSTER) || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:TRACE) || + isConst?(target.ability,PBAbilities,:WONDERGUARD) || + isConst?(target.ability,PBAbilities,:ZENMODE) + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + if isConst?(target.ability,PBAbilities,:TRUANT) && + user.opposes?(target) + score -= 90 + elsif isConst?(target.ability,PBAbilities,:SLOWSTART) && + user.opposes?(target) + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "066" + score -= 40 # don't prefer this move + if target.effects[PBEffects::Substitute]>0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + if user.ability==0 || user.ability==target.ability || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:TRUANT) || + isConst?(user.ability,PBAbilities,:FLOWERGIFT) || + isConst?(user.ability,PBAbilities,:FORECAST) || + isConst?(user.ability,PBAbilities,:ILLUSION) || + isConst?(user.ability,PBAbilities,:IMPOSTER) || + isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) || + isConst?(user.ability,PBAbilities,:TRACE) || + isConst?(user.ability,PBAbilities,:ZENMODE) + score -= 90 + end + if skill>=PBTrainerAI.highSkill + if isConst?(user.ability,PBAbilities,:TRUANT) && + user.opposes?(target) + score += 90 + elsif isConst?(user.ability,PBAbilities,:SLOWSTART) && + user.opposes?(target) + score += 90 + end + end + end + #--------------------------------------------------------------------------- + when "067" + score -= 40 # don't prefer this move + if skill>=PBTrainerAI.mediumSkill + if (user.ability==0 && target.ability==0) || + user.ability==target.ability || + isConst?(user.ability,PBAbilities,:ILLUSION) || + isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) || + isConst?(user.ability,PBAbilities,:WONDERGUARD) || + isConst?(target.ability,PBAbilities,:ILLUSION) || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:WONDERGUARD) + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + if isConst?(target.ability,PBAbilities,:TRUANT) && + user.opposes?(target) + score -= 90 + elsif isConst?(target.ability,PBAbilities,:SLOWSTART) && + user.opposes?(target) + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "068" + if target.effects[PBEffects::Substitute]>0 || + target.effects[PBEffects::GastroAcid] + score -= 90 + elsif skill>=PBTrainerAI.highSkill + score -= 90 if isConst?(target.ability,PBAbilities,:MULTITYPE) + score -= 90 if isConst?(target.ability,PBAbilities,:RKSSYSTEM) + score -= 90 if isConst?(target.ability,PBAbilities,:SLOWSTART) + score -= 90 if isConst?(target.ability,PBAbilities,:TRUANT) + end + #--------------------------------------------------------------------------- + when "069" + score -= 70 + #--------------------------------------------------------------------------- + when "06A" + if target.hp<=20 + score += 80 + elsif target.level>=25 + score -= 60 # Not useful against high-level Pokemon + end + #--------------------------------------------------------------------------- + when "06B" + score += 80 if target.hp<=40 + #--------------------------------------------------------------------------- + when "06C" + score -= 50 + score += target.hp*100/target.totalhp + #--------------------------------------------------------------------------- + when "06D" + score += 80 if target.hp<=user.level + #--------------------------------------------------------------------------- + when "06E" + if user.hp>=target.hp + score -= 90 + elsif user.hpuser.level + #--------------------------------------------------------------------------- + when "071" + if target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + attack = pbRoughStat(user,PBStats::ATTACK,skill) + spatk = pbRoughStat(user,PBStats::SPATK,skill) + if attack*1.5=PBTrainerAI.mediumSkill && target.lastMoveUsed>0 + moveData = pbGetMoveData(target.lastMoveUsed) + if moveData[MOVE_BASE_DAMAGE]>0 && + (MOVE_CATEGORY_PER_MOVE && moveData[MOVE_CATEGORY]==0) || + (!MOVE_CATEGORY_PER_MOVE && PBTypes.isPhysicalType?(moveData[MOVE_TYPE])) + score -= 60 + end + end + end + #--------------------------------------------------------------------------- + when "072" + if target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + attack = pbRoughStat(user,PBStats::ATTACK,skill) + spatk = pbRoughStat(user,PBStats::SPATK,skill) + if attack>spatk*1.5 + score -= 60 + elsif skill>=PBTrainerAI.mediumSkill && target.lastMoveUsed>0 + moveData = pbGetMoveData(target.lastMoveUsed) + if moveData[MOVE_BASE_DAMAGE]>0 && + (MOVE_CATEGORY_PER_MOVE && moveData[MOVE_CATEGORY]==1) || + (!MOVE_CATEGORY_PER_MOVE && !PBTypes.isSpecialType?(moveData[MOVE_TYPE])) + score -= 60 + end + end + end + #--------------------------------------------------------------------------- + when "073" + score -= 90 if target.effects[PBEffects::HyperBeam]>0 + #--------------------------------------------------------------------------- + when "074" + target.eachAlly do |b| + next if !b.near?(target) + score += 10 + end + #--------------------------------------------------------------------------- + when "075" + #--------------------------------------------------------------------------- + when "076" + #--------------------------------------------------------------------------- + when "077" + #--------------------------------------------------------------------------- + when "078" + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + #--------------------------------------------------------------------------- + when "079" + #--------------------------------------------------------------------------- + when "07A" + #--------------------------------------------------------------------------- + when "07B" + #--------------------------------------------------------------------------- + when "07C" + score -= 20 if target.status==PBStatuses::PARALYSIS # Will cure status + #--------------------------------------------------------------------------- + when "07D" + score -= 20 if target.status==PBStatuses::SLEEP && # Will cure status + target.statusCount>1 + #--------------------------------------------------------------------------- + when "07E" + #--------------------------------------------------------------------------- + when "07F" + #--------------------------------------------------------------------------- + when "080" + #--------------------------------------------------------------------------- + when "081" + attspeed = pbRoughStat(user,PBStats::SPEED,skill) + oppspeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if oppspeed>attspeed + #--------------------------------------------------------------------------- + when "082" + score += 20 if @battle.pbOpposingBattlerCount(user)>1 + #--------------------------------------------------------------------------- + when "083" + if skill>=PBTrainerAI.mediumSkill + user.eachAlly do |b| + next if !b.pbHasMove?(move.id) + score += 20 + end + end + #--------------------------------------------------------------------------- + when "084" + attspeed = pbRoughStat(user,PBStats::SPEED,skill) + oppspeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if oppspeed>attspeed + #--------------------------------------------------------------------------- + when "085" + #--------------------------------------------------------------------------- + when "086" + #--------------------------------------------------------------------------- + when "087" + #--------------------------------------------------------------------------- + when "088" + #--------------------------------------------------------------------------- + when "089" + #--------------------------------------------------------------------------- + when "08A" + #--------------------------------------------------------------------------- + when "08B" + #--------------------------------------------------------------------------- + when "08C" + #--------------------------------------------------------------------------- + when "08D" + #--------------------------------------------------------------------------- + when "08E" + #--------------------------------------------------------------------------- + when "08F" + #--------------------------------------------------------------------------- + when "090" + #--------------------------------------------------------------------------- + when "091" + #--------------------------------------------------------------------------- + when "092" + #--------------------------------------------------------------------------- + when "093" + score += 25 if user.effects[PBEffects::Rage] + #--------------------------------------------------------------------------- + when "094" + #--------------------------------------------------------------------------- + when "095" + #--------------------------------------------------------------------------- + when "096" + score -= 90 if !pbIsBerry?(user.item) || !user.itemActive? + #--------------------------------------------------------------------------- + when "097" + #--------------------------------------------------------------------------- + when "098" + #--------------------------------------------------------------------------- + when "099" + #--------------------------------------------------------------------------- + when "09A" + #--------------------------------------------------------------------------- + when "09B" + #--------------------------------------------------------------------------- + when "09C" + hasAlly = false + user.eachAlly do |b| + hasAlly = true + score += 30 + break + end + score -= 90 if !hasAlly + #--------------------------------------------------------------------------- + when "09D" + score -= 90 if user.effects[PBEffects::MudSport] + #--------------------------------------------------------------------------- + when "09E" + score -= 90 if user.effects[PBEffects::WaterSport] + #--------------------------------------------------------------------------- + when "09F" + #--------------------------------------------------------------------------- + when "0A0" + #--------------------------------------------------------------------------- + when "0A1" + score -= 90 if user.pbOwnSide.effects[PBEffects::LuckyChant]>0 + #--------------------------------------------------------------------------- + when "0A2" + score -= 90 if user.pbOwnSide.effects[PBEffects::Reflect]>0 + #--------------------------------------------------------------------------- + when "0A3" + score -= 90 if user.pbOwnSide.effects[PBEffects::LightScreen]>0 + #--------------------------------------------------------------------------- + when "0A4" + #--------------------------------------------------------------------------- + when "0A5" + #--------------------------------------------------------------------------- + when "0A6" + score -= 90 if target.effects[PBEffects::Substitute]>0 + score -= 90 if user.effects[PBEffects::LockOn]>0 + #--------------------------------------------------------------------------- + when "0A7" + if target.effects[PBEffects::Foresight] + score -= 90 + elsif target.pbHasType?(:GHOST) + score += 70 + elsif target.stages[PBStats::EVASION]<=0 + score -= 60 + end + #--------------------------------------------------------------------------- + when "0A8" + if target.effects[PBEffects::MiracleEye] + score -= 90 + elsif target.pbHasType?(:DARK) + score += 70 + elsif target.stages[PBStats::EVASION]<=0 + score -= 60 + end + #--------------------------------------------------------------------------- + when "0A9" + #--------------------------------------------------------------------------- + when "0AA" + if user.effects[PBEffects::ProtectRate]>1 || + target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + if skill>=PBTrainerAI.mediumSkill + score -= user.effects[PBEffects::ProtectRate]*40 + end + score += 50 if user.turnCount==0 + score += 30 if target.effects[PBEffects::TwoTurnAttack]>0 + end + #--------------------------------------------------------------------------- + when "0AB" + #--------------------------------------------------------------------------- + when "0AC" + #--------------------------------------------------------------------------- + when "0AD" + #--------------------------------------------------------------------------- + when "0AE" + score -= 40 + if skill>=PBTrainerAI.highSkill + score -= 100 if target.lastRegularMoveUsed<=0 || + !pbGetMoveData(target.lastRegularMoveUsed,MOVE_FLAGS)[/e/] # Not copyable by Mirror Move + end + #--------------------------------------------------------------------------- + when "0AF" + #--------------------------------------------------------------------------- + when "0B0" + #--------------------------------------------------------------------------- + when "0B1" + #--------------------------------------------------------------------------- + when "0B2" + #--------------------------------------------------------------------------- + when "0B3" + #--------------------------------------------------------------------------- + when "0B4" + if user.asleep? + score += 100 # Because it can only be used while asleep + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "0B5" + #--------------------------------------------------------------------------- + when "0B6" + #--------------------------------------------------------------------------- + when "0B7" + score -= 90 if target.effects[PBEffects::Torment] + #--------------------------------------------------------------------------- + when "0B8" + score -= 90 if user.effects[PBEffects::Imprison] + #--------------------------------------------------------------------------- + when "0B9" + score -= 90 if target.effects[PBEffects::Disable]>0 + #--------------------------------------------------------------------------- + when "0BA" + score -= 90 if target.effects[PBEffects::Taunt]>0 + #--------------------------------------------------------------------------- + when "0BB" + score -= 90 if target.effects[PBEffects::HealBlock]>0 + #--------------------------------------------------------------------------- + when "0BC" + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if target.effects[PBEffects::Encore]>0 + score -= 90 + elsif aspeed>ospeed + if target.lastMoveUsed<=0 + score -= 90 + else + moveData = pbGetMoveData(target.lastRegularMoveUsed) + if moveData[MOVE_CATEGORY]==2 && # Status move + (moveData[MOVE_TARGET]==PBTargets::User || + moveData[MOVE_TARGET]==PBTargets::BothSides) + score += 60 + elsif moveData[MOVE_CATEGORY]!=2 && # Damaging move + moveData[MOVE_TARGET]==PBTargets::NearOther && + PBTypes.ineffective?(pbCalcTypeMod(moveData[MOVE_TYPE],target,user)) + score += 60 + end + end + end + #--------------------------------------------------------------------------- + when "0BD" + #--------------------------------------------------------------------------- + when "0BF" + #--------------------------------------------------------------------------- + when "0C0" + #--------------------------------------------------------------------------- + when "0C1" + #--------------------------------------------------------------------------- + when "0C2" + #--------------------------------------------------------------------------- + when "0C3" + #--------------------------------------------------------------------------- + when "0C4" + #--------------------------------------------------------------------------- + when "0C7" + score += 20 if user.effects[PBEffects::FocusEnergy]>0 + if skill>=PBTrainerAI.highSkill + score += 20 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + #--------------------------------------------------------------------------- + when "0C9" + #--------------------------------------------------------------------------- + when "0CA" + #--------------------------------------------------------------------------- + when "0CB" + #--------------------------------------------------------------------------- + when "0CC" + #--------------------------------------------------------------------------- + when "0CD" + #--------------------------------------------------------------------------- + when "0CE" + #--------------------------------------------------------------------------- + when "0CF" + score += 40 if target.effects[PBEffects::Trapping]==0 + #--------------------------------------------------------------------------- + when "0D0" + score += 40 if target.effects[PBEffects::Trapping]==0 + #--------------------------------------------------------------------------- + when "0D1" + #--------------------------------------------------------------------------- + when "0D2" + #--------------------------------------------------------------------------- + when "0D3" + #--------------------------------------------------------------------------- + when "0D4" + if user.hp<=user.totalhp/4 + score -= 90 + elsif user.hp<=user.totalhp/2 + score -= 50 + end + #--------------------------------------------------------------------------- + when "0D5", "0D6" + if user.hp==user.totalhp || (skill>=PBTrainerAI.mediumSkill && !user.canHeal?) + score -= 90 + else + score += 50 + score -= user.hp*100/user.totalhp + end + #--------------------------------------------------------------------------- + when "0D7" + score -= 90 if @battle.positions[user.index].effects[PBEffects::Wish]>0 + #--------------------------------------------------------------------------- + when "0D8" + if user.hp==user.totalhp || (skill>=PBTrainerAI.mediumSkill && !user.canHeal?) + score -= 90 + else + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + score += 30 + when PBWeather::None + else + score -= 30 + end + score += 50 + score -= user.hp*100/user.totalhp + end + #--------------------------------------------------------------------------- + when "0D9" + if user.hp==user.totalhp || !user.pbCanSleep?(user,false,nil,true) + score -= 90 + else + score += 70 + score -= user.hp*140/user.totalhp + score += 30 if user.status!=0 + end + #--------------------------------------------------------------------------- + when "0DA" + score -= 90 if user.effects[PBEffects::AquaRing] + #--------------------------------------------------------------------------- + when "0DB" + score -= 90 if user.effects[PBEffects::Ingrain] + #--------------------------------------------------------------------------- + when "0DC" + if target.effects[PBEffects::LeechSeed]>=0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill && target.pbHasType?(:GRASS) + score -= 90 + else + score += 60 if user.turnCount==0 + end + #--------------------------------------------------------------------------- + when "0DD" + if skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:LIQUIDOOZE) + score -= 70 + else + score += 20 if user.hp<=user.totalhp/2 + end + #--------------------------------------------------------------------------- + when "0DE" + if !target.asleep? + score -= 100 + elsif skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:LIQUIDOOZE) + score -= 70 + else + score += 20 if user.hp<=user.totalhp/2 + end + #--------------------------------------------------------------------------- + when "0DF" + if user.opposes?(target) + score -= 100 + else + score += 20 if target.hp=PBTrainerAI.mediumSkill && reserves==0 && foes>0 + score -= 100 # don't want to lose + elsif skill>=PBTrainerAI.highSkill && reserves==0 && foes==0 + score += 80 # want to draw + else + score -= user.hp*100/user.totalhp + end + #--------------------------------------------------------------------------- + when "0E1" + #--------------------------------------------------------------------------- + when "0E2" + if !target.pbCanLowerStatStage?(PBStats::ATTACK,user) && + !target.pbCanLowerStatStage?(PBStats::SPATK,user) + score -= 100 + elsif @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + score -= 100 + else + score += target.stages[PBStats::ATTACK]*10 + score += target.stages[PBStats::SPATK]*10 + score -= user.hp*100/user.totalhp + end + #--------------------------------------------------------------------------- + when "0E3", "0E4" + score -= 70 + #--------------------------------------------------------------------------- + when "0E5" + if @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + score -= 90 + else + score -= 90 if target.effects[PBEffects::PerishSong]>0 + end + #--------------------------------------------------------------------------- + when "0E6" + score += 50 + score -= user.hp*100/user.totalhp + score += 30 if user.hp<=user.totalhp/10 + #--------------------------------------------------------------------------- + when "0E7" + score += 50 + score -= user.hp*100/user.totalhp + score += 30 if user.hp<=user.totalhp/10 + #--------------------------------------------------------------------------- + when "0E8" + score -= 25 if user.hp>user.totalhp/2 + if skill>=PBTrainerAI.mediumSkill + score -= 90 if user.effects[PBEffects::ProtectRate]>1 + score -= 90 if target.effects[PBEffects::HyperBeam]>0 + else + score -= user.effects[PBEffects::ProtectRate]*40 + end + #--------------------------------------------------------------------------- + when "0E9" + if target.hp==1 + score -= 90 + elsif target.hp<=target.totalhp/8 + score -= 60 + elsif target.hp<=target.totalhp/4 + score -= 30 + end + #--------------------------------------------------------------------------- + when "0EA" + score -= 100 if @battle.trainerBattle? + #--------------------------------------------------------------------------- + when "0EB" + if target.effects[PBEffects::Ingrain] || + (skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:SUCTIONCUPS)) + score -= 90 + else + ch = 0 + @battle.pbParty(target.index).each_with_index do |pkmn,i| + ch += 1 if @battle.pbCanSwitchLax?(target.index,i) + end + score -= 90 if ch==0 + end + if score>20 + score += 50 if target.pbOwnSide.effects[PBEffects::Spikes]>0 + score += 50 if target.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 + score += 50 if target.pbOwnSide.effects[PBEffects::StealthRock] + end + #--------------------------------------------------------------------------- + when "0EC" + if !target.effects[PBEffects::Ingrain] && + !(skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:SUCTIONCUPS)) + score += 40 if target.pbOwnSide.effects[PBEffects::Spikes]>0 + score += 40 if target.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 + score += 40 if target.pbOwnSide.effects[PBEffects::StealthRock] + end + #--------------------------------------------------------------------------- + when "0ED" + if !@battle.pbCanChooseNonActive?(user.index) + score -= 80 + else + score -= 40 if user.effects[PBEffects::Confusion]>0 + total = 0 + PBStats.eachBattleStat { |s| total += user.stages[s] } + if total<=0 || user.turnCount==0 + score -= 60 + else + score += total*10 + # special case: user has no damaging moves + hasDamagingMove = false + user.eachMove do |m| + next if !m.damagingMove? + hasDamagingMove = true + break + end + score += 75 if !hasDamagingMove + end + end + #--------------------------------------------------------------------------- + when "0EE" + #--------------------------------------------------------------------------- + when "0EF" + score -= 90 if target.effects[PBEffects::MeanLook]>=0 + #--------------------------------------------------------------------------- + when "0F0" + if skill>=PBTrainerAI.highSkill + score += 20 if target.item!=0 + end + #--------------------------------------------------------------------------- + when "0F1" + if skill>=PBTrainerAI.highSkill + if user.item==0 && target.item!=0 + score += 40 + else + score -= 90 + end + else + score -= 80 + end + #--------------------------------------------------------------------------- + when "0F2" + if user.item==0 && target.item==0 + score -= 90 + elsif skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:STICKYHOLD) + score -= 90 + elsif user.hasActiveItem?([:FLAMEORB,:TOXICORB,:STICKYBARB,:IRONBALL, + :CHOICEBAND,:CHOICESCARF,:CHOICESPECS]) + score += 50 + elsif user.item==0 && target.item!=0 + score -= 30 if pbGetMoveData(user.lastMoveUsed,MOVE_FUNCTION_CODE)=="0F2" # Trick/Switcheroo + end + #--------------------------------------------------------------------------- + when "0F3" + if user.item==0 || target.item!=0 + score -= 90 + else + if user.hasActiveItem?([:FLAMEORB,:TOXICORB,:STICKYBARB,:IRONBALL, + :CHOICEBAND,:CHOICESCARF,:CHOICESPECS]) + score += 50 + else + score -= 80 + end + end + #--------------------------------------------------------------------------- + when "0F4", "0F5" + if target.effects[PBEffects::Substitute]==0 + if skill>=PBTrainerAI.highSkill && pbIsBerry?(target.item) + score += 30 + end + end + #--------------------------------------------------------------------------- + when "0F6" + if user.recycleItem==0 || user.item!=0 + score -= 80 + elsif user.recycleItem!=0 + score += 30 + end + #--------------------------------------------------------------------------- + when "0F7" + if user.item==0 || !user.itemActive? || + user.unlosableItem?(user.item) || pbIsPokeBall?(user.item) + score -= 90 + end + #--------------------------------------------------------------------------- + when "0F8" + score -= 90 if target.effects[PBEffects::Embargo]>0 + #--------------------------------------------------------------------------- + when "0F9" + if @battle.field.effects[PBEffects::MagicRoom]>0 + score -= 90 + else + score += 30 if user.item==0 && target.item!=0 + end + #--------------------------------------------------------------------------- + when "0FA" + score -= 25 + #--------------------------------------------------------------------------- + when "0FB" + score -= 30 + #--------------------------------------------------------------------------- + when "0FC" + score -= 40 + #--------------------------------------------------------------------------- + when "0FD" + score -= 30 + if target.pbCanParalyze?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeedospeed + score -= 40 + end + end + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET]) + end + end + #--------------------------------------------------------------------------- + when "0FE" + score -= 30 + if target.pbCanBurn?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET,:FLAREBOOST]) + end + end + #--------------------------------------------------------------------------- + when "0FF" + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::Sun + score -= 90 + else + user.eachMove do |m| + next if !m.damagingMove? || !isConst?(m.type,PBTypes,:FIRE) + score += 20 + end + end + #--------------------------------------------------------------------------- + when "100" + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::Rain + score -= 90 + else + user.eachMove do |m| + next if !m.damagingMove? || !isConst?(m.type,PBTypes,:WATER) + score += 20 + end + end + #--------------------------------------------------------------------------- + when "101" + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::Sandstorm + score -= 90 + end + #--------------------------------------------------------------------------- + when "102" + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::Hail + score -= 90 + end + #--------------------------------------------------------------------------- + when "103" + if user.pbOpposingSide.effects[PBEffects::Spikes]>=3 + score -= 90 + else + canChoose = false + user.eachOpposing do |b| + next if !@battle.pbCanChooseNonActive?(b.index) + canChoose = true + break + end + if !canChoose + # Opponent can't switch in any Pokemon + score -= 90 + else + score += 10*@battle.pbAbleNonActiveCount(user.idxOpposingSide) + score += [40,26,13][user.pbOpposingSide.effects[PBEffects::Spikes]] + end + end + #--------------------------------------------------------------------------- + when "104" + if user.pbOpposingSide.effects[PBEffects::ToxicSpikes]>=2 + score -= 90 + else + canChoose = false + user.eachOpposing do |b| + next if !@battle.pbCanChooseNonActive?(b.index) + canChoose = true + break + end + if !canChoose + # Opponent can't switch in any Pokemon + score -= 90 + else + score += 8*@battle.pbAbleNonActiveCount(user.idxOpposingSide) + score += [26,13][user.pbOpposingSide.effects[PBEffects::ToxicSpikes]] + end + end + #--------------------------------------------------------------------------- + when "105" + if user.pbOpposingSide.effects[PBEffects::StealthRock] + score -= 90 + else + canChoose = false + user.eachOpposing do |b| + next if !@battle.pbCanChooseNonActive?(b.index) + canChoose = true + break + end + if !canChoose + # Opponent can't switch in any Pokemon + score -= 90 + else + score += 10*@battle.pbAbleNonActiveCount(user.idxOpposingSide) + end + end + #--------------------------------------------------------------------------- + when "106" + #--------------------------------------------------------------------------- + when "107" + #--------------------------------------------------------------------------- + when "108" + #--------------------------------------------------------------------------- + when "109" + #--------------------------------------------------------------------------- + when "10A" + score += 20 if user.pbOpposingSide.effects[PBEffects::AuroraVeil]>0 + score += 20 if user.pbOpposingSide.effects[PBEffects::Reflect]>0 + score += 20 if user.pbOpposingSide.effects[PBEffects::LightScreen]>0 + #--------------------------------------------------------------------------- + when "10B" + score += 10*(user.stages[PBStats::ACCURACY]-target.stages[PBStats::EVASION]) + #--------------------------------------------------------------------------- + when "10C" + if user.effects[PBEffects::Substitute]>0 + score -= 90 + elsif user.hp<=user.totalhp/4 + score -= 90 + end + #--------------------------------------------------------------------------- + when "10D" + if user.pbHasType?(:GHOST) + if target.effects[PBEffects::Curse] + score -= 90 + elsif user.hp<=user.totalhp/2 + if @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + score -= 90 + else + score -= 50 + score -= 30 if @battle.switchStyle + end + end + else + avg = user.stages[PBStats::SPEED]*10 + avg -= user.stages[PBStats::ATTACK]*10 + avg -= user.stages[PBStats::DEFENSE]*10 + score += avg/3 + end + #--------------------------------------------------------------------------- + when "10E" + score -= 40 + #--------------------------------------------------------------------------- + when "10F" + if target.effects[PBEffects::Nightmare] || + target.effects[PBEffects::Substitute]>0 + score -= 90 + elsif !target.asleep? + score -= 90 + else + score -= 90 if target.statusCount<=1 + score += 50 if target.statusCount>3 + end + #--------------------------------------------------------------------------- + when "110" + score += 30 if user.effects[PBEffects::Trapping]>0 + score += 30 if user.effects[PBEffects::LeechSeed]>=0 + if @battle.pbAbleNonActiveCount(user.idxOwnSide)>0 + score += 80 if user.pbOwnSide.effects[PBEffects::Spikes]>0 + score += 80 if user.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 + score += 80 if user.pbOwnSide.effects[PBEffects::StealthRock] + end + #--------------------------------------------------------------------------- + when "111" + if @battle.positions[target.index].effects[PBEffects::FutureSightCounter]>0 + score -= 100 + elsif @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + # Future Sight tends to be wasteful if down to last Pokemon + score -= 70 + end + #--------------------------------------------------------------------------- + when "112" + avg = 0 + avg -= user.stages[PBStats::DEFENSE]*10 + avg -= user.stages[PBStats::SPDEF]*10 + score += avg/2 + if user.effects[PBEffects::Stockpile]>=3 + score -= 80 + else + # More preferable if user also has Spit Up/Swallow + score += 20 if user.pbHasMoveFunction?("113","114") # Spit Up, Swallow + end + #--------------------------------------------------------------------------- + when "113" + score -= 100 if user.effects[PBEffects::Stockpile]==0 + #--------------------------------------------------------------------------- + when "114" + if user.effects[PBEffects::Stockpile]==0 + score -= 90 + elsif user.hp==user.totalhp + score -= 90 + else + mult = [0,25,50,100][user.effects[PBEffects::Stockpile]] + score += mult + score -= user.hp*mult*2/user.totalhp + end + #--------------------------------------------------------------------------- + when "115" + score += 50 if target.effects[PBEffects::HyperBeam]>0 + score -= 35 if target.hp<=target.totalhp/2 # If target is weak, no + score -= 70 if target.hp<=target.totalhp/4 # need to risk this move + #--------------------------------------------------------------------------- + when "116" + #--------------------------------------------------------------------------- + when "117" + hasAlly = false + user.eachAlly do |b| + hasAlly = true + break + end + score -= 90 if !hasAlly + #--------------------------------------------------------------------------- + when "118" + if @battle.field.effects[PBEffects::Gravity]>0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + score -= 30 + score -= 20 if user.effects[PBEffects::SkyDrop]>=0 + score -= 20 if user.effects[PBEffects::MagnetRise]>0 + score -= 20 if user.effects[PBEffects::Telekinesis]>0 + score -= 20 if user.pbHasType?(:FLYING) + score -= 20 if user.hasActiveAbility?(:LEVITATE) + score -= 20 if user.hasActiveItem?(:AIRBALLOON) + score += 20 if target.effects[PBEffects::SkyDrop]>=0 + score += 20 if target.effects[PBEffects::MagnetRise]>0 + score += 20 if target.effects[PBEffects::Telekinesis]>0 + score += 20 if target.inTwoTurnAttack?("0C9","0CC","0CE") # Fly, Bounce, Sky Drop + score += 20 if target.pbHasType?(:FLYING) + score += 20 if target.hasActiveAbility?(:LEVITATE) + score += 20 if target.hasActiveItem?(:AIRBALLOON) + end + #--------------------------------------------------------------------------- + when "119" + if user.effects[PBEffects::MagnetRise]>0 || + user.effects[PBEffects::Ingrain] || + user.effects[PBEffects::SmackDown] + score -= 90 + end + #--------------------------------------------------------------------------- + when "11A" + if target.effects[PBEffects::Telekinesis]>0 || + target.effects[PBEffects::Ingrain] || + target.effects[PBEffects::SmackDown] + score -= 90 + end + #--------------------------------------------------------------------------- + when "11B" + #--------------------------------------------------------------------------- + when "11C" + if skill>=PBTrainerAI.mediumSkill + score += 20 if target.effects[PBEffects::MagnetRise]>0 + score += 20 if target.effects[PBEffects::Telekinesis]>0 + score += 20 if target.inTwoTurnAttack?("0C9","0CC") # Fly, Bounce + score += 20 if target.pbHasType?(:FLYING) + score += 20 if target.hasActiveAbility?(:LEVITATE) + score += 20 if target.hasActiveItem?(:AIRBALLOON) + end + #--------------------------------------------------------------------------- + when "11D" + #--------------------------------------------------------------------------- + when "11E" + #--------------------------------------------------------------------------- + when "11F" + #--------------------------------------------------------------------------- + when "120" + #--------------------------------------------------------------------------- + when "121" + #--------------------------------------------------------------------------- + when "122" + #--------------------------------------------------------------------------- + when "123" + if !target.pbHasType?(user.type1) && + !target.pbHasType?(user.type2) + score -= 90 + end + #--------------------------------------------------------------------------- + when "124" + #--------------------------------------------------------------------------- + when "125" + #--------------------------------------------------------------------------- + when "126" + score += 20 # Shadow moves are more preferable + #--------------------------------------------------------------------------- + when "127" + score += 20 # Shadow moves are more preferable + if target.pbCanParalyze?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeedospeed + score -= 40 + end + end + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET]) + end + end + #--------------------------------------------------------------------------- + when "128" + score += 20 # Shadow moves are more preferable + if target.pbCanBurn?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET,:FLAREBOOST]) + end + end + #--------------------------------------------------------------------------- + when "129" + score += 20 # Shadow moves are more preferable + if target.pbCanFreeze?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 20 if target.hasActiveAbility?(:MARVELSCALE) + end + end + #--------------------------------------------------------------------------- + when "12A" + score += 20 # Shadow moves are more preferable + if target.pbCanConfuse?(user,false) + score += 30 + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "12B" + score += 20 # Shadow moves are more preferable + if !target.pbCanLowerStatStage?(PBStats::DEFENSE,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::DEFENSE]*20 + end + #--------------------------------------------------------------------------- + when "12C" + score += 20 # Shadow moves are more preferable + if !target.pbCanLowerStatStage?(PBStats::EVASION,user) + score -= 90 + else + score += target.stages[PBStats::EVASION]*15 + end + #--------------------------------------------------------------------------- + when "12D" + score += 20 # Shadow moves are more preferable + #--------------------------------------------------------------------------- + when "12E" + score += 20 # Shadow moves are more preferable + score += 20 if target.hp>=target.totalhp/2 + score -= 20 if user.hp=0 + #--------------------------------------------------------------------------- + when "130" + score += 20 # Shadow moves are more preferable + score -= 40 + #--------------------------------------------------------------------------- + when "131" + score += 20 # Shadow moves are more preferable + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::ShadowSky + score -= 90 + end + #--------------------------------------------------------------------------- + when "132" + score += 20 # Shadow moves are more preferable + if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 || + target.pbOwnSide.effects[PBEffects::Reflect]>0 || + target.pbOwnSide.effects[PBEffects::LightScreen]>0 || + target.pbOwnSide.effects[PBEffects::Safeguard]>0 + score += 30 + score -= 90 if user.pbOwnSide.effects[PBEffects::AuroraVeil]>0 || + user.pbOwnSide.effects[PBEffects::Reflect]>0 || + user.pbOwnSide.effects[PBEffects::LightScreen]>0 || + user.pbOwnSide.effects[PBEffects::Safeguard]>0 + else + score -= 110 + end + #--------------------------------------------------------------------------- + when "133", "134" + score -= 95 + score = 0 if skill>=PBTrainerAI.highSkill + #--------------------------------------------------------------------------- + when "135" + if target.pbCanFreeze?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 20 if target.hasActiveAbility?(:MARVELSCALE) + end + end + #--------------------------------------------------------------------------- + when "136" + score += 20 if user.stages[PBStats::DEFENSE]<0 + #--------------------------------------------------------------------------- + when "137" + hasEffect = user.statStageAtMax?(PBStats::DEFENSE) && + user.statStageAtMax?(PBStats::SPDEF) + user.eachAlly do |b| + next if b.statStageAtMax?(PBStats::DEFENSE) && b.statStageAtMax?(PBStats::SPDEF) + hasEffect = true + score -= b.stages[PBStats::DEFENSE]*10 + score -= b.stages[PBStats::SPDEF]*10 + end + if hasEffect + score -= user.stages[PBStats::DEFENSE]*10 + score -= user.stages[PBStats::SPDEF]*10 + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "138" + if target.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score -= target.stages[PBStats::SPDEF]*10 + end + #--------------------------------------------------------------------------- + when "139" + if !target.pbCanLowerStatStage?(PBStats::ATTACK,user) + score -= 90 + else + score += target.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "13A" + avg = target.stages[PBStats::ATTACK]*10 + avg += target.stages[PBStats::SPATK]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "13B" + if !isConst?(user.species,PBSpecies,:HOOPA) || user.form!=1 + score -= 100 + else + score += 20 if target.stages[PBStats::DEFENSE]>0 + end + #--------------------------------------------------------------------------- + when "13C" + score += 20 if target.stages[PBStats::SPATK]>0 + #--------------------------------------------------------------------------- + when "13D" + if !target.pbCanLowerStatStage?(PBStats::SPATK,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::SPATK]*20 + end + #--------------------------------------------------------------------------- + when "13E" + count = 0 + @battle.eachBattler do |b| + if b.pbHasType?(:GRASS) && !b.airborne? && + (!b.statStageAtMax?(PBStats::ATTACK) || !b.statStageAtMax?(PBStats::SPATK)) + count += 1 + if user.opposes?(b) + score -= 20 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPATK]*10 + end + end + end + score -= 95 if count==0 + #--------------------------------------------------------------------------- + when "13F" + count = 0 + @battle.eachBattler do |b| + if b.pbHasType?(:GRASS) && !b.statStageAtMax?(PBStats::DEFENSE) + count += 1 + if user.opposes?(b) + score -= 20 + else + score -= user.stages[PBStats::DEFENSE]*10 + end + end + end + score -= 95 if count==0 + #--------------------------------------------------------------------------- + when "140" + count=0 + @battle.eachBattler do |b| + if b.poisoned? && + (!b.statStageAtMin?(PBStats::ATTACK) || + !b.statStageAtMin?(PBStats::SPATK) || + !b.statStageAtMin?(PBStats::SPEED)) + count += 1 + if user.opposes?(b) + score += user.stages[PBStats::ATTACK]*10 + score += user.stages[PBStats::SPATK]*10 + score += user.stages[PBStats::SPEED]*10 + else + score -= 20 + end + end + end + score -= 95 if count==0 + #--------------------------------------------------------------------------- + when "141" + if target.effects[PBEffects::Substitute]>0 + score -= 90 + else + numpos = 0; numneg = 0 + PBStats.eachBattleStat do |s| + numpos += target.stages[s] if target.stages[s]>0 + numneg += target.stages[s] if target.stages[s]<0 + end + if numpos!=0 || numneg!=0 + score += (numpos-numneg)*10 + else + score -= 95 + end + end + #--------------------------------------------------------------------------- + when "142" + score -= 90 if target.pbHasType?(:GHOST) + #--------------------------------------------------------------------------- + when "143" + score -= 90 if target.pbHasType?(:GRASS) + #--------------------------------------------------------------------------- + when "144" + #--------------------------------------------------------------------------- + when "145" + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score -= 90 if aspeed>ospeed + #--------------------------------------------------------------------------- + when "146" + #--------------------------------------------------------------------------- + when "147" + #--------------------------------------------------------------------------- + when "148" + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeed>ospeed + score -= 90 + else + score += 30 if target.pbHasMoveType?(:FIRE) + end + #--------------------------------------------------------------------------- + when "149" + if user.turnCount==0 + score += 30 + else + score -= 90 # Because it will fail here + score = 0 if skill>=PBTrainerAI.bestSkill + end + #--------------------------------------------------------------------------- + when "14A" + #--------------------------------------------------------------------------- + when "14B", "14C" + if user.effects[PBEffects::ProtectRate]>1 || + target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + if skill>=PBTrainerAI.mediumSkill + score -= user.effects[PBEffects::ProtectRate]*40 + end + score += 50 if user.turnCount==0 + score += 30 if target.effects[PBEffects::TwoTurnAttack]>0 + end + #--------------------------------------------------------------------------- + when "14D" + #--------------------------------------------------------------------------- + when "14E" + if user.statStageAtMax?(PBStats::SPATK) && + user.statStageAtMax?(PBStats::SPDEF) && + user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score -= user.stages[PBStats::SPATK]*10 # Only *10 isntead of *20 + score -= user.stages[PBStats::SPDEF]*10 # because two-turn attack + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.mediumSkill + hasSpecialAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecialAttack = true + break + end + if hasSpecialAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + #--------------------------------------------------------------------------- + when "14F" + if skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:LIQUIDOOZE) + score -= 80 + else + score += 40 if user.hp<=user.totalhp/2 + end + #--------------------------------------------------------------------------- + when "150" + score += 20 if !user.statStageAtMax?(PBStats::ATTACK) && target.hp<=target.totalhp/4 + #--------------------------------------------------------------------------- + when "151" + avg = target.stages[PBStats::ATTACK]*10 + avg += target.stages[PBStats::SPATK]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "152" + #--------------------------------------------------------------------------- + when "153" + score -= 95 if target.pbOwnSide.effects[PBEffects::StickyWeb] + #--------------------------------------------------------------------------- + when "154" + #--------------------------------------------------------------------------- + when "155" + #--------------------------------------------------------------------------- + when "156" + #--------------------------------------------------------------------------- + when "157" + score -= 90 + #--------------------------------------------------------------------------- + when "158" + score -= 90 if !user.belched? + #--------------------------------------------------------------------------- + when "159" + if !target.pbCanPoison?(user,false) && !target.pbCanLowerStatStage?(PBStats::SPEED,user) + score -= 90 + else + if target.pbCanPoison?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + score += 30 if target.hp<=target.totalhp/4 + score += 50 if target.hp<=target.totalhp/8 + score -= 40 if target.effects[PBEffects::Yawn]>0 + end + if skill>=PBTrainerAI.highSkill + score += 10 if pbRoughStat(target,PBStats::DEFENSE,skill)>100 + score += 10 if pbRoughStat(target,PBStats::SPDEF,skill)>100 + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:TOXICBOOST]) + end + end + if target.pbCanLowerStatStage?(PBStats::SPEED,user) + score += target.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + end + #--------------------------------------------------------------------------- + when "15A" + if target.opposes?(user) + score -= 40 if target.status==PBStatuses::BURN + else + score += 40 if target.status==PBStatuses::BURN + end + #--------------------------------------------------------------------------- + when "15B" + if target.status==PBStatuses::NONE + score -= 90 + elsif user.hp==user.totalhp && target.opposes?(user) + score -= 90 + else + score += (user.totalhp-user.hp)*50/user.totalhp + score -= 30 if target.opposes?(user) + end + #--------------------------------------------------------------------------- + when "15C" + hasEffect = user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::SPATK) + user.eachAlly do |b| + next if b.statStageAtMax?(PBStats::ATTACK) && b.statStageAtMax?(PBStats::SPATK) + hasEffect = true + score -= b.stages[PBStats::ATTACK]*10 + score -= b.stages[PBStats::SPATK]*10 + end + if hasEffect + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPATK]*10 + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "15D" + numStages = 0 + PBStats.eachBattleStat do |s| + next if target.stages[s]<=0 + numStages += target.stages[s] + end + score += numStages*20 + #--------------------------------------------------------------------------- + when "15E" + if user.effects[PBEffects::LaserFocus]>0 + score -= 90 + else + score += 40 + end + #--------------------------------------------------------------------------- + when "15F" + score += user.stages[PBStats::DEFENSE]*10 + #--------------------------------------------------------------------------- + when "160" + if target.statStageAtMin?(PBStats::ATTACK) + score -= 90 + else + if target.pbCanLowerStatStage?(PBStats::ATTACK,user) + score += target.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + score += (user.totalhp-user.hp)*50/user.totalhp + end + #--------------------------------------------------------------------------- + when "161" + if skill>=PBTrainerAI.mediumSkill + if user.speed>target.speed + score += 50 + else + score -= 70 + end + end + #--------------------------------------------------------------------------- + when "162" + score -= 90 if !user.pbHasType?(:FIRE) + #--------------------------------------------------------------------------- + when "163" + #--------------------------------------------------------------------------- + when "164" + #--------------------------------------------------------------------------- + when "165" + if skill>=PBTrainerAI.mediumSkill + userSpeed = pbRoughStat(user,PBStats::SPEED,skill) + targetSpeed = pbRoughStat(target,PBStats::SPEED,skill) + if userSpeed0 || @battle.pbWeather!=PBWeather::Hail + score -= 90 + else + score += 40 + end + #--------------------------------------------------------------------------- + when "168" + if user.effects[PBEffects::ProtectRate]>1 || + target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + if skill>=PBTrainerAI.mediumSkill + score -= user.effects[PBEffects::ProtectRate]*40 + end + score += 50 if user.turnCount==0 + score += 30 if target.effects[PBEffects::TwoTurnAttack]>0 + score += 20 # Because of possible poisoning + end + #--------------------------------------------------------------------------- + when "169" + #--------------------------------------------------------------------------- + when "16A" + hasAlly = false + target.eachAlly do |b| + hasAlly = true + break + end + score -= 90 if !hasAlly + #--------------------------------------------------------------------------- + when "16B" + if skill>=PBTrainerAI.mediumSkill + if target.lastRegularMoveUsed<0 || + !target.pbHasMove?(target.lastRegularMoveUsed) || + target.usingMultiTurnAttack? + score -= 90 + else + # Without lots of code here to determine good/bad moves and relative + # speeds, using this move is likely to just be a waste of a turn + score -= 50 + end + end + #--------------------------------------------------------------------------- + when "16C" + if target.effects[PBEffects::ThroatChop]==0 && skill>=PBTrainerAI.highSkill + hasSoundMove = false + user.eachMove do |m| + next if !m.soundMove? + hasSoundMove = true + break + end + score += 40 if hasSoundMove + end + #--------------------------------------------------------------------------- + when "16D" + if user.hp==user.totalhp || (skill>=PBTrainerAI.mediumSkill && !user.canHeal?) + score -= 90 + else + score += 50 + score -= user.hp*100/user.totalhp + score += 30 if @battle.pbWeather==PBWeather::Sandstorm + end + #--------------------------------------------------------------------------- + when "16E" + if user.hp==user.totalhp || (skill>=PBTrainerAI.mediumSkill && !user.canHeal?) + score -= 90 + else + score += 50 + score -= user.hp*100/user.totalhp + if skill>=PBTrainerAI.mediumSkill + score += 30 if @battle.field.terrain==PBBattleTerrains::Grassy + end + end + #--------------------------------------------------------------------------- + when "16F" + if !target.opposes?(user) + if target.hp==target.totalhp || (skill>=PBTrainerAI.mediumSkill && !target.canHeal?) + score -= 90 + else + score += 50 + score -= target.hp*100/target.totalhp + end + end + #--------------------------------------------------------------------------- + when "170" + reserves = @battle.pbAbleNonActiveCount(user.idxOwnSide) + foes = @battle.pbAbleNonActiveCount(user.idxOpposingSide) + if @battle.pbCheckGlobalAbility(:DAMP) + score -= 100 + elsif skill>=PBTrainerAI.mediumSkill && reserves==0 && foes>0 + score -= 100 # don't want to lose + elsif skill>=PBTrainerAI.highSkill && reserves==0 && foes==0 + score += 80 # want to draw + else + score -= (user.total.hp-user.hp)*75/user.totalhp + end + #--------------------------------------------------------------------------- + when "171" + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score -= 80 if !hasPhysicalAttack + end + #--------------------------------------------------------------------------- + when "172" + score += 20 # Because of possible burning + #--------------------------------------------------------------------------- + when "173" + #--------------------------------------------------------------------------- + when "174" + score -= 90 if user.turnCount>0 || user.lastRoundMoved>=0 + #--------------------------------------------------------------------------- + when "175" + score += 30 if target.effects[PBEffects::Minimize] + #--------------------------------------------------------------------------- + end + return score + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb b/Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb new file mode 100644 index 000000000..8f0b8ad8f --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb @@ -0,0 +1,675 @@ +class PokeBattle_AI + #============================================================================= + # + #============================================================================= + def pbTargetsMultiple?(move,user) + numTargets = 0 + case move.pbTarget(user) + when PBTargets::AllNearFoes + @battle.eachOtherSideBattler(user) { |b| numTargets += 1 if b.near?(user) } + return numTargets>1 + when PBTargets::AllNearOthers + @battle.eachBattler { |b| numTargets += 1 if b.near?(user) } + return numTargets>1 + when PBTargets::UserAndAllies + @battle.eachSameSideBattler(user) { |b| numTargets += 1 } + return numTargets>1 + when PBTargets::AllFoes + @battle.eachOtherSideBattler(user) { |b| numTargets += 1 } + return numTargets>1 + when PBTargets::AllBattlers + @battle.eachBattler { |b| numTargets += 1 } + return numTargets>1 + end + return false + end + + #============================================================================= + # Move's type effectiveness + #============================================================================= + def pbCalcTypeModSingle(moveType,defType,user,target) + ret = PBTypes.getEffectiveness(moveType,defType) + # Ring Target + if target.hasActiveItem?(:RINGTARGET) + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if PBTypes.ineffective?(moveType,defType) + end + # Foresight + if user.hasActiveAbility?(:SCRAPPY) || target.effects[PBEffects::Foresight] + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:GHOST) && + PBTypes.ineffective?(moveType,defType) + end + # Miracle Eye + if target.effects[PBEffects::MiracleEye] + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:DARK) && + PBTypes.ineffective?(moveType,defType) + end + # Delta Stream's weather + if @battle.pbWeather==PBWeather::StrongWinds + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) && + PBTypes.superEffective?(moveType,defType) + end + # Grounded Flying-type Pokémon become susceptible to Ground moves + if !target.airborne? + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) && + isConst?(moveType,PBTypes,:GROUND) + end + return ret + end + + def pbCalcTypeMod(moveType,user,target) + return PBTypeEffectiveness::NORMAL_EFFECTIVE if moveType<0 + return PBTypeEffectiveness::NORMAL_EFFECTIVE if isConst?(moveType,PBTypes,:GROUND) && + target.pbHasType?(:FLYING) && target.hasActiveItem?(:IRONBALL) + # Determine types + tTypes = target.pbTypes(true) + # Get effectivenesses + typeMods = [PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE] * 3 # 3 types max + tTypes.each_with_index do |type,i| + typeMods[i] = pbCalcTypeModSingle(moveType,type,user,target) + end + # Multiply all effectivenesses together + ret = 1 + typeMods.each { |m| ret *= m } + return ret + end + + # For switching. Determines the effectiveness of a potential switch-in against + # an opposing battler. + def pbCalcTypeModPokemon(battlerThis,battlerOther) + mod1 = PBTypes.getCombinedEffectiveness(battlerThis.type1,target.type1,target.type2) + mod2 = PBTypeEffectiveness::NORMAL_EFFECTIVE + if battlerThis.type1!=battlerThis.type2 + mod2 = PBTypes.getCombinedEffectiveness(battlerThis.type2,target.type1,target.type2) + end + return mod1*mod2 # Normal effectiveness is 64 here + end + + #============================================================================= + # Immunity to a move because of the target's ability, item or other effects + #============================================================================= + def pbCheckMoveImmunity(score,move,user,target,skill) + type = pbRoughType(move,user,skill) + typeMod = pbCalcTypeMod(type,user,target) + # Type effectiveness + return true if PBTypes.ineffective?(typeMod) || score<=0 + # Immunity due to ability/item/other effects + if skill>=PBTrainerAI.mediumSkill + if isConst?(move.type,PBTypes,:GROUND) + return true if target.airborne? && !move.hitsFlyingTargets? + elsif isConst?(move.type,PBTypes,:FIRE) + return true if target.hasActiveAbility?(:FLASHFIRE) + elsif isConst?(move.type,PBTypes,:WATER) + return true if target.hasActiveAbility?([:DRYSKIN,:STORMDRAIN,:WATERABSORB]) + elsif isConst?(move.type,PBTypes,:GRASS) + return true if target.hasActiveAbility?(:SAPSIPPER) + elsif isConst?(move.type,PBTypes,:ELECTRIC) + return true if target.hasActiveAbility?([:LIGHTNINGROD,:MOTORDRIVE,:VOLTABSORB]) + end + return true if PBTypes.notVeryEffective?(typeMod) && + target.hasActiveAbility?(:WONDERGUARD) + return true if move.damagingMove? && user.index!=target.index && !target.opposes?(user) && + target.hasActiveAbility?(:TELEPATHY) + return true if move.canMagicCoat? && target.hasActiveAbility?(:MAGICBOUNCE) && + target.opposes?(user) + return true if move.soundMove? && target.hasActiveAbility?(:SOUNDPROOF) + return true if move.bombMove? && target.hasActiveAbility?(:BULLETPROOF) + if move.powderMove? + return true if target.pbHasType?(:GRASS) + return true if target.hasActiveAbility?(:OVERCOAT) + return true if target.hasActiveItem?(:SAFETYGOGGLES) + end + return true if target.effects[PBEffects::Substitute]>0 && move.statusMove? && + !move.ignoresSubstitute?(user) && user.index!=target.index + return true if NEWEST_BATTLE_MECHANICS && user.hasActiveAbility?(:PRANKSTER) && + target.pbHasType?(:DARK) && target.opposes?(user) + return true if move.priority>0 && @battle.field.terrain==PBBattleTerrains::Psychic && + target.affectedByTerrain? && target.opposes?(user) + end + return false + end + + #============================================================================= + # Get approximate properties for a battler + #============================================================================= + def pbRoughType(move,user,skill) + ret = move.type + if skill>=PBTrainerAI.highSkill + ret = move.pbCalcType(user) + end + return ret + end + + def pbRoughStat(battler,stat,skill) + return battler.pbSpeed if skill>=PBTrainerAI.highSkill && stat==PBStats::SPEED + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + stage = battler.stages[stat]+6 + value = 0 + case stat + when PBStats::ATTACK; value = battler.attack + when PBStats::DEFENSE; value = battler.defense + when PBStats::SPATK; value = battler.spatk + when PBStats::SPDEF; value = battler.spdef + when PBStats::SPEED; value = battler.speed + end + return (value.to_f*stageMul[stage]/stageDiv[stage]).floor + end + + #============================================================================= + # Get a better move's base damage value + #============================================================================= + def pbMoveBaseDamage(move,user,target,skill) + baseDmg = move.baseDamage + baseDmg = 60 if baseDmg==1 + return baseDmg if skill=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize] + # Sonic Boom, Dragon Rage, Super Fang, Night Shade, Endeavor + when "06A", "06B", "06C", "06D", "06E" + baseDmg = move.pbFixedDamage(user,target) + when "06F" # Psywave + baseDmg = user.level + when "070" # OHKO + baseDmg = 200 + when "071", "072", "073" # Counter, Mirror Coat, Metal Burst + baseDmg = 60 + when "075", "076", "0D0", "12D" # Surf, Earthquake, Whirlpool, Shadow Storm + baseDmg = move.pbModifyDamage(baseDmg,user,target) + # Gust, Twister, Venoshock, Smelling Salts, Wake-Up Slap, Facade, Hex, Brine, + # Retaliate, Weather Ball, Return, Frustration, Eruption, Crush Grip, + # Stored Power, Punishment, Hidden Power, Fury Cutter, Echoed Voice, + # Trump Card, Flail, Electro Ball, Low Kick, Fling, Spit Up + when "077", "078", "07B", "07C", "07D", "07E", "07F", "080", "085", "087", + "089", "08A", "08B", "08C", "08E", "08F", "090", "091", "092", "097", + "098", "099", "09A", "0F7", "113" + baseDmg = move.pbBaseDamage(baseDmg,user,target) + when "086" # Acrobatics + baseDmg *= 2 if user.item==0 || user.hasActiveItem?(:FLYINGGEM) + when "08D" # Gyro Ball + targetSpeed = pbRoughStat(target,PBStats::SPEED,skill) + userSpeed = pbRoughStat(user,PBStats::SPEED,skill) + baseDmg = [[(25*targetSpeed/userSpeed).floor,150].min,1].max + when "094" # Present + baseDmg = 50 + when "095" # Magnitude + baseDmg = 71 + baseDmg *= 2 if target.inTwoTurnAttack?("0CA") # Dig + when "096" # Natural Gift + baseDmg = move.pbNaturalGiftBaseDamage(user.item) + when "09B" # Heavy Slam + baseDmg = move.pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if NEWEST_BATTLE_MECHANICS && skill>=PBTrainerAI.mediumSkill && + target.effects[PBEffects::Minimize] + when "0A0", "0BD", "0BE" # Frost Breath, Double Kick, Twineedle + baseDmg *= 2 + when "0BF" # Triple Kick + baseDmg *= 6 # Hits do x1, x2, x3 baseDmg in turn, for x6 in total + when "0C0" # Fury Attack + if user.hasActiveAbility?(:SKILLLINK) + baseDmg *= 5 + else + baseDmg = (baseDmg*19/6).floor # Average damage dealt + end + when "0C1" # Beat Up + mult = 0 + @battle.eachInTeamFromBattlerIndex(user.index) do |pkmn,i| + mult += 1 if pkmn && pkmn.able? && pkmn.status==PBStatuses::NONE + end + baseDmg *= mult + when "0C4" # Solar Beam + baseDmg = move.pbBaseDamageMultiplier(baseDmg,user,target) + when "0D3" # Rollout + baseDmg *= 2 if user.effects[PBEffects::DefenseCurl] + when "0D4" # Bide + baseDmg = 40 + when "0E1" # Final Gambit + baseDmg = user.hp + when "144" # Flying Press + type = getConst(PBTypes,:FLYING) || -1 + if type>=0 + if skill>=PBTrainerAI.highSkill + targetTypes = target.pbTypes(true) + mult = PBTypes.getCombinedEffectiveness(type, + targetTypes[0],targetTypes[1],targetTypes[2]) + baseDmg = (baseDmg.to_f*mult/PBTypeEffectiveness::NORMAL_EFFECTIVE).round + else + mult = PBTypes.getCombinedEffectiveness(type, + target.type1,target.type2,target.effects[PBEffects::Type3]) + baseDmg = (baseDmg.to_f*mult/PBTypeEffectiveness::NORMAL_EFFECTIVE).round + end + end + baseDmg *= 2 if skill>=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize] + when "166" # Stomping Tantrum + baseDmg *= 2 if user.lastRoundMoveFailed + when "175" # Double Iron Bash + baseDmg *= 2 + baseDmg *= 2 if skill>=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize] + end + return baseDmg + end + + #============================================================================= + # Damage calculation + #============================================================================= + def pbRoughDamage(move,user,target,skill,baseDmg) + # Fixed damage moves + return baseDmg if move.is_a?(PokeBattle_FixedDamageMove) + # Get the move's type + type = pbRoughType(move,user,skill) + ##### Calculate user's attack stat ##### + atk = pbRoughStat(user,PBStats::ATTACK,skill) + if move.function=="121" # Foul Play + atk = pbRoughStat(target,PBStats::ATTACK,skill) + elsif move.specialMove?(type) + if move.function=="121" # Foul Play + atk = pbRoughStat(target,PBStats::SPATK,skill) + else + atk = pbRoughStat(user,PBStats::SPATK,skill) + end + end + ##### Calculate target's defense stat ##### + defense = pbRoughStat(target,PBStats::DEFENSE,skill) + if move.specialMove?(type) && move.function!="122" # Psyshock + defense = pbRoughStat(target,PBStats::SPDEF,skill) + end + ##### Calculate all multiplier effects ##### + multipliers = [0x1000,0x1000,0x1000,0x1000] + # Ability effects that alter damage + moldBreaker = false + if skill>=PBTrainerAI.highSkill && target.hasMoldBreaker? + moldBreaker = true + end + if skill>=PBTrainerAI.mediumSkill && user.abilityActive? + # NOTE: These abilities aren't suitable for checking at the start of the + # round. + abilityBlacklist = [:ANALYTIC,:SNIPER,:TINTEDLENS,:AERILATE,:PIXILATE,:REFRIGERATE] + canCheck = true + abilityBlacklist.each do |m| + next if !isConst?(move.id,PBMoves,m) + canCheck = false + break + end + if canCheck + BattleHandlers.triggerDamageCalcUserAbility(user.ability, + user,target,move,multipliers,baseDmg,type) + end + end + if skill>=PBTrainerAI.mediumSkill && !moldBreaker + user.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerDamageCalcUserAllyAbility(b.ability, + user,target,move,multipliers,baseDmg,type) + end + end + if skill>=PBTrainerAI.bestSkill && !moldBreaker && target.abilityActive? + # NOTE: These abilities aren't suitable for checking at the start of the + # round. + abilityBlacklist = [:FILTER,:SOLIDROCK] + canCheck = true + abilityBlacklist.each do |m| + next if !isConst?(move.id,PBMoves,m) + canCheck = false + break + end + if canCheck + BattleHandlers.triggerDamageCalcTargetAbility(target.ability, + user,target,move,multipliers,baseDmg,type) + end + end + if skill>=PBTrainerAI.bestSkill && !moldBreaker + target.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerDamageCalcTargetAllyAbility(b.ability, + user,target,move,multipliers,baseDmg,type) + end + end + # Item effects that alter damage + # NOTE: Type-boosting gems aren't suitable for checking at the start of the + # round. + if skill>=PBTrainerAI.mediumSkill && user.itemActive? + # NOTE: These items aren't suitable for checking at the start of the + # round. + itemBlacklist = [:EXPERTBELT,:LIFEORB] + canCheck = true + itemBlacklist.each do |i| + next if !isConst?(user.item,PBItems,i) + canCheck = false + break + end + if canCheck + BattleHandlers.triggerDamageCalcUserItem(user.item, + user,target,move,multipliers,baseDmg,type) + end + end + if skill>=PBTrainerAI.bestSkill && target.itemActive? + # NOTE: Type-weakening berries aren't suitable for checking at the start + # of the round. + if !pbIsBerry?(target.item) + BattleHandlers.triggerDamageCalcTargetItem(target.item, + user,target,move,multipliers,baseDmg,type) + end + end + # Global abilities + if skill>=PBTrainerAI.mediumSkill + if (@battle.pbCheckGlobalAbility(:DARKAURA) && isConst?(type,PBTypes,:DARK)) || + (@battle.pbCheckGlobalAbility(:FAIRYAURA) && isConst?(type,PBTypes,:FAIRY)) + if @battle.pbCheckGlobalAbility(:AURABREAK) + multipliers[BASE_DMG_MULT] *= 2/3 + else + multipliers[BASE_DMG_MULT] *= 4/3 + end + end + end + # Parental Bond + if skill>=PBTrainerAI.mediumSkill && user.hasActiveAbility?(:PARENTALBOND) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.25).floor + end + # Me First + # TODO + # Helping Hand - n/a + # Charge + if skill>=PBTrainerAI.mediumSkill + if user.effects[PBEffects::Charge]>0 && isConst?(type,PBTypes,:ELECTRIC) + multipliers[BASE_DMG_MULT] *= 2 + end + end + # Mud Sport and Water Sport + if skill>=PBTrainerAI.mediumSkill + if isConst?(type,PBTypes,:ELECTRIC) + @battle.eachBattler do |b| + next if !b.effects[PBEffects::MudSport] + multipliers[BASE_DMG_MULT] /= 3 + break + end + if @battle.field.effects[PBEffects::MudSportField]>0 + multipliers[BASE_DMG_MULT] /= 3 + end + end + if isConst?(type,PBTypes,:FIRE) + @battle.eachBattler do |b| + next if !b.effects[PBEffects::WaterSport] + multipliers[BASE_DMG_MULT] /= 3 + break + end + if @battle.field.effects[PBEffects::WaterSportField]>0 + multipliers[BASE_DMG_MULT] /= 3 + end + end + end + # Terrain moves + if user.affectedByTerrain? && skill>=PBTrainerAI.mediumSkill + case @battle.field.terrain + when PBBattleTerrains::Electric + if isConst?(type,PBTypes,:ELECTRIC) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + when PBBattleTerrains::Grassy + if isConst?(type,PBTypes,:GRASS) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + when PBBattleTerrains::Psychic + if isConst?(type,PBTypes,:PSYCHIC) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + end + end + if target.affectedByTerrain? && skill>=PBTrainerAI.mediumSkill + if @battle.field.terrain==PBBattleTerrains::Misty && isConst?(type,PBTypes,:DRAGON) + multipliers[BASE_DMG_MULT] /= 2 + end + end + # Badge multipliers + if skill>=PBTrainerAI.highSkill + if @battle.internalBattle + # Don't need to check the Atk/Sp Atk-boosting badges because the AI + # won't control the player's Pokémon. + if target.pbOwnedByPlayer? + if move.physicalMove?(type) && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_DEFENSE + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round + elsif move.specialMove?(type) && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_SPDEF + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round + end + end + end + end + # Multi-targeting attacks + if skill>=PBTrainerAI.highSkill + if pbTargetsMultiple?(move,user) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*0.75).round + end + end + # Weather + if skill>=PBTrainerAI.mediumSkill + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + if isConst?(type,PBTypes,:FIRE) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + elsif isConst?(type,PBTypes,:WATER) + multipliers[FINAL_DMG_MULT] /= 2 + end + when PBWeather::Rain, PBWeather::HeavyRain + if isConst?(type,PBTypes,:FIRE) + multipliers[FINAL_DMG_MULT] /= 2 + elsif isConst?(type,PBTypes,:WATER) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + end + when PBWeather::Sandstorm + if target.pbHasType?(:ROCK) && move.specialMove?(type) && move.function!="122" # Psyshock + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.5).round + end + end + end + # Critical hits - n/a + # Random variance - n/a + # STAB + if skill>=PBTrainerAI.mediumSkill + if type>=0 && user.pbHasType?(type) + if user.hasActiveAbility?(:ADAPTABILITY) + multipliers[FINAL_DMG_MULT] *= 2 + else + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + end + end + end + # Type effectiveness + if skill>=PBTrainerAI.mediumSkill + typemod = pbCalcTypeMod(type,user,target) + multipliers[FINAL_DMG_MULT] *= typemod.to_f/PBTypeEffectiveness::NORMAL_EFFECTIVE + multipliers[FINAL_DMG_MULT] = multipliers[FINAL_DMG_MULT].round + end + # Burn + if skill>=PBTrainerAI.highSkill + if user.status==PBStatuses::BURN && move.physicalMove?(type) && + !user.hasActiveAbility?(:GUTS) && + !(NEWEST_BATTLE_MECHANICS && move.function=="07E") # Facade + multipliers[FINAL_DMG_MULT] /= 2 + end + end + # Aurora Veil, Reflect, Light Screen + if skill>=PBTrainerAI.highSkill + if !move.ignoresReflect? && !user.hasActiveAbility?(:INFILTRATOR) + if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::Reflect]>0 && move.physicalMove?(type) + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::LightScreen]>0 && move.specialMove?(type) + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + end + end + end + # Minimize + if skill>=PBTrainerAI.highSkill + if target.effects[PBEffects::Minimize] && move.tramplesMinimize?(2) + multipliers[FINAL_DMG_MULT] *= 2 + end + end + # Move-specific base damage modifiers + # TODO + # Move-specific final damage modifiers + # TODO + ##### Main damage calculation ##### + baseDmg = [(baseDmg * multipliers[BASE_DMG_MULT] / 0x1000).round,1].max + atk = [(atk * multipliers[ATK_MULT] / 0x1000).round,1].max + defense = [(defense * multipliers[DEF_MULT] / 0x1000).round,1].max + damage = (((2.0*user.level/5+2).floor*baseDmg*atk/defense).floor/50).floor+2 + damage = [(damage * multipliers[FINAL_DMG_MULT] / 0x1000).round,1].max + # "AI-specific calculations below" + # Increased critical hit rates + if skill>=PBTrainerAI.mediumSkill + c = 0 + # Ability effects that alter critical hit rate + if c>=0 && user.abilityActive? + c = BattleHandlers.triggerCriticalCalcUserAbility(user.ability,user,target,c) + end + if skill>=PBTrainerAI.bestSkill + if c>=0 && !moldBreaker && target.abilityActive? + c = BattleHandlers.triggerCriticalCalcTargetAbility(target.ability,user,target,c) + end + end + # Item effects that alter critical hit rate + if c>=0 && user.itemActive? + c = BattleHandlers.triggerCriticalCalcUserItem(user.item,user,target,c) + end + if skill>=PBTrainerAI.bestSkill + if c>=0 && target.itemActive? + c = BattleHandlers.triggerCriticalCalcTargetItem(target.item,user,target,c) + end + end + # Other efffects + c = -1 if target.pbOwnSide.effects[PBEffects::LuckyChant]>0 + if c>=0 + c += 1 if move.highCriticalRate? + c += user.effects[PBEffects::FocusEnergy] + c += 1 if user.inHyperMode? && isConst?(move.type,PBTypes,:SHADOW) + end + if c>=0 + c = 4 if c>4 + damage += damage*0.1*c + end + end + return damage.floor + end + + #============================================================================= + # Accuracy calculation + #============================================================================= + def pbRoughAccuracy(move,user,target,skill) + # "Always hit" effects and "always hit" accuracy + if skill>=PBTrainerAI.mediumSkill + return 125 if target.effects[PBEffects::Minimize] && move.tramplesMinimize?(1) + return 125 if target.effects[PBEffects::Telekinesis]>0 + end + baseAcc = move.accuracy + if skill>=PBTrainerAI.highSkill + baseAcc = move.pbBaseAccuracy(user,target) + end + return 125 if baseAcc==0 && skill>=PBTrainerAI.mediumSkill + # Get the move's type + type = pbRoughType(move,user,skill) + # Calculate all modifier effects + modifiers = [] + modifiers[BASE_ACC] = baseAcc + modifiers[ACC_STAGE] = user.stages[PBStats::ACCURACY] + modifiers[EVA_STAGE] = target.stages[PBStats::EVASION] + modifiers[ACC_MULT] = 0x1000 + modifiers[EVA_MULT] = 0x1000 + pbCalcAccuracyModifiers(user,target,modifiers,move,type,skill) + # Check if move can't miss + return 125 if modifiers[BASE_ACC]==0 + # Calculation + accStage = [[modifiers[ACC_STAGE],-6].max,6].min + 6 + evaStage = [[modifiers[EVA_STAGE],-6].max,6].min + 6 + stageMul = [3,3,3,3,3,3, 3, 4,5,6,7,8,9] + stageDiv = [9,8,7,6,5,4, 3, 3,3,3,3,3,3] + accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage] + evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage] + accuracy = (accuracy * modifiers[ACC_MULT] / 0x1000).round + evasion = (evasion * modifiers[EVA_MULT] / 0x1000).round + evasion = 1 if evasion<1 + return modifiers[BASE_ACC] * accuracy / evasion + end + + def pbCalcAccuracyModifiers(user,target,modifiers,move,type,skill) + moldBreaker = false + if skill>=PBTrainerAI.highSkill && target.hasMoldBreaker? + moldBreaker = true + end + # Ability effects that alter accuracy calculation + if skill>=PBTrainerAI.mediumSkill + if user.abilityActive? + BattleHandlers.triggerAccuracyCalcUserAbility(user.ability, + modifiers,user,target,move,type) + end + user.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerAccuracyCalcUserAllyAbility(b.ability, + modifiers,user,target,move,type) + end + end + if skill>=PBTrainerAI.bestSkill + if target.abilityActive? && !moldBreaker + BattleHandlers.triggerAccuracyCalcTargetAbility(target.ability, + modifiers,user,target,move,type) + end + end + # Item effects that alter accuracy calculation + if skill>=PBTrainerAI.mediumSkill + if user.itemActive? + BattleHandlers.triggerAccuracyCalcUserItem(user.item, + modifiers,user,target,move,type) + end + end + if skill>=PBTrainerAI.bestSkill + if target.itemActive? + BattleHandlers.triggerAccuracyCalcTargetItem(target.item, + modifiers,user,target,move,type) + end + end + # Other effects, inc. ones that set ACC_MULT or EVA_STAGE to specific values + if skill>=PBTrainerAI.mediumSkill + if @battle.field.effects[PBEffects::Gravity]>0 + modifiers[ACC_MULT] = (modifiers[ACC_MULT]*5/3).round + end + if user.effects[PBEffects::MicleBerry] + modifiers[ACC_MULT] = (modifiers[ACC_MULT]*1.2).round + end + modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::Foresight] && modifiers[EVA_STAGE]>0 + modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::MiracleEye] && modifiers[EVA_STAGE]>0 + end + # "AI-specific calculations below" + if skill>=PBTrainerAI.mediumSkill + modifiers[EVA_STAGE] = 0 if move.function=="0A9" # Chip Away + modifiers[BASE_ACC] = 0 if ["0A5","139","13A","13B","13C", # "Always hit" + "147"].include?(move.function) + modifiers[BASE_ACC] = 0 if user.effects[PBEffects::LockOn]>0 && + user.effects[PBEffects::LockOnPos]==target.index + end + if skill>=PBTrainerAI.highSkill + if move.function=="006" # Toxic + modifiers[BASE_ACC] = 0 if NEWEST_BATTLE_MECHANICS && move.statusMove? && + user.pbHasType?(:POISON) + end + if move.function=="070" # OHKO moves + modifiers[BASE_ACC] = move.accuracy+user.level-target.level + modifiers[ACC_MULT] = 0 if target.level>user.level + if skill>=PBTrainerAI.bestSkill + modifiers[ACC_MULT] = 0 if target.hasActiveAbility?(:STURDY) + end + end + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_PBTargets.rb b/Data/Scripts/011_Battle/004_PBTargets.rb new file mode 100644 index 000000000..44983d050 --- /dev/null +++ b/Data/Scripts/011_Battle/004_PBTargets.rb @@ -0,0 +1,70 @@ +module PBTargets + # NOTE: These numbers are all over the place because of backwards + # compatibility. As untidy as they are, they need to be left like this. + None = 1 # Bide, Counter, Metal Burst, Mirror Coat (calculate a target) + User = 10 + NearAlly = 100 # Aromatic Mist, Helping Hand, Hold Hands + UserOrNearAlly = 200 # Acupressure + NearFoe = 400 # Me First + AllNearFoes = 4 + RandomNearFoe = 2 # Petal Dance, Outrage, Struggle, Thrash, Uproar + Foe = 9 # For throwing a Poké Ball + NearOther = 0 + AllNearOthers = 8 + Other = 3 # Most Flying-type moves, pulse moves (hits non-near targets) + UserSide = 40 + FoeSide = 80 # Entry hazards + BothSides = 20 + UserAndAllies = 5 # Aromatherapy, Gear Up, Heal Bell, Life Dew, Magnetic Flux, Howl (in Gen 8+) + AllFoes = 6 # Unused (for completeness) + AllBattlers = 7 # Flower Shield, Perish Song, Rototiller, Teatime + + def self.noTargets?(target) + return target==None || + target==User || + target==UserSide || + target==FoeSide || + target==BothSides + end + + # Used to determine if you are able to choose a target for the move. + def self.oneTarget?(target) + return !PBTargets.noTargets?(target) && + !PBTargets.multipleTargets?(target) + end + + def self.multipleTargets?(target) + return target==AllNearFoes || + target==AllNearOthers || + target==UserAndAllies || + target==AllFoes || + target==AllBattlers + end + + # These moves do not target specific Pokémon but are still affected by Pressure. + def self.targetsFoeSide?(target) + return target==FoeSide || + target==BothSides + end + + def self.canChooseDistantTarget?(target) + return target==Other + end + + # These moves can be redirected to a different target. + def self.canChooseOneFoeTarget?(target) + return target==NearFoe || + target==NearOther || + target==Other || + target==RandomNearFoe + end + + # Used by the AI to avoid targeting an ally with a move if that move could + # target an opponent instead. + def self.canChooseFoeTarget?(target) + return target==NearFoe || + target==NearOther || + target==Other || + target==RandomNearFoe + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/001_PokeBattle_Animation.rb b/Data/Scripts/011_Battle/005_Battle scene/001_PokeBattle_Animation.rb new file mode 100644 index 000000000..a4ec2bc79 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/001_PokeBattle_Animation.rb @@ -0,0 +1,264 @@ +class PokeBattle_Animation + def initialize(sprites,viewport) + @sprites = sprites + @viewport = viewport + @pictureEx = [] # For all the PictureEx + @pictureSprites = [] # For all the sprites + @tempSprites = [] # For sprites that exist only for this animation + @animDone = false + createProcesses + end + + def dispose + @tempSprites.each { |s| s.dispose if s } + end + + def createProcesses; end + def empty?; return @pictureEx.length==0; end + def animDone?; return @animDone; end + + def addSprite(s,origin=PictureOrigin::TopLeft) + num = @pictureEx.length + picture = PictureEx.new(s.z) + picture.x = s.x + picture.y = s.y + picture.visible = s.visible + picture.tone = s.tone.clone + picture.setOrigin(0,origin) + @pictureEx[num] = picture + @pictureSprites[num] = s + return picture + end + + def addNewSprite(x,y,name,origin=PictureOrigin::TopLeft) + num = @pictureEx.length + picture = PictureEx.new(num) + picture.setXY(0,x,y) + picture.setName(0,name) + picture.setOrigin(0,origin) + @pictureEx[num] = picture + s = IconSprite.new(x,y,@viewport) + s.setBitmap(name) + @pictureSprites[num] = s + @tempSprites.push(s) + return picture + end + + def update + return if @animDone + @tempSprites.each { |s| s.update if s } + finished = true + @pictureEx.each_with_index do |p,i| + next if !p.running? + finished = false + p.update + setPictureIconSprite(@pictureSprites[i],p) + end + @animDone = true if finished + end +end + + + +module PokeBattle_BallAnimationMixin + # Returns the color that the Pokémon turns when it goes into or out of its + # Poké Ball. + def getBattlerColorFromBallType(ballType) + case ballType + when 1; return Color.new(132, 189, 247) # Great Ball + when 2; return Color.new(189, 247, 165) # Safari Ball + when 3; return Color.new(255, 255, 123) # Ultra Ball + when 4; return Color.new(189, 165, 231) # Master Ball + when 5; return Color.new(173, 255, 206) # Net Ball + when 6; return Color.new( 99, 206, 247) # Dive Ball + when 7; return Color.new(247, 222, 82) # Nest Ball + when 8; return Color.new(255, 198, 132) # Repeat Ball + when 9; return Color.new(239, 247, 247) # Timer Ball + when 10; return Color.new(255, 140, 82) # Luxury Ball + when 11; return Color.new(255, 74, 82) # Premier Ball + when 12; return Color.new(115, 115, 140) # Dusk Ball + when 13; return Color.new(255, 198, 231) # Heal Ball + when 14; return Color.new(140, 214, 255) # Quick Ball + when 15; return Color.new(247, 66, 41) # Cherish Ball + end + return Color.new(255, 181, 247) # Poké Ball, Sport Ball, Apricorn Balls, others + end + + def addBallSprite(ballX,ballY,ballType) + ball = addNewSprite(ballX,ballY, + sprintf("Graphics/Battle animations/ball_%02d",ballType),PictureOrigin::Center) + @ballSprite = @pictureSprites.last + if @ballSprite.bitmap.width>=@ballSprite.bitmap.height + @ballSprite.src_rect.width = @ballSprite.bitmap.height/2 + ball.setSrcSize(0,@ballSprite.bitmap.height/2,@ballSprite.bitmap.height) + end + return ball + end + + def ballTracksHand(ball,traSprite,safariThrow=false) + # Back sprite isn't animated, no hand-tracking needed + if traSprite.bitmap.width=@ballSprite.bitmap.height + # 2* because each frame is twice as tall as it is wide + numFrames = 2*@ballSprite.bitmap.width/@ballSprite.bitmap.height + end + if numFrames>1 + curFrame = 0 + for i in 1..duration + thisFrame = numFrames*numTumbles*i/duration + if thisFrame>curFrame + curFrame = thisFrame + ball.setSrc(delay+i-1,(curFrame%numFrames)*@ballSprite.bitmap.height/2,0) + end + end + ball.setSrc(delay+duration,0,0) + end + # Rotate ball + ball.moveAngle(delay,duration,360*3) + ball.setAngle(delay+duration,0) + end + + def ballSetOpen(ball,delay,ballType) + ball.setName(delay,sprintf("Graphics/Battle animations/ball_%02d_open",ballType)) + if @ballSprite && @ballSprite.bitmap.width>=@ballSprite.bitmap.height + ball.setSrcSize(delay,@ballSprite.bitmap.height/2,@ballSprite.bitmap.height) + end + end + + def ballSetClosed(ball,delay,ballType) + ball.setName(delay,sprintf("Graphics/Battle animations/ball_%02d",ballType)) + if @ballSprite && @ballSprite.bitmap.width>=@ballSprite.bitmap.height + ball.setSrcSize(delay,@ballSprite.bitmap.height/2,@ballSprite.bitmap.height) + end + end + + def ballOpenUp(ball,delay,ballType,showSquish=true,playSE=true) + if showSquish + ball.moveZoomXY(delay,1,120,80) # Squish + ball.moveZoom(delay+5,1,100) # Unsquish + delay += 6 + end + ball.setSE(delay,"Battle recall") if playSE + ballSetOpen(ball,delay,ballType) + end + + def battlerAppear(battler,delay,battlerX,battlerY,batSprite,color) + battler.setVisible(delay,true) + battler.setOpacity(delay,255) + battler.moveXY(delay,5,battlerX,battlerY) + battler.moveZoom(delay,5,100,[batSprite,:pbPlayIntroAnimation]) + # NOTE: As soon as the battler sprite finishes zooming, and just as it + # starts changing its tone to normal, it plays its intro animation. + color.alpha = 0 + battler.moveColor(delay+5,10,color) + end + + def battlerAbsorb(battler,delay,battlerX,battlerY,color) + color.alpha = 255 + battler.moveColor(delay,10,color) + delay = battler.totalDuration + battler.moveXY(delay,5,battlerX,battlerY) + battler.moveZoom(delay,5,0) + battler.setVisible(delay+5,false) + end + + # The regular Poké Ball burst animation. + def ballBurst(delay,ballX,ballY,ballType) + end + + # The Poké Ball burst animation used when absorbing a wild Pokémon during a + # capture attempt. + def ballBurstCapture(delay,ballX,ballY,ballType) + end + + def ballCaptureSuccess(ball,delay,ballX,ballY) + ball.setSE(delay,"Battle catch click") + ball.moveTone(delay,4,Tone.new(-64,-64,-64,128)) + end + + # The Poké Ball burst animation used when recalling a Pokémon. + def ballBurstRecall(delay,ballX,ballY,ballType) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/002_PokeBattle_SceneAnimations.rb b/Data/Scripts/011_Battle/005_Battle scene/002_PokeBattle_SceneAnimations.rb new file mode 100644 index 000000000..53f2fdaae --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/002_PokeBattle_SceneAnimations.rb @@ -0,0 +1,882 @@ +#=============================================================================== +# Shows the battle scene fading in while elements slide around into place +#=============================================================================== +class BattleIntroAnimation < PokeBattle_Animation + def initialize(sprites,viewport,battle) + @battle = battle + super(sprites,viewport) + end + + def createProcesses + appearTime = 20 # This is in 1/20 seconds + # Background + if @sprites["battle_bg2"] + makeSlideSprite("battle_bg",0.5,appearTime) + makeSlideSprite("battle_bg2",0.5,appearTime) + end + # Bases + makeSlideSprite("base_0",1,appearTime,PictureOrigin::Bottom) + makeSlideSprite("base_1",-1,appearTime,PictureOrigin::Center) + # Player sprite, partner trainer sprite + @battle.player.each_with_index do |p,i| + makeSlideSprite("player_#{i+1}",1,appearTime,PictureOrigin::Bottom) + end + # Opposing trainer sprite(s) or wild Pokémon sprite(s) + if @battle.trainerBattle? + @battle.opponent.each_with_index do |p,i| + makeSlideSprite("trainer_#{i+1}",-1,appearTime,PictureOrigin::Bottom) + end + else # Wild battle + @battle.pbParty(1).each_with_index do |pkmn,i| + idxBattler = 2*i+1 + makeSlideSprite("pokemon_#{idxBattler}",-1,appearTime,PictureOrigin::Bottom) + end + end + # Shadows + for i in 0...@battle.battlers.length + makeSlideSprite("shadow_#{i}",((i%2)==0) ? 1 : -1,appearTime,PictureOrigin::Center) + end + # Fading blackness over whole screen + blackScreen = addNewSprite(0,0,"Graphics/Battle animations/black_screen") + blackScreen.setZ(0,999) + blackScreen.moveOpacity(0,8,0) + # Fading blackness over command bar + blackBar = addNewSprite(@sprites["cmdBar_bg"].x,@sprites["cmdBar_bg"].y, + "Graphics/Battle animations/black_bar") + blackBar.setZ(0,998) + blackBar.moveOpacity(appearTime*3/4,appearTime/4,0) + end + + def makeSlideSprite(spriteName,deltaMult,appearTime,origin=nil) + # If deltaMult is positive, the sprite starts off to the right and moves + # left (for sprites on the player's side and the background). + return if !@sprites[spriteName] + s = addSprite(@sprites[spriteName],origin) + s.setDelta(0,(Graphics.width*deltaMult).floor,0) + s.moveDelta(0,appearTime,(-Graphics.width*deltaMult).floor,0) + end +end + + + +#=============================================================================== +# Shows wild Pokémon fading back to their normal color, and triggers their intro +# animations +#=============================================================================== +class BattleIntroAnimation2 < PokeBattle_Animation + def initialize(sprites,viewport,sideSize) + @sideSize = sideSize + super(sprites,viewport) + end + + def createProcesses + for i in 0...@sideSize + idxBattler = 2*i+1 + next if !@sprites["pokemon_#{idxBattler}"] + battler = addSprite(@sprites["pokemon_#{idxBattler}"],PictureOrigin::Bottom) + battler.moveTone(0,4,Tone.new(0,0,0,0)) + battler.setCallback(10*i,[@sprites["pokemon_#{idxBattler}"],:pbPlayIntroAnimation]) + end + end +end + + + +#=============================================================================== +# Makes a side's party bar and balls appear +#=============================================================================== +class LineupAppearAnimation < PokeBattle_Animation + BAR_DISPLAY_WIDTH = 248 + + def initialize(sprites,viewport,side,party,partyStarts,fullAnim) + @side = side + @party = party + @partyStarts = partyStarts + @fullAnim = fullAnim # True at start of battle, false when switching + resetGraphics(sprites) + super(sprites,viewport) + end + + def resetGraphics(sprites) + bar = sprites["partyBar_#{@side}"] + case @side + when 0 # Player's lineup + barX = Graphics.width - BAR_DISPLAY_WIDTH + barY = Graphics.height - 142 + ballX = barX + 44 + ballY = barY - 30 + when 1 # Opposing lineup + barX = BAR_DISPLAY_WIDTH + barY = 114 + ballX = barX - 44 - 30 # 30 is width of ball icon + ballY = barY - 30 + barX -= bar.bitmap.width + end + ballXdiff = 32*(1-2*@side) + bar.x = barX + bar.y = barY + bar.opacity = 255 + bar.visible = false + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + ball = sprites["partyBall_#{@side}_#{i}"] + ball.x = ballX + ball.y = ballY + ball.opacity = 255 + ball.visible = false + ballX += ballXdiff + end + end + + def getPartyIndexFromBallIndex(idxBall) + # Player's lineup (just show balls for player's party) + if @side==0 + return idxBall if @partyStarts.length<2 + return idxBall if idxBall<@partyStarts[1] + return -1 + end + # Opposing lineup + # NOTE: This doesn't work well for 4+ opposing trainers. + ballsPerTrainer = PokeBattle_SceneConstants::NUM_BALLS/@partyStarts.length # 6/3/2 + startsIndex = idxBall/ballsPerTrainer + teamIndex = idxBall%ballsPerTrainer + ret = @partyStarts[startsIndex]+teamIndex + if startsIndex<@partyStarts.length-1 + # There is a later trainer, don't spill over into its team + return -1 if ret>=@partyStarts[startsIndex+1] + end + return ret + end + + def createProcesses + bar = addSprite(@sprites["partyBar_#{@side}"]) + bar.setVisible(0,true) + dir = (@side==0) ? 1 : -1 + bar.setDelta(0,dir*Graphics.width/2,0) + bar.moveDelta(0,8,-dir*Graphics.width/2,0) + delay = bar.totalDuration + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + createBall(i,(@fullAnim) ? delay+i*2 : 0,dir) + end + end + + def createBall(idxBall,delay,dir) + # Choose ball's graphic + idxParty = getPartyIndexFromBallIndex(idxBall) + graphicFilename = "Graphics/Pictures/Battle/icon_ball_empty" + if idxParty>=0 && idxParty<@party.length && @party[idxParty] + if !@party[idxParty].able? + graphicFilename = "Graphics/Pictures/Battle/icon_ball_faint" + elsif @party[idxParty].status!=PBStatuses::NONE + graphicFilename = "Graphics/Pictures/Battle/icon_ball_status" + else + graphicFilename = "Graphics/Pictures/Battle/icon_ball" + end + end + # Set up ball sprite + ball = addSprite(@sprites["partyBall_#{@side}_#{idxBall}"]) + ball.setVisible(delay,true) + ball.setName(delay,graphicFilename) + ball.setDelta(delay,dir*Graphics.width/2,0) + ball.moveDelta(delay,8,-dir*Graphics.width/2,0) + end +end + + + +#=============================================================================== +# Makes a Pokémon's data box appear +#=============================================================================== +class DataBoxAppearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxBox) + @idxBox = idxBox + super(sprites,viewport) + end + + def createProcesses + return if !@sprites["dataBox_#{@idxBox}"] + box = addSprite(@sprites["dataBox_#{@idxBox}"]) + box.setVisible(0,true) + dir = ((@idxBox%2)==0) ? 1 : -1 + box.setDelta(0,dir*Graphics.width/2,0) + box.moveDelta(0,8,-dir*Graphics.width/2,0) + end +end + + + +#=============================================================================== +# Makes a Pokémon's data box disappear +#=============================================================================== +class DataBoxDisappearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxBox) + @idxBox = idxBox + super(sprites,viewport) + end + + def createProcesses + return if !@sprites["dataBox_#{@idxBox}"] || !@sprites["dataBox_#{@idxBox}"].visible + box = addSprite(@sprites["dataBox_#{@idxBox}"]) + dir = ((@idxBox%2)==0) ? 1 : -1 + box.moveDelta(0,8,dir*Graphics.width/2,0) + box.setVisible(8,false) + end +end + + + +#=============================================================================== +# Makes a Pokémon's ability bar appear +#=============================================================================== +class AbilitySplashAppearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,side) + @side = side + super(sprites,viewport) + end + + def createProcesses + return if !@sprites["abilityBar_#{@side}"] + bar = addSprite(@sprites["abilityBar_#{@side}"]) + bar.setVisible(0,true) + dir = (@side==0) ? 1 : -1 + bar.moveDelta(0,8,dir*Graphics.width/2,0) + end +end + + + +#=============================================================================== +# Makes a Pokémon's ability bar disappear +#=============================================================================== +class AbilitySplashDisappearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,side) + @side = side + super(sprites,viewport) + end + + def createProcesses + return if !@sprites["abilityBar_#{@side}"] + bar = addSprite(@sprites["abilityBar_#{@side}"]) + dir = (@side==0) ? -1 : 1 + bar.moveDelta(0,8,dir*Graphics.width/2,0) + bar.setVisible(8,false) + end +end + + + +#=============================================================================== +# Make an enemy trainer slide on-screen from the right. Makes the previous +# trainer slide off to the right first if it is on-screen. +# Used at the end of battle. +#=============================================================================== +class TrainerAppearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxTrainer) + @idxTrainer = idxTrainer + super(sprites,viewport) + end + + def createProcesses + delay = 0 + # Make old trainer sprite move off-screen first if necessary + if @idxTrainer>0 && @sprites["trainer_#{@idxTrainer}"].visible + oldTrainer = addSprite(@sprites["trainer_#{@idxTrainer}"],PictureOrigin::Bottom) + oldTrainer.moveDelta(delay,8,Graphics.width/4,0) + oldTrainer.setVisible(delay+8,false) + delay = oldTrainer.totalDuration + end + # Make new trainer sprite move on-screen + if @sprites["trainer_#{@idxTrainer+1}"] + trainerX, trainerY = PokeBattle_SceneConstants.pbTrainerPosition(1) + trainerX += 64+Graphics.width/4 + newTrainer = addSprite(@sprites["trainer_#{@idxTrainer+1}"],PictureOrigin::Bottom) + newTrainer.setVisible(delay,true) + newTrainer.setXY(delay,trainerX,trainerY) + newTrainer.moveDelta(delay,8,-Graphics.width/4,0) + end + end +end + + + +#=============================================================================== +# Shows the player (and partner) and the player party lineup sliding off screen. +# Shows the player's/partner's throwing animation (if they have one). +# Doesn't show the ball thrown or the Pokémon. +#=============================================================================== +class PlayerFadeAnimation < PokeBattle_Animation + def initialize(sprites,viewport,fullAnim=false) + @fullAnim = fullAnim # True at start of battle, false when switching + super(sprites,viewport) + end + + def createProcesses + # NOTE: The movement speeds of trainers/bar/balls are all different. + # Move trainer sprite(s) off-screen + spriteNameBase = "player" + i = 1 + while @sprites[spriteNameBase+"_#{i}"] + pl = @sprites[spriteNameBase+"_#{i}"] + i += 1 + next if !pl.visible || pl.x<0 + trainer = addSprite(pl,PictureOrigin::Bottom) + trainer.moveDelta(0,16,-Graphics.width/2,0) + # Animate trainer sprite(s) if they have multiple frames + if pl.bitmap && !pl.bitmap.disposed? && pl.bitmap.width>=pl.bitmap.height*2 + size = pl.src_rect.width # Width per frame + trainer.setSrc(0,size,0) + trainer.setSrc(5,size*2,0) + trainer.setSrc(7,size*3,0) + trainer.setSrc(9,size*4,0) + end + trainer.setVisible(16,false) + end + # Move and fade party bar/balls + delay = 3 + if @sprites["partyBar_0"] && @sprites["partyBar_0"].visible + partyBar = addSprite(@sprites["partyBar_0"]) + partyBar.moveDelta(delay,16,-Graphics.width/4,0) if @fullAnim + partyBar.moveOpacity(delay,12,0) + partyBar.setVisible(delay+12,false) + partyBar.setOpacity(delay+12,255) + end + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + next if !@sprites["partyBall_0_#{i}"] || !@sprites["partyBall_0_#{i}"].visible + partyBall = addSprite(@sprites["partyBall_0_#{i}"]) + partyBall.moveDelta(delay+2*i,16,-Graphics.width,0) if @fullAnim + partyBall.moveOpacity(delay,12,0) + partyBall.setVisible(delay+12,false) + partyBall.setOpacity(delay+12,255) + end + end +end + + + +#=============================================================================== +# Shows the enemy trainer(s) and the enemy party lineup sliding off screen. +# Doesn't show the ball thrown or the Pokémon. +#=============================================================================== +class TrainerFadeAnimation < PokeBattle_Animation + def initialize(sprites,viewport,fullAnim=false) + @fullAnim = fullAnim # True at start of battle, false when switching + super(sprites,viewport) + end + + def createProcesses + # NOTE: The movement speeds of trainers/bar/balls are all different. + # Move trainer sprite(s) off-screen + spriteNameBase = "trainer" + i = 1 + while @sprites[spriteNameBase+"_#{i}"] + trSprite = @sprites[spriteNameBase+"_#{i}"] + i += 1 + next if !trSprite.visible || trSprite.x>Graphics.width + trainer = addSprite(trSprite,PictureOrigin::Bottom) + trainer.moveDelta(0,16,Graphics.width/2,0) + trainer.setVisible(16,false) + end + # Move and fade party bar/balls + delay = 3 + if @sprites["partyBar_1"] && @sprites["partyBar_1"].visible + partyBar = addSprite(@sprites["partyBar_1"]) + partyBar.moveDelta(delay,16,Graphics.width/4,0) if @fullAnim + partyBar.moveOpacity(delay,12,0) + partyBar.setVisible(delay+12,false) + partyBar.setOpacity(delay+12,255) + end + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + next if !@sprites["partyBall_1_#{i}"] || !@sprites["partyBall_1_#{i}"].visible + partyBall = addSprite(@sprites["partyBall_1_#{i}"]) + partyBall.moveDelta(delay+2*i,16,Graphics.width,0) if @fullAnim + partyBall.moveOpacity(delay,12,0) + partyBall.setVisible(delay+12,false) + partyBall.setOpacity(delay+12,255) + end + end +end + + + +#=============================================================================== +# Shows a Pokémon being sent out on the player's side (including by a partner). +# Includes the Poké Ball being thrown. +#=============================================================================== +class PokeballPlayerSendOutAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,idxTrainer,battler,startBattle,idxOrder=0) + @idxTrainer = idxTrainer + @battler = battler + @showingTrainer = startBattle + @idxOrder = idxOrder + @trainer = @battler.battle.pbGetOwnerFromBattlerIndex(@battler.index) + sprites["pokemon_#{battler.index}"].visible = false + @shadowVisible = sprites["shadow_#{battler.index}"].visible + sprites["shadow_#{battler.index}"].visible = false + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + traSprite = @sprites["player_#{@idxTrainer}"] + # Calculate the Poké Ball graphic to use + ballType = 0 + if !batSprite.pkmn.nil? + ballType = batSprite.pkmn.ballused || 0 + end + # Calculate the color to turn the battler sprite + col = getBattlerColorFromBallType(ballType) + col.alpha = 255 + # Calculate start and end coordinates for battler sprite movement + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + battlerStartX = ballPos[0] # Is also where the Ball needs to end + battlerStartY = ballPos[1] # Is also where the Ball needs to end + 18 + battlerEndX = batSprite.x + battlerEndY = batSprite.y + # Calculate start and end coordinates for Poké Ball sprite movement + ballStartX = -6 + ballStartY = 202 + ballMidX = 0 # Unused in trajectory calculation + ballMidY = battlerStartY-144 + # Set up Poké Ball sprite + ball = addBallSprite(ballStartX,ballStartY,ballType) + ball.setZ(0,25) + ball.setVisible(0,false) + # Poké Ball tracking the player's hand animation (if trainer is visible) + if @showingTrainer && traSprite && traSprite.x>0 + ball.setZ(0,traSprite.z-1) + ballStartX, ballStartY = ballTracksHand(ball,traSprite) + end + delay = ball.totalDuration # 0 or 7 + # Poké Ball trajectory animation + createBallTrajectory(ball,delay,12, + ballStartX,ballStartY,ballMidX,ballMidY,battlerStartX,battlerStartY-18) + ball.setZ(9,batSprite.z-1) + delay = ball.totalDuration+4 + delay += 10*@idxOrder # Stagger appearances if multiple Pokémon are sent out at once + ballOpenUp(ball,delay-2,ballType) + ballBurst(delay,battlerStartX,battlerStartY-18,ballType) + ball.moveOpacity(delay+2,2,0) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + battler.setXY(0,battlerStartX,battlerStartY) + battler.setZoom(0,0) + battler.setColor(0,col) + # Battler animation + battlerAppear(battler,delay,battlerEndX,battlerEndY,batSprite,col) + if @shadowVisible + # Set up shadow sprite + shadow = addSprite(shaSprite,PictureOrigin::Center) + shadow.setOpacity(0,0) + # Shadow animation + shadow.setVisible(delay,@shadowVisible) + shadow.moveOpacity(delay+5,10,255) + end + end +end + + + +#=============================================================================== +# Shows a Pokémon being sent out on the opposing side. +# Includes the Poké Ball being "thrown" (although here the Poké Ball just +# appears in the spot where it opens up rather than being thrown to there). +#=============================================================================== +class PokeballTrainerSendOutAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,idxTrainer,battler,startBattle,idxOrder) + @idxTrainer = idxTrainer + @battler = battler + @showingTrainer = startBattle + @idxOrder = idxOrder + sprites["pokemon_#{battler.index}"].visible = false + @shadowVisible = sprites["shadow_#{battler.index}"].visible + sprites["shadow_#{battler.index}"].visible = false + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + # Calculate the Poké Ball graphic to use + ballType = 0 + if !batSprite.pkmn.nil? + ballType = batSprite.pkmn.ballused || 0 + end + # Calculate the color to turn the battler sprite + col = getBattlerColorFromBallType(ballType) + col.alpha = 255 + # Calculate start and end coordinates for battler sprite movement + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + battlerStartX = ballPos[0] + battlerStartY = ballPos[1] + battlerEndX = batSprite.x + battlerEndY = batSprite.y + # Set up Poké Ball sprite + ball = addBallSprite(0,0,ballType) + ball.setZ(0,batSprite.z-1) + # Poké Ball animation + createBallTrajectory(ball,battlerStartX,battlerStartY) + delay = ball.totalDuration+6 + delay += 10 if @showingTrainer # Give time for trainer to slide off screen + delay += 10*@idxOrder # Stagger appearances if multiple Pokémon are sent out at once + ballOpenUp(ball,delay-2,ballType) + ballBurst(delay,battlerStartX,battlerStartY-18,ballType) + ball.moveOpacity(delay+2,2,0) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + battler.setXY(0,battlerStartX,battlerStartY) + battler.setZoom(0,0) + battler.setColor(0,col) + # Battler animation + battlerAppear(battler,delay,battlerEndX,battlerEndY,batSprite,col) + if @shadowVisible + # Set up shadow sprite + shadow = addSprite(shaSprite,PictureOrigin::Center) + shadow.setOpacity(0,0) + # Shadow animation + shadow.setVisible(delay,@shadowVisible) + shadow.moveOpacity(delay+5,10,255) + end + end + + def createBallTrajectory(ball,destX,destY) + # NOTE: In HGSS, there isn't a Poké Ball arc under any circumstance (neither + # when throwing out the first Pokémon nor when switching/replacing a + # fainted Pokémon). This is probably worth changing. + ball.setXY(0,destX,destY-4) + end +end + + + +#=============================================================================== +# Shows a Pokémon being recalled into its Poké Ball +#=============================================================================== +class BattlerRecallAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,idxBattler) + @idxBattler = idxBattler + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@idxBattler}"] + shaSprite = @sprites["shadow_#{@idxBattler}"] + # Calculate the Poké Ball graphic to use + ballType = 0 + if !batSprite.pkmn.nil? + ballType = batSprite.pkmn.ballused || 0 + end + # Calculate the color to turn the battler sprite + col = getBattlerColorFromBallType(ballType) + col.alpha = 0 + # Calculate start and end coordinates for battler sprite movement + battlerStartX = batSprite.x + battlerStartY = batSprite.y + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@idxBattler,batSprite.sideSize) + battlerEndX = ballPos[0] + battlerEndY = ballPos[1] + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + battler.setVisible(0,true) + battler.setColor(0,col) + # Set up Poké Ball sprite + ball = addBallSprite(battlerEndX,battlerEndY,ballType) + ball.setZ(0,batSprite.z+1) + # Poké Ball animation + ballOpenUp(ball,0,ballType) + delay = ball.totalDuration + ballBurstRecall(delay,battlerEndX,battlerEndY,ballType) + ball.moveOpacity(10,2,0) + # Battler animation + battlerAbsorb(battler,delay,battlerEndX,battlerEndY,col) + if shaSprite.visible + # Set up shadow sprite + shadow = addSprite(shaSprite,PictureOrigin::Center) + # Shadow animation + shadow.moveOpacity(0,10,0) + shadow.setVisible(delay,false) + end + end +end + + + +#=============================================================================== +# Shows a Pokémon flashing after taking damage +#=============================================================================== +class BattlerDamageAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxBattler,effectiveness) + @idxBattler = idxBattler + @effectiveness = effectiveness + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@idxBattler}"] + shaSprite = @sprites["shadow_#{@idxBattler}"] + # Set up battler/shadow sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + shadow = addSprite(shaSprite,PictureOrigin::Center) + # Animation + delay = 0 + case @effectiveness + when 0; battler.setSE(delay,"Battle damage normal") + when 1; battler.setSE(delay,"Battle damage weak") + when 2; battler.setSE(delay,"Battle damage super") + end + 4.times do # 4 flashes, each lasting 0.2 (4/20) seconds + battler.setVisible(delay,false) + shadow.setVisible(delay,false) + battler.setVisible(delay+2,true) if batSprite.visible + shadow.setVisible(delay+2,true) if shaSprite.visible + delay += 4 + end + # Restore original battler/shadow sprites visibilities + battler.setVisible(delay,batSprite.visible) + shadow.setVisible(delay,shaSprite.visible) + end +end + + + +#=============================================================================== +# Shows a Pokémon fainting +#=============================================================================== +class BattlerFaintAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxBattler,battle) + @idxBattler = idxBattler + @battle = battle + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@idxBattler}"] + shaSprite = @sprites["shadow_#{@idxBattler}"] + # Set up battler/shadow sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + shadow = addSprite(shaSprite,PictureOrigin::Center) + # Get approx duration depending on sprite's position/size. Min 20 frames. + battlerTop = batSprite.y-batSprite.height + cropY = PokeBattle_SceneConstants.pbBattlerPosition(@idxBattler, + @battle.pbSideSize(@idxBattler))[1] + cropY += 8 + duration = (cropY-battlerTop)/8 + duration = 10 if duration<10 # Min 0.5 seconds + # Animation + # Play cry + delay = 10 + cry = pbCryFile(batSprite.pkmn) + if cry + battler.setSE(0,pbCryFile(batSprite.pkmn)) + delay = pbCryFrameLength(batSprite.pkmn)*20/Graphics.frame_rate + end + # Sprite drops down + shadow.setVisible(delay,false) + battler.setSE(delay,"Pkmn faint") + battler.moveOpacity(delay,duration,0) + battler.moveDelta(delay,duration,0,cropY-battlerTop) + battler.setCropBottom(delay,cropY) + battler.setVisible(delay+duration,false) + battler.setOpacity(delay+duration,255) + end +end + + + +#=============================================================================== +# Shows the player's Poké Ball being thrown to capture a Pokémon +#=============================================================================== +class PokeballThrowCaptureAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport, + ballType,numShakes,critCapture,battler,showingTrainer) + @ballType = ballType + @numShakes = (critCapture) ? 1 : numShakes + @critCapture = critCapture + @battler = battler + @showingTrainer = showingTrainer # Only true if a Safari Zone battle + @shadowVisible = sprites["shadow_#{battler.index}"].visible + @trainer = battler.battle.pbPlayer + super(sprites,viewport) + end + + def createProcesses + # Calculate start and end coordinates for battler sprite movement + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + traSprite = @sprites["player_1"] + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + battlerStartX = batSprite.x + battlerStartY = batSprite.y + ballStartX = -6 + ballStartY = 246 + ballMidX = 0 # Unused in arc calculation + ballMidY = 78 + ballEndX = ballPos[0] + ballEndY = 112 + ballGroundY = ballPos[1]-4 + # Set up Poké Ball sprite + ball = addBallSprite(ballStartX,ballStartY,@ballType) + ball.setZ(0,batSprite.z+1) + @ballSpriteIndex = (@numShakes>=4 || @critCapture) ? @tempSprites.length-1 : -1 + # Set up trainer sprite (only visible in Safari Zone battles) + if @showingTrainer && traSprite + if traSprite.bitmap.width>=traSprite.bitmap.height*2 + trainer = addSprite(traSprite,PictureOrigin::Bottom) + # Trainer animation + ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite) + end + end + delay = ball.totalDuration # 0 or 7 + # Poké Ball arc animation + ball.setSE(delay,"Battle throw") + createBallTrajectory(ball,delay,16, + ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) + ball.setZ(9,batSprite.z+1) + ball.setSE(delay+16,"Battle ball hit") + # Poké Ball opens up + delay = ball.totalDuration+6 + ballOpenUp(ball,delay,@ballType,true,false) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + # Poké Ball absorbs battler + delay = ball.totalDuration + ballBurstCapture(delay,ballEndX,ballEndY,@ballType) + delay = ball.totalDuration+4 + # NOTE: The Pokémon does not change color while being absorbed into a Poké + # Ball during a capture attempt. This may be an oversight in HGSS. + battler.setSE(delay,"Battle jump to ball") + battler.moveXY(delay,5,ballEndX,ballEndY) + battler.moveZoom(delay,5,0) + battler.setVisible(delay+5,false) + if @shadowVisible + # Set up shadow sprite + shadow = addSprite(shaSprite,PictureOrigin::Center) + # Shadow animation + shadow.moveOpacity(delay,5,0) + shadow.moveZoom(delay,5,0) + shadow.setVisible(delay+5,false) + end + # Poké Ball closes + delay = battler.totalDuration + ballSetClosed(ball,delay,@ballType) + ball.moveTone(delay,3,Tone.new(96,64,-160,160)) + ball.moveTone(delay+5,3,Tone.new(0,0,0,0)) + # Poké Ball critical capture animation + delay = ball.totalDuration+3 + if @critCapture + ball.setSE(delay,"Battle ball shake") + ball.moveXY(delay,1,ballEndX+4,ballEndY) + ball.moveXY(delay+1,2,ballEndX-4,ballEndY) + ball.moveXY(delay+3,2,ballEndX+4,ballEndY) + ball.setSE(delay+4,"Battle ball shake") + ball.moveXY(delay+5,2,ballEndX-4,ballEndY) + ball.moveXY(delay+7,1,ballEndX,ballEndY) + delay = ball.totalDuration+3 + end + # Poké Ball drops to the ground + for i in 0...4 + t = [4,4,3,2][i] # Time taken to rise or fall for each bounce + d = [1,2,4,8][i] # Fraction of the starting height each bounce rises to + delay -= t if i==0 + if i>0 + ball.setZoomXY(delay,100+5*(5-i),100-5*(5-i)) # Squish + ball.moveZoom(delay,2,100) # Unsquish + ball.moveXY(delay,t,ballEndX,ballGroundY-(ballGroundY-ballEndY)/d) + end + ball.moveXY(delay+t,t,ballEndX,ballGroundY) + ball.setSE(delay+2*t,"Battle ball drop",100-i*7) + delay = ball.totalDuration + end + battler.setXY(ball.totalDuration,ballEndX,ballGroundY) + # Poké Ball shakes + delay = ball.totalDuration+12 + for i in 0...[@numShakes,3].min + ball.setSE(delay,"Battle ball shake") + ball.moveXY(delay,2,ballEndX-2*(4-i),ballGroundY) + ball.moveAngle(delay,2,5*(4-i)) # positive means counterclockwise + ball.moveXY(delay+2,4,ballEndX+2*(4-i),ballGroundY) + ball.moveAngle(delay+2,4,-5*(4-i)) # negative means clockwise + ball.moveXY(delay+6,2,ballEndX,ballGroundY) + ball.moveAngle(delay+6,2,0) + delay = ball.totalDuration+8 + end + if @numShakes==0 || (@numShakes<4 && !@critCapture) + # Poké Ball opens + ball.setZ(delay,batSprite.z-1) + ballOpenUp(ball,delay,@ballType,false) + ballBurst(delay,ballEndX,ballGroundY,@ballType) + ball.moveOpacity(delay+2,2,0) + # Battler emerges + col = getBattlerColorFromBallType(@ballType) + col.alpha = 255 + battler.setColor(delay,col) + battlerAppear(battler,delay,battlerStartX,battlerStartY,batSprite,col) + if @shadowVisible + shadow.setVisible(delay+5,true) + shadow.setZoom(delay+5,100) + shadow.moveOpacity(delay+5,10,255) + end + else + # Pokémon was caught + ballCaptureSuccess(ball,delay,ballEndX,ballGroundY) + end + end + + def dispose + if @ballSpriteIndex>=0 + # Capture was successful, the Poké Ball sprite should stay around after + # this animation has finished. + @sprites["captureBall"] = @tempSprites[@ballSpriteIndex] + @tempSprites[@ballSpriteIndex] = nil + end + super + end +end + + + +#=============================================================================== +# Shows the player throwing a Poké Ball and it being deflected +#=============================================================================== +class PokeballThrowDeflectAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,ballType,battler) + @ballType = ballType + @battler = battler + super(sprites,viewport) + end + + def createProcesses + # Calculate start and end coordinates for battler sprite movement + batSprite = @sprites["pokemon_#{@battler.index}"] + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + ballStartX = -6 + ballStartY = 246 + ballMidX = 190 # Unused in arc calculation + ballMidY = 78 + ballEndX = ballPos[0] + ballEndY = 112 + # Set up Poké Ball sprite + ball = addBallSprite(ballStartX,ballStartY,@ballType) + ball.setZ(0,90) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + # Poké Ball arc animation + ball.setSE(0,"Battle throw") + createBallTrajectory(ball,0,16, + ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) + # Poké Ball knocked back + delay = ball.totalDuration + ball.setSE(delay,"Battle ball drop") + ball.moveXY(delay,8,-32,Graphics.height-96+32) # Back to player's corner + createBallTumbling(ball,delay,8) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/003_PokeBattle_SceneConstants.rb b/Data/Scripts/011_Battle/005_Battle scene/003_PokeBattle_SceneConstants.rb new file mode 100644 index 000000000..59f2315f5 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/003_PokeBattle_SceneConstants.rb @@ -0,0 +1,63 @@ +module PokeBattle_SceneConstants + USE_ABILITY_SPLASH = true + # Text colors + MESSAGE_BASE_COLOR = Color.new(80,80,88) + MESSAGE_SHADOW_COLOR = Color.new(160,160,168) + + # The number of party balls to show in each side's lineup. + NUM_BALLS = 6 + + # Centre bottom of the player's side base graphic + PLAYER_BASE_X = 128 + PLAYER_BASE_Y = Graphics.height - 80 + + # Centre middle of the foe's side base graphic + FOE_BASE_X = Graphics.width - 128 + FOE_BASE_Y = (Graphics.height * 3/4) - 112 + + # Returns where the centre bottom of a battler's sprite should be, given its + # index and the number of battlers on its side, assuming the battler has + # metrics of 0 (those are added later). + def self.pbBattlerPosition(index,sideSize=1) + # Start at the centre of the base for the appropriate side + if (index&1)==0; ret = [PLAYER_BASE_X,PLAYER_BASE_Y] + else; ret = [FOE_BASE_X,FOE_BASE_Y] + end + # Shift depending on index (no shifting needed for sideSize of 1) + case sideSize + when 2 + ret[0] += [-48, 48, 32, -32][index] + ret[1] += [ 0, 0, 16, -16][index] + when 3 + ret[0] += [-80, 80, 0, 0, 80, -80][index] + ret[1] += [ 0, 0, 8, -8, 16, -16][index] + end + return ret + end + + # Returns where the centre bottom of a trainer's sprite should be, given its + # side (0/1), index and the number of trainers on its side. + def self.pbTrainerPosition(side,index=0,sideSize=1) + # Start at the centre of the base for the appropriate side + if side==0; ret = [PLAYER_BASE_X,PLAYER_BASE_Y-16] + else; ret = [FOE_BASE_X,FOE_BASE_Y+6] + end + # Shift depending on index (no shifting needed for sideSize of 1) + case sideSize + when 2 + ret[0] += [-48, 48, 32, -32][2*index+side] + ret[1] += [ 0, 0, 0, -16][2*index+side] + when 3 + ret[0] += [-80, 80, 0, 0, 80, -80][2*index+side] + ret[1] += [ 0, 0, 0, -8, 0, -16][2*index+side] + end + return ret + end + + # Default focal points of user and target in animations - do not change! + # Is the centre middle of each sprite + FOCUSUSER_X = 128 # 144 + FOCUSUSER_Y = 224 # 188 + FOCUSTARGET_X = 384 # 352 + FOCUSTARGET_Y = 96 # 108, 98 +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/004_PokeBattle_SceneElements.rb b/Data/Scripts/011_Battle/005_Battle scene/004_PokeBattle_SceneElements.rb new file mode 100644 index 000000000..527bfd4e0 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/004_PokeBattle_SceneElements.rb @@ -0,0 +1,657 @@ +#=============================================================================== +# Data box for regular battles +#=============================================================================== +class PokemonDataBox < SpriteWrapper + attr_reader :battler + attr_accessor :selected + attr_reader :animatingHP + attr_reader :animatingExp + + # Time in seconds to fully fill the Exp bar (from empty). + EXP_BAR_FILL_TIME = 1.75 + # Maximum time in seconds to make a change to the HP bar. + HP_BAR_CHANGE_TIME = 1.0 + STATUS_ICON_HEIGHT = 16 + NAME_BASE_COLOR = Color.new(72,72,72) + NAME_SHADOW_COLOR = Color.new(184,184,184) + MALE_BASE_COLOR = Color.new(48,96,216) + MALE_SHADOW_COLOR = NAME_SHADOW_COLOR + FEMALE_BASE_COLOR = Color.new(248,88,40) + FEMALE_SHADOW_COLOR = NAME_SHADOW_COLOR + + def initialize(battler,sideSize,viewport=nil) + super(viewport) + @battler = battler + @sprites = {} + @spriteX = 0 + @spriteY = 0 + @spriteBaseX = 0 + @selected = 0 + @frame = 0 + @showHP = false # Specifically, show the HP numbers + @animatingHP = false + @showExp = false # Specifically, show the Exp bar + @animatingExp = false + @expFlash = 0 + initializeDataBoxGraphic(sideSize) + initializeOtherGraphics(viewport) + refresh + end + + def initializeDataBoxGraphic(sideSize) + onPlayerSide = ((@battler.index%2)==0) + # Get the data box graphic and set whether the HP numbers/Exp bar are shown + if sideSize==1 # One Pokémon on side, use the regular dara box BG + bgFilename = ["Graphics/Pictures/Battle/databox_normal", + "Graphics/Pictures/Battle/databox_normal_foe"][@battler.index%2] + if onPlayerSide + @showHP = true + @showExp = true + end + else # Multiple Pokémon on side, use the thin dara box BG + bgFilename = ["Graphics/Pictures/Battle/databox_thin", + "Graphics/Pictures/Battle/databox_thin_foe"][@battler.index%2] + end + @databoxBitmap = AnimatedBitmap.new(bgFilename) + # Determine the co-ordinates of the data box and the left edge padding width + if onPlayerSide + @spriteX = Graphics.width - 244 + @spriteY = Graphics.height - 192 + @spriteBaseX = 34 + else + @spriteX = -16 + @spriteY = 36 + @spriteBaseX = 16 + end + case sideSize + when 2 + @spriteX += [-12, 12, 0, 0][@battler.index] + @spriteY += [-20, -34, 34, 20][@battler.index] + when 3 + @spriteX += [-12, 12, -6, 6, 0, 0][@battler.index] + @spriteY += [-42, -46, 4, 0, 50, 46][@battler.index] + end + end + + def initializeOtherGraphics(viewport) + # Create other bitmaps + @numbersBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/icon_numbers")) + @hpBarBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/overlay_hp")) + @expBarBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/overlay_exp")) + # Create sprite to draw HP numbers on + @hpNumbers = BitmapSprite.new(124,16,viewport) + pbSetSmallFont(@hpNumbers.bitmap) + @sprites["hpNumbers"] = @hpNumbers + # Create sprite wrapper that displays HP bar + @hpBar = SpriteWrapper.new(viewport) + @hpBar.bitmap = @hpBarBitmap.bitmap + @hpBar.src_rect.height = @hpBarBitmap.height/3 + @sprites["hpBar"] = @hpBar + # Create sprite wrapper that displays Exp bar + @expBar = SpriteWrapper.new(viewport) + @expBar.bitmap = @expBarBitmap.bitmap + @sprites["expBar"] = @expBar + # Create sprite wrapper that displays everything except the above + @contents = BitmapWrapper.new(@databoxBitmap.width,@databoxBitmap.height) + self.bitmap = @contents + self.visible = false + self.z = 150+((@battler.index)/2)*5 + pbSetSystemFont(self.bitmap) + end + + def dispose + pbDisposeSpriteHash(@sprites) + @databoxBitmap.dispose + @numbersBitmap.dispose + @hpBarBitmap.dispose + @expBarBitmap.dispose + @contents.dispose + super + end + + def x=(value) + super + @hpBar.x = value+@spriteBaseX+102 + @expBar.x = value+@spriteBaseX+6 + @hpNumbers.x = value+@spriteBaseX+80 + end + + def y=(value) + super + @hpBar.y = value+40 + @expBar.y = value+74 + @hpNumbers.y = value+52 + end + + def z=(value) + super + @hpBar.z = value+1 + @expBar.z = value+1 + @hpNumbers.z = value+2 + end + + def opacity=(value) + super + for i in @sprites + i[1].opacity = value if !i[1].disposed? + end + end + + def visible=(value) + super + for i in @sprites + i[1].visible = value if !i[1].disposed? + end + @expBar.visible = (value && @showExp) + end + + def color=(value) + super + for i in @sprites + i[1].color = value if !i[1].disposed? + end + end + + def battler=(b) + @battler = b + self.visible = (@battler && !@battler.fainted?) + end + + def hp + return (@animatingHP) ? @currentHP : @battler.hp + end + + def expFraction + return (@animatingExp) ? @currentExp.to_f/@rangeExp : @battler.pokemon.expFraction + end + + def animateHP(oldHP,newHP,rangeHP) + @currentHP = oldHP + @endHP = newHP + @rangeHP = rangeHP + # NOTE: A change in HP takes the same amount of time to animate, no matter + # how big a change it is. + @hpIncPerFrame = (newHP-oldHP).abs/(HP_BAR_CHANGE_TIME*Graphics.frame_rate) + # minInc is the smallest amount that HP is allowed to change per frame. + # This avoids a tiny change in HP still taking HP_BAR_CHANGE_TIME seconds. + minInc = (rangeHP*4)/(@hpBarBitmap.width*HP_BAR_CHANGE_TIME*Graphics.frame_rate) + @hpIncPerFrame = minInc if @hpIncPerFrame116 + textPos.push([@battler.name,@spriteBaseX+8-nameOffset,6,false,NAME_BASE_COLOR,NAME_SHADOW_COLOR]) + # Draw Pokémon's gender symbol + case @battler.displayGender + when 0 # Male + textPos.push([_INTL("♂"),@spriteBaseX+126,6,false,MALE_BASE_COLOR,MALE_SHADOW_COLOR]) + when 1 # Female + textPos.push([_INTL("♀"),@spriteBaseX+126,6,false,FEMALE_BASE_COLOR,FEMALE_SHADOW_COLOR]) + end + pbDrawTextPositions(self.bitmap,textPos) + # Draw Pokémon's level + imagePos.push(["Graphics/Pictures/Battle/overlay_lv",@spriteBaseX+140,16]) + pbDrawNumber(@battler.level,self.bitmap,@spriteBaseX+162,16) + # Draw shiny icon + if @battler.shiny? + shinyX = (@battler.opposes?(0)) ? 206 : -6 # Foe's/player's + imagePos.push(["Graphics/Pictures/shiny",@spriteBaseX+shinyX,36]) + end + # Draw Mega Evolution/Primal Reversion icon + if @battler.mega? + imagePos.push(["Graphics/Pictures/Battle/icon_mega",@spriteBaseX+8,34]) + elsif @battler.primal? + primalX = (@battler.opposes?) ? 208 : -28 # Foe's/player's + if isConst?(@battler.pokemon.species,PBSpecies,:KYOGRE) + imagePos.push(["Graphics/Pictures/Battle/icon_primal_Kyogre",@spriteBaseX+primalX,4]) + elsif isConst?(@battler.pokemon.species,PBSpecies,:GROUDON) + imagePos.push(["Graphics/Pictures/Battle/icon_primal_Groudon",@spriteBaseX+primalX,4]) + end + end + # Draw owned icon (foe Pokémon only) + if @battler.owned? && @battler.opposes?(0) + imagePos.push(["Graphics/Pictures/Battle/icon_own",@spriteBaseX+8,36]) + end + # Draw status icon + if @battler.status>0 + s = @battler.status + s = 6 if s==PBStatuses::POISON && @battler.statusCount>0 # Badly poisoned + imagePos.push(["Graphics/Pictures/Battle/icon_statuses",@spriteBaseX+24,36, + 0,(s-1)*STATUS_ICON_HEIGHT,-1,STATUS_ICON_HEIGHT]) + end + pbDrawImagePositions(self.bitmap,imagePos) + refreshHP + refreshExp + end + + def refreshHP + @hpNumbers.bitmap.clear + return if !@battler.pokemon + # Show HP numbers + if @showHP + pbDrawNumber(self.hp,@hpNumbers.bitmap,54,2,1) + pbDrawNumber(-1,@hpNumbers.bitmap,54,2) # / char + pbDrawNumber(@battler.totalhp,@hpNumbers.bitmap,70,2) + end + # Resize HP bar + w = 0 + if self.hp>0 + w = @hpBarBitmap.width.to_f*self.hp/@battler.totalhp + w = 1 if w<1 + # NOTE: The line below snaps the bar's width to the nearest 2 pixels, to + # fit in with the rest of the graphics which are doubled in size. + w = ((w/2.0).round)*2 + end + @hpBar.src_rect.width = w + hpColor = 0 # Green bar + hpColor = 1 if self.hp<=@battler.totalhp/2 # Yellow bar + hpColor = 2 if self.hp<=@battler.totalhp/4 # Red bar + @hpBar.src_rect.y = hpColor*@hpBarBitmap.height/3 + end + + def refreshExp + return if !@showExp + w = self.expFraction*@expBarBitmap.width + # NOTE: The line below snaps the bar's width to the nearest 2 pixels, to + # fit in with the rest of the graphics which are doubled in size. + w = ((w/2).round)*2 + @expBar.src_rect.width = w + end + + def updateHPAnimation + return if !@animatingHP + if @currentHP<@endHP # Gaining HP + @currentHP += @hpIncPerFrame + @currentHP = @endHP if @currentHP>=@endHP + elsif @currentHP>@endHP # Losing HP + @currentHP -= @hpIncPerFrame + @currentHP = @endHP if @currentHP<=@endHP + end + # Refresh the HP bar/numbers + refreshHP + @animatingHP = false if @currentHP==@endHP + end + + def updateExpAnimation + return if !@animatingExp + if !@showExp # Not showing the Exp bar, no need to waste time animating it + @currentExp = @endExp + @animatingExp = false + return + end + if @currentExp<@endExp # Gaining Exp + @currentExp += @expIncPerFrame + @currentExp = @endExp if @currentExp>=@endExp + elsif @currentExp>@endExp # Losing Exp + @currentExp -= @expIncPerFrame + @currentExp = @endExp if @currentExp<=@endExp + end + # Refresh the Exp bar + refreshExp + return if @currentExp!=@endExp # Exp bar still has more to animate + # Exp bar is completely filled, level up with a flash and sound effect + if @currentExp>=@rangeExp + if @expFlash==0 + pbSEStop + @expFlash = Graphics.frame_rate/5 + pbSEPlay("Exp full") + self.flash(Color.new(64,200,248,192),@expFlash) + for i in @sprites + i[1].flash(Color.new(64,200,248,192),@expFlash) if !i[1].disposed? + end + else + @expFlash -= 1 + @animatingExp = false if @expFlash==0 + end + else + pbSEStop + # Exp bar has finished filling, end animation + @animatingExp = false + end + end + + QUARTER_ANIM_PERIOD = Graphics.frame_rate*3/20 + + def updatePositions(frameCounter) + self.x = @spriteX + self.y = @spriteY + # Data box bobbing while Pokémon is selected + if @selected==1 || @selected==2 # Choosing commands/targeted or damaged + case (frameCounter/QUARTER_ANIM_PERIOD).floor + when 1; self.y = @spriteY-2 + when 3; self.y = @spriteY+2 + end + end + end + + def update(frameCounter=0) + super() + # Animate HP bar + updateHPAnimation + # Animate Exp bar + updateExpAnimation + # Update coordinates of the data box + updatePositions(frameCounter) + pbUpdateSpriteHash(@sprites) + end +end + + + +#=============================================================================== +# Splash bar to announce a triggered ability +#=============================================================================== +class AbilitySplashBar < SpriteWrapper + attr_reader :battler + + TEXT_BASE_COLOR = Color.new(0,0,0) + TEXT_SHADOW_COLOR = Color.new(248,248,248) + + def initialize(side,viewport=nil) + super(viewport) + @side = side + @battler = nil + # Create sprite wrapper that displays background graphic + @bgBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/ability_bar")) + @bgSprite = SpriteWrapper.new(viewport) + @bgSprite.bitmap = @bgBitmap.bitmap + @bgSprite.src_rect.y = (side==0) ? 0 : @bgBitmap.height/2 + @bgSprite.src_rect.height = @bgBitmap.height/2 + # Create bitmap that displays the text + @contents = BitmapWrapper.new(@bgBitmap.width,@bgBitmap.height/2) + self.bitmap = @contents + pbSetSystemFont(self.bitmap) + # Position the bar + self.x = (side==0) ? -Graphics.width/2 : Graphics.width + self.y = (side==0) ? 180 : 80 + self.z = 120 + self.visible = false + end + + def dispose + @bgSprite.dispose + @bgBitmap.dispose + @contents.dispose + super + end + + def x=(value) + super + @bgSprite.x = value + end + + def y=(value) + super + @bgSprite.y = value + end + + def z=(value) + super + @bgSprite.z = value-1 + end + + def opacity=(value) + super + @bgSprite.opacity = value + end + + def visible=(value) + super + @bgSprite.visible = value + end + + def color=(value) + super + @bgSprite.color = value + end + + def battler=(value) + @battler = value + refresh + end + + def refresh + self.bitmap.clear + return if !@battler + textPos = [] + textX = (@side==0) ? 10 : self.bitmap.width-8 + # Draw Pokémon's name + textPos.push([_INTL("{1}'s",@battler.name),textX,2,@side==1, + TEXT_BASE_COLOR,TEXT_SHADOW_COLOR,true]) + # Draw Pokémon's ability + textPos.push([@battler.abilityName,textX,32,@side==1, + TEXT_BASE_COLOR,TEXT_SHADOW_COLOR,true]) + pbDrawTextPositions(self.bitmap,textPos) + end + + def update + super + @bgSprite.update + end +end + + + +#=============================================================================== +# Pokémon sprite (used in battle) +#=============================================================================== +class PokemonBattlerSprite < RPG::Sprite + attr_reader :pkmn + attr_accessor :index + attr_accessor :selected + attr_reader :sideSize + + def initialize(viewport,sideSize,index,battleAnimations) + super(viewport) + @pkmn = nil + @sideSize = sideSize + @index = index + @battleAnimations = battleAnimations + # @selected: 0 = not selected, 1 = choosing action bobbing for this Pokémon, + # 2 = flashing when targeted + @selected = 0 + @frame = 0 + @updating = false + @spriteX = 0 # Actual x coordinate + @spriteY = 0 # Actual y coordinate + @spriteXExtra = 0 # Offset due to "bobbing" animation + @spriteYExtra = 0 # Offset due to "bobbing" animation + @_iconBitmap = nil + self.visible = false + end + + def dispose + @_iconBitmap.dispose if @_iconBitmap + @_iconBitmap = nil + self.bitmap = nil if !self.disposed? + super + end + + def x; return @spriteX; end + def y; return @spriteY; end + + def x=(value) + @spriteX = value + super(value+@spriteXExtra) + end + + def y=(value) + @spriteY = value + super(value+@spriteYExtra) + end + + def width; return (self.bitmap) ? self.bitmap.width : 0; end + def height; return (self.bitmap) ? self.bitmap.height : 0; end + + def visible=(value) + @spriteVisible = value if !@updating # For selection/targeting flashing + super + end + + # Set sprite's origin to bottom middle + def pbSetOrigin + return if !@_iconBitmap + self.ox = @_iconBitmap.width/2 + self.oy = @_iconBitmap.height + end + + def pbSetPosition + return if !@_iconBitmap + pbSetOrigin + if (@index%2)==0 + self.z = 50+5*@index/2 + else + self.z = 50-5*(@index+1)/2 + end + # Set original position + p = PokeBattle_SceneConstants.pbBattlerPosition(@index,@sideSize) + @spriteX = p[0] + @spriteY = p[1] + # Apply metrics + pbApplyBattlerMetricsToSprite(self,@index,@pkmn.fSpecies) + end + + def setPokemonBitmap(pkmn,back=false) + @pkmn = pkmn + @_iconBitmap.dispose if @_iconBitmap + @_iconBitmap = pbLoadPokemonBitmap(@pkmn,back) + self.bitmap = (@_iconBitmap) ? @_iconBitmap.bitmap : nil + pbSetPosition + end + + # This method plays the battle entrance animation of a Pokémon. By default + # this is just playing the Pokémon's cry, but you can expand on it. The + # recommendation is to create a PictureEx animation and push it into the + # @battleAnimations array. + def pbPlayIntroAnimation(pictureEx=nil) + return if !@pkmn + cry = pbCryFile(@pkmn) + pbSEPlay(cry) if cry + end + + QUARTER_ANIM_PERIOD = Graphics.frame_rate*3/20 + SIXTH_ANIM_PERIOD = Graphics.frame_rate*2/20 + + def update(frameCounter=0) + return if !@_iconBitmap + @updating = true + # Update bitmap + @_iconBitmap.update + self.bitmap = @_iconBitmap.bitmap + # Pokémon sprite bobbing while Pokémon is selected + @spriteYExtra = 0 + if @selected==1 # When choosing commands for this Pokémon + case (frameCounter/QUARTER_ANIM_PERIOD).floor + when 1; @spriteYExtra = 2 + when 3; @spriteYExtra = -2 + end + end + self.x = self.x + self.y = self.y + self.visible = @spriteVisible + # Pokémon sprite blinking when targeted + if @selected==2 && @spriteVisible + case (frameCounter/SIXTH_ANIM_PERIOD).floor + when 2, 5; self.visible = false + else; self.visible = true + end + end + @updating = false + end +end + + + +#=============================================================================== +# Shadow sprite for Pokémon (used in battle) +#=============================================================================== +class PokemonBattlerShadowSprite < RPG::Sprite + attr_reader :pkmn + attr_accessor :index + attr_accessor :selected + + def initialize(viewport,sideSize,index) + super(viewport) + @pkmn = nil + @sideSize = sideSize + @index = index + @_iconBitmap = nil + self.visible = false + end + + def dispose + @_iconBitmap.dispose if @_iconBitmap + @_iconBitmap = nil + self.bitmap = nil if !self.disposed? + super + end + + def width; return (self.bitmap) ? self.bitmap.width : 0; end + def height; return (self.bitmap) ? self.bitmap.height : 0; end + + # Set sprite's origin to centre + def pbSetOrigin + return if !@_iconBitmap + self.ox = @_iconBitmap.width/2 + self.oy = @_iconBitmap.height/2 + end + + def pbSetPosition + return if !@_iconBitmap + pbSetOrigin + self.z = 3 + # Set original position + p = PokeBattle_SceneConstants.pbBattlerPosition(@index,@sideSize) + self.x = p[0] + self.y = p[1] + # Apply metrics + pbApplyBattlerMetricsToSprite(self,@index,@pkmn.fSpecies,true) + end + + def setPokemonBitmap(pkmn) + @pkmn = pkmn + @_iconBitmap.dispose if @_iconBitmap + @_iconBitmap = pbLoadPokemonShadowBitmap(@pkmn) + self.bitmap = (@_iconBitmap) ? @_iconBitmap.bitmap : nil + pbSetPosition + end + + def update(frameCounter=0) + return if !@_iconBitmap + # Update bitmap + @_iconBitmap.update + self.bitmap = @_iconBitmap.bitmap + end +end diff --git a/Data/Scripts/011_Battle/005_Battle scene/005_PokeBattle_SceneMenus.rb b/Data/Scripts/011_Battle/005_Battle scene/005_PokeBattle_SceneMenus.rb new file mode 100644 index 000000000..0f35d9383 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/005_PokeBattle_SceneMenus.rb @@ -0,0 +1,548 @@ +#=============================================================================== +# Base class for all three menu classes below +#=============================================================================== +class BattleMenuBase + attr_accessor :x + attr_accessor :y + attr_reader :z + attr_reader :visible + attr_reader :color + attr_reader :index + attr_reader :mode + # NOTE: Button width is half the width of the graphic containing them all. + BUTTON_HEIGHT = 46 + TEXT_BASE_COLOR = PokeBattle_SceneConstants::MESSAGE_BASE_COLOR + TEXT_SHADOW_COLOR = PokeBattle_SceneConstants::MESSAGE_SHADOW_COLOR + + def initialize(viewport=nil) + @x = 0 + @y = 0 + @z = 0 + @visible = false + @color = Color.new(0,0,0,0) + @index = 0 + @mode = 0 + @disposed = false + @sprites = {} + @visibility = {} + end + + def dispose + return if disposed? + pbDisposeSpriteHash(@sprites) + @disposed = true + end + + def disposed?; return @disposed; end + + def z=(value) + @z = value + for i in @sprites + i[1].z = value if !i[1].disposed? + end + end + + def visible=(value) + @visible = value + for i in @sprites + i[1].visible = (value && @visibility[i[0]]) if !i[1].disposed? + end + end + + def color=(value) + @color = value + for i in @sprites + i[1].color = value if !i[1].disposed? + end + end + + def index=(value) + oldValue = @index + @index = value + @cmdWindow.index = @index if @cmdWindow + refresh if @index!=oldValue + end + + def mode=(value) + oldValue = @mode + @mode = value + refresh if @mode!=oldValue + end + + def addSprite(key,sprite) + @sprites[key] = sprite + @visibility[key] = true + end + + def setIndexAndMode(index,mode) + oldIndex = @index + oldMode = @mode + @index = index + @mode = mode + @cmdWindow.index = @index if @cmdWindow + refresh if @index!=oldIndex || @mode!=oldMode + end + + def refresh; end + + def update + pbUpdateSpriteHash(@sprites) + end +end + + + +#=============================================================================== +# Command menu (Fight/Pokémon/Bag/Run) +#=============================================================================== +class CommandMenuDisplay < BattleMenuBase + # If true, displays graphics from Graphics/Pictures/Battle/overlay_command.png + # and Graphics/Pictures/Battle/cursor_command.png. + # If false, just displays text and the command window over the graphic + # Graphics/Pictures/Battle/overlay_message.png. You will need to edit def + # pbShowWindow to make the graphic appear while the command menu is being + # displayed. + USE_GRAPHICS = true + # Lists of which button graphics to use in different situations/types of battle. + MODES = [ + [0,2,1,3], # 0 = Regular battle + [0,2,1,9], # 1 = Regular battle with "Cancel" instead of "Run" + [0,2,1,4], # 2 = Regular battle with "Call" instead of "Run" + [5,7,6,3], # 3 = Safari Zone + [0,8,1,3] # 4 = Bug Catching Contest + ] + + def initialize(viewport,z) + super(viewport) + self.x = 0 + self.y = Graphics.height-96 + # Create message box (shows "What will X do?") + @msgBox = Window_UnformattedTextPokemon.newWithSize("", + self.x+16,self.y+2,220,Graphics.height-self.y,viewport) + @msgBox.baseColor = TEXT_BASE_COLOR + @msgBox.shadowColor = TEXT_SHADOW_COLOR + @msgBox.windowskin = nil + addSprite("msgBox",@msgBox) + if USE_GRAPHICS + # Create background graphic + background = IconSprite.new(self.x,self.y,viewport) + background.setBitmap("Graphics/Pictures/Battle/overlay_command") + addSprite("background",background) + # Create bitmaps + @buttonBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_command")) + # Create action buttons + @buttons = Array.new(4) do |i| # 4 command options, therefore 4 buttons + button = SpriteWrapper.new(viewport) + button.bitmap = @buttonBitmap.bitmap + button.x = self.x+Graphics.width-260 + button.x += (((i%2)==0) ? 0 : @buttonBitmap.width/2-4) + button.y = self.y+6 + button.y += (((i/2)==0) ? 0 : BUTTON_HEIGHT-4) + button.src_rect.width = @buttonBitmap.width/2 + button.src_rect.height = BUTTON_HEIGHT + addSprite("button_#{i}",button) + next button + end + else + # Create command window (shows Fight/Bag/Pokémon/Run) + @cmdWindow = Window_CommandPokemon.newWithSize([], + self.x+Graphics.width-240,self.y,240,Graphics.height-self.y,viewport) + @cmdWindow.columns = 2 + @cmdWindow.columnSpacing = 4 + @cmdWindow.ignore_input = true + addSprite("cmdWindow",@cmdWindow) + end + self.z = z + refresh + end + + def dispose + super + @buttonBitmap.dispose if @buttonBitmap + end + + def z=(value) + super + @msgBox.z += 1 + @cmdWindow.z += 1 if @cmdWindow + end + + def setTexts(value) + @msgBox.text = value[0] + return if USE_GRAPHICS + commands = [] + for i in 1..4 + commands.push(value[i]) if value[i] && value[i]!=nil + end + @cmdWindow.commands = commands + end + + def refreshButtons + return if !USE_GRAPHICS + for i in 0...4 + button = @buttons[i] + button.src_rect.x = (i==@index) ? @buttonBitmap.width/2 : 0 + button.src_rect.y = MODES[@mode][i]*BUTTON_HEIGHT + button.z = self.z + ((i==@index) ? 3 : 2) + end + end + + def refresh + @msgBox.refresh + @cmdWindow.refresh if @cmdWindow + refreshButtons + end +end + + + +#=============================================================================== +# Fight menu (choose a move) +#=============================================================================== +class FightMenuDisplay < BattleMenuBase + attr_reader :battler + attr_reader :shiftMode + + # If true, displays graphics from Graphics/Pictures/Battle/overlay_fight.png + # and Graphics/Pictures/Battle/cursor_fight.png. + # If false, just displays text and the command window over the graphic + # Graphics/Pictures/Battle/overlay_message.png. You will need to edit def + # pbShowWindow to make the graphic appear while the command menu is being + # displayed. + USE_GRAPHICS = true + TYPE_ICON_HEIGHT = 28 + # Text colours of PP of selected move + PP_COLORS = [ + Color.new(248,72,72),Color.new(136,48,48), # Red, zero PP + Color.new(248,136,32),Color.new(144,72,24), # Orange, 1/4 of total PP or less + Color.new(248,192,0),Color.new(144,104,0), # Yellow, 1/2 of total PP or less + TEXT_BASE_COLOR,TEXT_SHADOW_COLOR # Black, more than 1/2 of total PP + ] + MAX_MOVES = 4 # Number of moves to display at once + + def initialize(viewport,z) + super(viewport) + self.x = 0 + self.y = Graphics.height-96 + @battler = nil + @shiftMode = 0 + # NOTE: @mode is for the display of the Mega Evolution button. + # 0=don't show, 1=show unpressed, 2=show pressed + if USE_GRAPHICS + # Create bitmaps + @buttonBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_fight")) + @typeBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/types")) + @megaEvoBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_mega")) + @shiftBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_shift")) + # Create background graphic + background = IconSprite.new(0,Graphics.height-96,viewport) + background.setBitmap("Graphics/Pictures/Battle/overlay_fight") + addSprite("background",background) + # Create move buttons + @buttons = Array.new(MAX_MOVES) do |i| + button = SpriteWrapper.new(viewport) + button.bitmap = @buttonBitmap.bitmap + button.x = self.x+4 + button.x += (((i%2)==0) ? 0 : @buttonBitmap.width/2-4) + button.y = self.y+6 + button.y += (((i/2)==0) ? 0 : BUTTON_HEIGHT-4) + button.src_rect.width = @buttonBitmap.width/2 + button.src_rect.height = BUTTON_HEIGHT + addSprite("button_#{i}",button) + next button + end + # Create overlay for buttons (shows move names) + @overlay = BitmapSprite.new(Graphics.width,Graphics.height-self.y,viewport) + @overlay.x = self.x + @overlay.y = self.y + pbSetNarrowFont(@overlay.bitmap) + addSprite("overlay",@overlay) + # Create overlay for selected move's info (shows move's PP) + @infoOverlay = BitmapSprite.new(Graphics.width,Graphics.height-self.y,viewport) + @infoOverlay.x = self.x + @infoOverlay.y = self.y + pbSetNarrowFont(@infoOverlay.bitmap) + addSprite("infoOverlay",@infoOverlay) + # Create type icon + @typeIcon = SpriteWrapper.new(viewport) + @typeIcon.bitmap = @typeBitmap.bitmap + @typeIcon.x = self.x+416 + @typeIcon.y = self.y+20 + @typeIcon.src_rect.height = TYPE_ICON_HEIGHT + addSprite("typeIcon",@typeIcon) + # Create Mega Evolution button + @megaButton = SpriteWrapper.new(viewport) + @megaButton.bitmap = @megaEvoBitmap.bitmap + @megaButton.x = self.x+146 + @megaButton.y = self.y-@megaEvoBitmap.height/2 + @megaButton.src_rect.height = @megaEvoBitmap.height/2 + addSprite("megaButton",@megaButton) + # Create Shift button + @shiftButton = SpriteWrapper.new(viewport) + @shiftButton.bitmap = @shiftBitmap.bitmap + @shiftButton.x = self.x+4 + @shiftButton.y = self.y-@shiftBitmap.height + addSprite("shiftButton",@shiftButton) + else + # Create message box (shows type and PP of selected move) + @msgBox = Window_AdvancedTextPokemon.newWithSize("", + self.x+320,self.y,Graphics.width-320,Graphics.height-self.y,viewport) + @msgBox.baseColor = TEXT_BASE_COLOR + @msgBox.shadowColor = TEXT_SHADOW_COLOR + pbSetNarrowFont(@msgBox.contents) + addSprite("msgBox",@msgBox) + # Create command window (shows moves) + @cmdWindow = Window_CommandPokemon.newWithSize([], + self.x,self.y,320,Graphics.height-self.y,viewport) + @cmdWindow.columns = 2 + @cmdWindow.columnSpacing = 4 + @cmdWindow.ignore_input = true + pbSetNarrowFont(@cmdWindow.contents) + addSprite("cmdWindow",@cmdWindow) + end + self.z = z + end + + def dispose + super + @buttonBitmap.dispose if @buttonBitmap + @typeBitmap.dispose if @typeBitmap + @megaEvoBitmap.dispose if @megaEvoBitmap + @shiftBitmap.dispose if @shiftBitmap + end + + def z=(value) + super + @msgBox.z += 1 if @msgBox + @cmdWindow.z += 2 if @cmdWindow + @overlay.z += 5 if @overlay + @infoOverlay.z += 6 if @infoOverlay + @typeIcon.z += 1 if @typeIcon + end + + def battler=(value) + @battler = value + refresh + refreshButtonNames + end + + def shiftMode=(value) + oldValue = @shiftMode + @shiftMode = value + refreshShiftButton if @shiftMode!=oldValue + end + + def refreshButtonNames + moves = (@battler) ? @battler.moves : [] + if !USE_GRAPHICS + # Fill in command window + commands = [] + moves.each { |m| commands.push((m && m.id>0) ? m.name : "-") } + @cmdWindow.commands = commands + return + end + # Draw move names onto overlay + @overlay.bitmap.clear + textPos = [] + moves.each_with_index do |m,i| + button = @buttons[i] + next if !@visibility["button_#{i}"] + x = button.x-self.x+button.src_rect.width/2 + y = button.y-self.y+8 + moveNameBase = TEXT_BASE_COLOR + if m.type>=0 + # NOTE: This takes a colour from a particular pixel in the button + # graphic and makes the move name's base colour that same colour. + # The pixel is at coordinates 10,34 in the button box. If you + # change the graphic, you may want to change/remove the below line + # of code to ensure the font is an appropriate colour. + moveNameBase = button.bitmap.get_pixel(10,button.src_rect.y+34) + end + textPos.push([m.name,x,y,2,moveNameBase,TEXT_SHADOW_COLOR]) + end + pbDrawTextPositions(@overlay.bitmap,textPos) + end + + def refreshSelection + moves = (@battler) ? @battler.moves : [] + if USE_GRAPHICS + # Choose appropriate button graphics and z positions + @buttons.each_with_index do |button,i| + if !moves[i] || moves[i].id==0 + @visibility["button_#{i}"] = false + next + end + @visibility["button_#{i}"] = true + button.src_rect.x = (i==@index) ? @buttonBitmap.width/2 : 0 + button.src_rect.y = moves[i].type*BUTTON_HEIGHT + button.z = self.z + ((i==@index) ? 4 : 3) + end + end + refreshMoveData(moves[@index]) + end + + def refreshMoveData(move) + # Write PP and type of the selected move + if !USE_GRAPHICS + moveType = PBTypes.getName(move.type) + if move.totalpp<=0 + @msgBox.text = _INTL("PP: ---
TYPE/{1}",moveType) + else + @msgBox.text = _ISPRINTF("PP: {1: 2d}/{2: 2d}
TYPE/{3:s}", + move.pp,move.totalpp,moveType) + end + return + end + @infoOverlay.bitmap.clear + if !move || move.id==0 + @visibility["typeIcon"] = false + return + end + @visibility["typeIcon"] = true + # Type icon + @typeIcon.src_rect.y = move.type*TYPE_ICON_HEIGHT + # PP text + if move.totalpp>0 + ppFraction = [(4.0*move.pp/move.totalpp).ceil,3].min + textPos = [] + textPos.push([_INTL("PP: {1}/{2}",move.pp,move.totalpp), + 448,50,2,PP_COLORS[ppFraction*2],PP_COLORS[ppFraction*2+1]]) + pbDrawTextPositions(@infoOverlay.bitmap,textPos) + end + end + + def refreshMegaEvolutionButton + return if !USE_GRAPHICS + @megaButton.src_rect.y = (@mode-1)*@megaEvoBitmap.height/2 + @megaButton.z = self.z - 1 + end + + def refreshShiftButton + return if !USE_GRAPHICS + @shiftButton.src_rect.y = (@shiftMode-1)*@shiftBitmap.height + @shiftButton.z = self.z - 1 + end + + def refresh + return if !@battler + refreshSelection + refreshMegaEvolutionButton + refreshShiftButton + end +end + + + +#=============================================================================== +# Target menu (choose a move's target) +# NOTE: Unlike the command and fight menus, this one doesn't have a textbox-only +# version. +#=============================================================================== +class TargetMenuDisplay < BattleMenuBase + attr_accessor :mode + + # Lists of which button graphics to use in different situations/types of battle. + MODES = [ + [0,2,1,3], # 0 = Regular battle + [0,2,1,9], # 1 = Regular battle with "Cancel" instead of "Run" + [0,2,1,4], # 2 = Regular battle with "Call" instead of "Run" + [5,7,6,3], # 3 = Safari Zone + [0,8,1,3] # 4 = Bug Catching Contest + ] + CMD_BUTTON_WIDTH_SMALL = 170 + TEXT_BASE_COLOR = Color.new(240,248,224) + TEXT_SHADOW_COLOR = Color.new(64,64,64) + + def initialize(viewport,z,sideSizes) + super(viewport) + @sideSizes = sideSizes + maxIndex = (@sideSizes[0]>@sideSizes[1]) ? (@sideSizes[0]-1)*2 : @sideSizes[1]*2-1 + @smallButtons = (@sideSizes.max>2) + self.x = 0 + self.y = Graphics.height-96 + @texts = [] + # NOTE: @mode is for which buttons are shown as selected. + # 0=select 1 button (@index), 1=select all buttons with text + # Create bitmaps + @buttonBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_target")) + # Create target buttons + @buttons = Array.new(maxIndex+1) do |i| + numButtons = @sideSizes[i%2] + next if numButtons<=i/2 + # NOTE: Battler indexes go from left to right from the perspective of + # that side's trainer, so inc is different for each side for the + # same value of i/2. + inc = ((i%2)==0) ? i/2 : numButtons-1-i/2 + button = SpriteWrapper.new(viewport) + button.bitmap = @buttonBitmap.bitmap + button.src_rect.width = (@smallButtons) ? CMD_BUTTON_WIDTH_SMALL : @buttonBitmap.width/2 + button.src_rect.height = BUTTON_HEIGHT + if @smallButtons + button.x = self.x+170-[0,82,166][numButtons-1] + else + button.x = self.x+138-[0,116][numButtons-1] + end + button.x += (button.src_rect.width-4)*inc + button.y = self.y+6 + button.y += (BUTTON_HEIGHT-4)*((i+1)%2) + addSprite("button_#{i}",button) + next button + end + # Create overlay (shows target names) + @overlay = BitmapSprite.new(Graphics.width,Graphics.height-self.y,viewport) + @overlay.x = self.x + @overlay.y = self.y + pbSetNarrowFont(@overlay.bitmap) + addSprite("overlay",@overlay) + self.z = z + refresh + end + + def dispose + super + @buttonBitmap.dispose if @buttonBitmap + end + + def z=(value) + super + @overlay.z += 5 if @overlay + end + + def setDetails(texts,mode) + @texts = texts + @mode = mode + refresh + end + + def refreshButtons + # Choose appropriate button graphics and z positions + @buttons.each_with_index do |button,i| + next if !button + sel = false + buttonType = 0 + if @texts[i] + sel ||= (@mode==0 && i==@index) + sel ||= (@mode==1) + buttonType = ((i%2)==0) ? 1 : 2 + end + buttonType = 2*buttonType + ((@smallButtons) ? 1 : 0) + button.src_rect.x = (sel) ? @buttonBitmap.width/2 : 0 + button.src_rect.y = buttonType*BUTTON_HEIGHT + button.z = self.z + ((sel) ? 3 : 2) + end + # Draw target names onto overlay + @overlay.bitmap.clear + textpos = [] + @buttons.each_with_index do |button,i| + next if !button || @texts[i].nil? || @texts[i]=="" + x = button.x-self.x+button.src_rect.width/2 + y = button.y-self.y+8 + textpos.push([@texts[i],x,y,2,TEXT_BASE_COLOR,TEXT_SHADOW_COLOR]) + end + pbDrawTextPositions(@overlay.bitmap,textpos) + end + + def refresh + refreshButtons + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/006_PokeBattle_Scene.rb b/Data/Scripts/011_Battle/005_Battle scene/006_PokeBattle_Scene.rb new file mode 100644 index 000000000..1afdbc543 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/006_PokeBattle_Scene.rb @@ -0,0 +1,351 @@ +# Battle scene (the visuals of the battle) +class PokeBattle_Scene + attr_accessor :abortable # For non-interactive battles, can quit immediately + attr_reader :viewport + attr_reader :sprites + + BLANK = 0 + MESSAGE_BOX = 1 + COMMAND_BOX = 2 + FIGHT_BOX = 3 + TARGET_BOX = 4 + + MESSAGE_PAUSE_TIME = (Graphics.frame_rate*1.0).floor # 1 second + + #============================================================================= + # Updating and refreshing + #============================================================================= + def pbUpdate(cw=nil) + pbGraphicsUpdate + pbInputUpdate + pbFrameUpdate(cw) + end + + def pbGraphicsUpdate + # Update lineup animations + if @animations.length>0 + shouldCompact = false + @animations.each_with_index do |a,i| + a.update + if a.animDone? + a.dispose + @animations[i] = nil + shouldCompact = true + end + end + @animations.compact! if shouldCompact + end + # Update other graphics + @sprites["battle_bg"].update if @sprites["battle_bg"].respond_to?("update") + Graphics.update + @frameCounter += 1 + @frameCounter = @frameCounter%(Graphics.frame_rate*12/20) + end + + def pbInputUpdate + Input.update + if Input.trigger?(Input::B) && @abortable && !@aborted + @aborted = true + @battle.pbAbort + end + end + + def pbFrameUpdate(cw=nil) + cw.update if cw + @battle.battlers.each_with_index do |b,i| + next if !b + @sprites["dataBox_#{i}"].update(@frameCounter) if @sprites["dataBox_#{i}"] + @sprites["pokemon_#{i}"].update(@frameCounter) if @sprites["pokemon_#{i}"] + @sprites["shadow_#{i}"].update(@frameCounter) if @sprites["shadow_#{i}"] + end + end + + def pbRefresh + @battle.battlers.each_with_index do |b,i| + next if !b + @sprites["dataBox_#{i}"].refresh if @sprites["dataBox_#{i}"] + end + end + + def pbRefreshOne(idxBattler) + @sprites["dataBox_#{idxBattler}"].refresh if @sprites["dataBox_#{idxBattler}"] + end + + #============================================================================= + # Party lineup + #============================================================================= + # Returns whether the party line-ups are currently coming on-screen + def inPartyAnimation? + return @animations.length>0 + end + + #============================================================================= + # Window displays + #============================================================================= + def pbShowWindow(windowType) + # NOTE: If you are not using fancy graphics for the command/fight menus, you + # will need to make "messageBox" also visible if the windowtype if + # COMMAND_BOX/FIGHT_BOX respectively. + @sprites["messageBox"].visible = (windowType==MESSAGE_BOX) + @sprites["messageWindow"].visible = (windowType==MESSAGE_BOX) + @sprites["commandWindow"].visible = (windowType==COMMAND_BOX) + @sprites["fightWindow"].visible = (windowType==FIGHT_BOX) + @sprites["targetWindow"].visible = (windowType==TARGET_BOX) + end + + # This is for the end of brief messages, which have been lingering on-screen + # while other things happened. This is only called when another message wants + # to be shown, and makes the brief message linger for one more second first. + # Some animations skip this extra second by setting @briefMessage to false + # despite not having any other messages to show. + def pbWaitMessage + return if !@briefMessage + pbShowWindow(MESSAGE_BOX) + cw = @sprites["messageWindow"] + MESSAGE_PAUSE_TIME.times do + pbUpdate(cw) + end + cw.text = "" + cw.visible = false + @briefMessage = false + end + + # NOTE: A regular message is displayed for 1 second after it fully appears (or + # less if B/C is pressed) and disappears automatically after that time. + def pbDisplayMessage(msg,brief=false) + pbWaitMessage + pbShowWindow(MESSAGE_BOX) + cw = @sprites["messageWindow"] + cw.setText(msg) + PBDebug.log(msg) + yielded = false + i = 0 + loop do + pbUpdate(cw) + if !cw.busy? + if !yielded + yield if block_given? # For playing SE as soon as the message is all shown + yielded = true + end + if brief + # NOTE: A brief message lingers on-screen while other things happen. A + # regular message has to end before the game can continue. + @briefMessage = true + break + end + if i>=MESSAGE_PAUSE_TIME # Autoclose after 1 second + cw.text = "" + cw.visible = false + break + end + i += 1 + end + if Input.trigger?(Input::B) || Input.trigger?(Input::C) || @abortable + if cw.busy? + pbPlayDecisionSE if cw.pausing? && !@abortable + cw.skipAhead + elsif !@abortable + cw.text = "" + cw.visible = false + break + end + end + end + end + alias pbDisplay pbDisplayMessage + + # NOTE: A paused message has the arrow in the bottom corner indicating there + # is another message immediately afterward. It is displayed for 3 + # seconds after it fully appears (or less if B/C is pressed) and + # disappears automatically after that time, except at the end of battle. + def pbDisplayPausedMessage(msg) + pbWaitMessage + pbShowWindow(MESSAGE_BOX) + cw = @sprites["messageWindow"] + cw.text = _INTL("{1}\1",msg) + PBDebug.log(msg) + yielded = false + i = 0 + loop do + pbUpdate(cw) + if !cw.busy? + if !yielded + yield if block_given? # For playing SE as soon as the message is all shown + yielded = true + end + if !@battleEnd + if i>=MESSAGE_PAUSE_TIME*3 # Autoclose after 3 seconds + cw.text = "" + cw.visible = false + break + end + i += 1 + end + end + if Input.trigger?(Input::B) || Input.trigger?(Input::C) || @abortable + if cw.busy? + pbPlayDecisionSE if cw.pausing? && !@abortable + cw.skipAhead + elsif !@abortable + cw.text = "" + pbPlayDecisionSE + break + end + end + end + end + + def pbDisplayConfirmMessage(msg) + return pbShowCommands(msg,[_INTL("Yes"),_INTL("No")],1)==0 + end + + def pbShowCommands(msg,commands,defaultValue) + pbWaitMessage + pbShowWindow(MESSAGE_BOX) + dw = @sprites["messageWindow"] + dw.text = msg + cw = Window_CommandPokemon.new(commands) + cw.x = Graphics.width-cw.width + cw.y = Graphics.height-cw.height-dw.height + cw.z = dw.z+1 + cw.index = 0 + cw.viewport = @viewport + PBDebug.log(msg) + loop do + cw.visible = (!dw.busy?) + pbUpdate(cw) + dw.update + if Input.trigger?(Input::B) && defaultValue>=0 + if dw.busy? + pbPlayDecisionSE if dw.pausing? + dw.resume + else + cw.dispose + dw.text = "" + return defaultValue + end + elsif Input.trigger?(Input::C) + if dw.busy? + pbPlayDecisionSE if dw.pausing? + dw.resume + else + cw.dispose + dw.text = "" + return cw.index + end + end + end + end + + #============================================================================= + # Sprites + #============================================================================= + def pbAddSprite(id,x,y,filename,viewport) + sprite = IconSprite.new(x,y,viewport) + if filename + sprite.setBitmap(filename) rescue nil + end + @sprites[id] = sprite + return sprite + end + + def pbAddPlane(id,filename,viewport) + sprite = AnimatedPlane.new(viewport) + if filename + sprite.setBitmap(filename) + end + @sprites[id] = sprite + return sprite + end + + def pbDisposeSprites + pbDisposeSpriteHash(@sprites) + end + + # Used by Ally Switch. + def pbSwapBattlerSprites(idxA,idxB) + @sprites["pokemon_#{idxA}"], @sprites["pokemon_#{idxB}"] = @sprites["pokemon_#{idxB}"], @sprites["pokemon_#{idxA}"] + @sprites["shadow_#{idxA}"], @sprites["shadow_#{idxB}"] = @sprites["shadow_#{idxB}"], @sprites["shadow_#{idxA}"] + @lastCmd[idxA], @lastCmd[idxB] = @lastCmd[idxB], @lastCmd[idxA] + @lastMove[idxA], @lastMove[idxB] = @lastMove[idxB], @lastMove[idxA] + [idxA,idxB].each do |i| + @sprites["pokemon_#{i}"].index = i + @sprites["pokemon_#{i}"].pbSetPosition + @sprites["shadow_#{i}"].index = i + @sprites["shadow_#{i}"].pbSetPosition + @sprites["dataBox_#{i}"].battler = @battle.battlers[i] + end + pbRefresh + end + + #============================================================================= + # Phases + #============================================================================= + def pbBeginCommandPhase + @sprites["messageWindow"].text = "" + end + + def pbBeginAttackPhase + pbSelectBattler(-1) + pbShowWindow(MESSAGE_BOX) + end + + def pbBeginEndOfRoundPhase + end + + def pbEndBattle(result) + @abortable = false + pbShowWindow(BLANK) + # Fade out all sprites + pbBGMFade(1.0) + pbFadeOutAndHide(@sprites) + pbDisposeSprites + end + + #============================================================================= + # + #============================================================================= + def pbSelectBattler(idxBattler,selectMode=1) + numWindows = @battle.sideSizes.max*2 + for i in 0...numWindows + sel = (idxBattler.is_a?(Array)) ? !idxBattler[i].nil? : i==idxBattler + selVal = (sel) ? selectMode : 0 + @sprites["dataBox_#{i}"].selected = selVal if @sprites["dataBox_#{i}"] + @sprites["pokemon_#{i}"].selected = selVal if @sprites["pokemon_#{i}"] + end + end + + def pbChangePokemon(idxBattler,pkmn) + idxBattler = idxBattler.index if idxBattler.respond_to?("index") + pkmnSprite = @sprites["pokemon_#{idxBattler}"] + shadowSprite = @sprites["shadow_#{idxBattler}"] + back = !@battle.opposes?(idxBattler) + pkmnSprite.setPokemonBitmap(pkmn,back) + shadowSprite.setPokemonBitmap(pkmn) + # Set visibility of battler's shadow + if shadowSprite && !back + shadowSprite.visible = showShadow?(pkmn.fSpecies) + end + end + + def pbResetMoveIndex(idxBattler) + @lastMove[idxBattler] = 0 + end + + #============================================================================= + # + #============================================================================= + # This method is called when the player wins a wild Pokémon battle. + # This method can change the battle's music for example. + def pbWildBattleSuccess + @battleEnd = true + pbBGMPlay(pbGetWildVictoryME) + end + + # This method is called when the player wins a trainer battle. + # This method can change the battle's music for example. + def pbTrainerBattleSuccess + @battleEnd = true + pbBGMPlay(pbGetTrainerVictoryME(@battle.opponent)) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/007_Scene_Initialize.rb b/Data/Scripts/011_Battle/005_Battle scene/007_Scene_Initialize.rb new file mode 100644 index 000000000..0bc8e2fcf --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/007_Scene_Initialize.rb @@ -0,0 +1,191 @@ +class PokeBattle_Scene + #============================================================================= + # Create the battle scene and its elements + #============================================================================= + def initialize + @battle = nil + @abortable = false + @aborted = false + @battleEnd = false + @animations = [] + @frameCounter = 0 + end + + # Called whenever the battle begins. + def pbStartBattle(battle) + @battle = battle + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @lastCmd = Array.new(@battle.battlers.length,0) + @lastMove = Array.new(@battle.battlers.length,0) + pbInitSprites + pbBattleIntroAnimation + end + + def pbInitSprites + @sprites = {} + # The background image and each side's base graphic + pbCreateBackdropSprites + # Create message box graphic + messageBox = pbAddSprite("messageBox",0,Graphics.height-96, + "Graphics/Pictures/Battle/overlay_message",@viewport) + messageBox.z = 195 + # Create message window (displays the message) + msgWindow = Window_AdvancedTextPokemon.newWithSize("", + 16,Graphics.height-96+2,Graphics.width-32,96,@viewport) + msgWindow.z = 200 + msgWindow.opacity = 0 + msgWindow.baseColor = PokeBattle_SceneConstants::MESSAGE_BASE_COLOR + msgWindow.shadowColor = PokeBattle_SceneConstants::MESSAGE_SHADOW_COLOR + msgWindow.letterbyletter = true + @sprites["messageWindow"] = msgWindow + # Create command window + @sprites["commandWindow"] = CommandMenuDisplay.new(@viewport,200) + # Create fight window + @sprites["fightWindow"] = FightMenuDisplay.new(@viewport,200) + # Create targeting window + @sprites["targetWindow"] = TargetMenuDisplay.new(@viewport,200,@battle.sideSizes) + pbShowWindow(MESSAGE_BOX) + # The party lineup graphics (bar and balls) for both sides + for side in 0...2 + partyBar = pbAddSprite("partyBar_#{side}",0,0, + "Graphics/Pictures/Battle/overlay_lineup",@viewport) + partyBar.z = 120 + partyBar.mirror = true if side==0 # Player's lineup bar only + partyBar.visible = false + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + ball = pbAddSprite("partyBall_#{side}_#{i}",0,0,nil,@viewport) + ball.z = 121 + ball.visible = false + end + # Ability splash bars + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @sprites["abilityBar_#{side}"] = AbilitySplashBar.new(side,@viewport) + end + end + # Player's and partner trainer's back sprite + @battle.player.each_with_index do |p,i| + pbCreateTrainerBackSprite(i,p.trainertype,@battle.player.length) + end + # Opposing trainer(s) sprites + if @battle.trainerBattle? + @battle.opponent.each_with_index do |p,i| + pbCreateTrainerFrontSprite(i,p.trainertype,@battle.opponent.length) + end + end + # Data boxes and Pokémon sprites + @battle.battlers.each_with_index do |b,i| + next if !b + @sprites["dataBox_#{i}"] = PokemonDataBox.new(b,@battle.pbSideSize(i),@viewport) + pbCreatePokemonSprite(i) + end + # Wild battle, so set up the Pokémon sprite(s) accordingly + if @battle.wildBattle? + @battle.pbParty(1).each_with_index do |pkmn,i| + index = i*2+1 + pbChangePokemon(index,pkmn) + pkmnSprite = @sprites["pokemon_#{index}"] + pkmnSprite.tone = Tone.new(-80,-80,-80) + pkmnSprite.visible = true + end + end + end + + def pbCreateBackdropSprites + case @battle.time + when 1; time = "eve" + when 2; time = "night" + end + # Put everything together into backdrop, bases and message bar filenames + backdropFilename = @battle.backdrop + baseFilename = @battle.backdrop + baseFilename = sprintf("%s_%s",baseFilename,@battle.backdropBase) if @battle.backdropBase + messageFilename = @battle.backdrop + if time + trialName = sprintf("%s_%s",backdropFilename,time) + if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_bg")) + backdropFilename = trialName + end + trialName = sprintf("%s_%s",baseFilename,time) + if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_base0")) + baseFilename = trialName + end + trialName = sprintf("%s_%s",messageFilename,time) + if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_message")) + messageFilename = trialName + end + end + if !pbResolveBitmap(sprintf("Graphics/Battlebacks/"+baseFilename+"_base0")) && + @battle.backdropBase + baseFilename = @battle.backdropBase + if time + trialName = sprintf("%s_%s",baseFilename,time) + if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_base0")) + baseFilename = trialName + end + end + end + # Finalise filenames + battleBG = "Graphics/Battlebacks/"+backdropFilename+"_bg" + playerBase = "Graphics/Battlebacks/"+baseFilename+"_base0" + enemyBase = "Graphics/Battlebacks/"+baseFilename+"_base1" + messageBG = "Graphics/Battlebacks/"+messageFilename+"_message" + # Apply graphics + bg = pbAddSprite("battle_bg",0,0,battleBG,@viewport) + bg.z = 0 + bg = pbAddSprite("battle_bg2",-Graphics.width,0,battleBG,@viewport) + bg.z = 0 + bg.mirror = true + for side in 0...2 + baseX, baseY = PokeBattle_SceneConstants.pbBattlerPosition(side) + base = pbAddSprite("base_#{side}",baseX,baseY, + (side==0) ? playerBase : enemyBase,@viewport) + base.z = 1 + if base.bitmap + base.ox = base.bitmap.width/2 + base.oy = (side==0) ? base.bitmap.height : base.bitmap.height/2 + end + end + cmdBarBG = pbAddSprite("cmdBar_bg",0,Graphics.height-96,messageBG,@viewport) + cmdBarBG.z = 180 + end + + def pbCreateTrainerBackSprite(idxTrainer,trainerType,numTrainers=1) + if idxTrainer==0 # Player's sprite + trainerFile = pbPlayerSpriteBackFile(trainerType) + else # Partner trainer's sprite + trainerFile = pbTrainerSpriteBackFile(trainerType) + end + spriteX, spriteY = PokeBattle_SceneConstants.pbTrainerPosition(0,idxTrainer,numTrainers) + trainer = pbAddSprite("player_#{idxTrainer+1}",spriteX,spriteY,trainerFile,@viewport) + return if !trainer.bitmap + # Alter position of sprite + trainer.z = 30+idxTrainer + if trainer.bitmap.width>trainer.bitmap.height*2 + trainer.src_rect.x = 0 + trainer.src_rect.width = trainer.bitmap.width/5 + end + trainer.ox = trainer.src_rect.width/2 + trainer.oy = trainer.bitmap.height + end + + def pbCreateTrainerFrontSprite(idxTrainer,trainerType,numTrainers=1) + trainerFile = pbTrainerSpriteFile(trainerType) + spriteX, spriteY = PokeBattle_SceneConstants.pbTrainerPosition(1,idxTrainer,numTrainers) + trainer = pbAddSprite("trainer_#{idxTrainer+1}",spriteX,spriteY,trainerFile,@viewport) + return if !trainer.bitmap + # Alter position of sprite + trainer.z = 7+idxTrainer + trainer.ox = trainer.src_rect.width/2 + trainer.oy = trainer.bitmap.height + end + + def pbCreatePokemonSprite(idxBattler) + sideSize = @battle.pbSideSize(idxBattler) + batSprite = PokemonBattlerSprite.new(@viewport,sideSize,idxBattler,@animations) + @sprites["pokemon_#{idxBattler}"] = batSprite + shaSprite = PokemonBattlerShadowSprite.new(@viewport,sideSize,idxBattler) + shaSprite.visible = false + @sprites["shadow_#{idxBattler}"] = shaSprite + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/008_Scene_Commands.rb b/Data/Scripts/011_Battle/005_Battle scene/008_Scene_Commands.rb new file mode 100644 index 000000000..ecb59e9f2 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/008_Scene_Commands.rb @@ -0,0 +1,467 @@ +class PokeBattle_Scene + #============================================================================= + # The player chooses a main command for a Pokémon + # Return values: -1=Cancel, 0=Fight, 1=Bag, 2=Pokémon, 3=Run, 4=Call + #============================================================================= + def pbCommandMenu(idxBattler,firstAction) + shadowTrainer = (hasConst?(PBTypes,:SHADOW) && @battle.trainerBattle?) + cmds = [ + _INTL("What will\n{1} do?",@battle.battlers[idxBattler].name), + _INTL("Fight"), + _INTL("Bag"), + _INTL("Pokémon"), + (shadowTrainer) ? _INTL("Call") : (firstAction) ? _INTL("Run") : _INTL("Cancel") + ] + ret = pbCommandMenuEx(idxBattler,cmds,(shadowTrainer) ? 2 : (firstAction) ? 0 : 1) + ret = 4 if ret==3 && shadowTrainer # Convert "Run" to "Call" + ret = -1 if ret==3 && !firstAction # Convert "Run" to "Cancel" + return ret + end + + # Mode: 0 = regular battle with "Run" (first choosable action in the round only) + # 1 = regular battle with "Cancel" + # 2 = regular battle with "Call" (for Shadow Pokémon battles) + # 3 = Safari Zone + # 4 = Bug Catching Contest + def pbCommandMenuEx(idxBattler,texts,mode=0) + pbShowWindow(COMMAND_BOX) + cw = @sprites["commandWindow"] + cw.setTexts(texts) + cw.setIndexAndMode(@lastCmd[idxBattler],mode) + pbSelectBattler(idxBattler) + ret = -1 + loop do + oldIndex = cw.index + pbUpdate(cw) + # Update selected command + if Input.trigger?(Input::LEFT) + cw.index -= 1 if (cw.index&1)==1 + elsif Input.trigger?(Input::RIGHT) + cw.index += 1 if (cw.index&1)==0 + elsif Input.trigger?(Input::UP) + cw.index -= 2 if (cw.index&2)==2 + elsif Input.trigger?(Input::DOWN) + cw.index += 2 if (cw.index&2)==0 + end + pbPlayCursorSE if cw.index!=oldIndex + # Actions + if Input.trigger?(Input::C) # Confirm choice + pbPlayDecisionSE + ret = cw.index + @lastCmd[idxBattler] = ret + break + elsif Input.trigger?(Input::B) && mode==1 # Cancel + pbPlayCancelSE + break + elsif Input.trigger?(Input::F9) && $DEBUG # Debug menu + pbPlayDecisionSE + ret = -2 + break + end + end + return ret + end + + #============================================================================= + # The player chooses a move for a Pokémon to use + #============================================================================= + def pbFightMenu(idxBattler,megaEvoPossible=false) + battler = @battle.battlers[idxBattler] + cw = @sprites["fightWindow"] + cw.battler = battler + moveIndex = 0 + if battler.moves[@lastMove[idxBattler]] && battler.moves[@lastMove[idxBattler]].id>0 + moveIndex = @lastMove[idxBattler] + end + cw.shiftMode = (@battle.pbCanShift?(idxBattler)) ? 1 : 0 + cw.setIndexAndMode(moveIndex,(megaEvoPossible) ? 1 : 0) + needFullRefresh = true + needRefresh = false + loop do + # Refresh view if necessary + if needFullRefresh + pbShowWindow(FIGHT_BOX) + pbSelectBattler(idxBattler) + needFullRefresh = false + end + if needRefresh + if megaEvoPossible + newMode = (@battle.pbRegisteredMegaEvolution?(idxBattler)) ? 2 : 1 + cw.mode = newMode if newMode!=cw.mode + end + needRefresh = false + end + oldIndex = cw.index + # General update + pbUpdate(cw) + # Update selected command + if Input.trigger?(Input::LEFT) + cw.index -= 1 if (cw.index&1)==1 + elsif Input.trigger?(Input::RIGHT) + if battler.moves[cw.index+1] && battler.moves[cw.index+1].id>0 + cw.index += 1 if (cw.index&1)==0 + end + elsif Input.trigger?(Input::UP) + cw.index -= 2 if (cw.index&2)==2 + elsif Input.trigger?(Input::DOWN) + if battler.moves[cw.index+2] && battler.moves[cw.index+2].id>0 + cw.index += 2 if (cw.index&2)==0 + end + end + pbPlayCursorSE if cw.index!=oldIndex + # Actions + if Input.trigger?(Input::C) # Confirm choice + pbPlayDecisionSE + break if yield cw.index + needFullRefresh = true + needRefresh = true + elsif Input.trigger?(Input::B) # Cancel fight menu + pbPlayCancelSE + break if yield -1 + needRefresh = true + elsif Input.trigger?(Input::A) # Toggle Mega Evolution + if megaEvoPossible + pbPlayDecisionSE + break if yield -2 + needRefresh = true + end + elsif Input.trigger?(Input::F5) # Shift + if cw.shiftMode>0 + pbPlayDecisionSE + break if yield -3 + needRefresh = true + end + end + end + @lastMove[idxBattler] = cw.index + end + + #============================================================================= + # Opens the party screen to choose a Pokémon to switch in (or just view its + # summary screens) + #============================================================================= + def pbPartyScreen(idxBattler,canCancel=false) + # Fade out and hide all sprites + visibleSprites = pbFadeOutAndHide(@sprites) + # Get player's party + party = @battle.pbParty(idxBattler) + partyPos = @battle.pbPartyOrder(idxBattler) + partyStart, partyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler) + modParty = @battle.pbPlayerDisplayParty(idxBattler) + # Start party screen + scene = PokemonParty_Scene.new + switchScreen = PokemonPartyScreen.new(scene,modParty) + switchScreen.pbStartScene(_INTL("Choose a Pokémon."),@battle.pbNumPositions(0,0)) + # Loop while in party screen + loop do + # Select a Pokémon + scene.pbSetHelpText(_INTL("Choose a Pokémon.")) + idxParty = switchScreen.pbChoosePokemon + if idxParty<0 + next if !canCancel + break + end + # Choose a command for the selected Pokémon + cmdSwitch = -1 + cmdSummary = -1 + commands = [] + commands[cmdSwitch = commands.length] = _INTL("Switch In") if modParty[idxParty].able? + commands[cmdSummary = commands.length] = _INTL("Summary") + commands[commands.length] = _INTL("Cancel") + command = scene.pbShowCommands(_INTL("Do what with {1}?",modParty[idxParty].name),commands) + if cmdSwitch>=0 && command==cmdSwitch # Switch In + idxPartyRet = -1 + partyPos.each_with_index do |pos,i| + next if pos!=idxParty+partyStart + idxPartyRet = i + break + end + break if yield idxPartyRet, switchScreen + elsif cmdSummary>=0 && command==cmdSummary # Summary + scene.pbSummary(idxParty,true) + end + end + # Close party screen + switchScreen.pbEndScene + # Fade back into battle screen + pbFadeInAndShow(@sprites,visibleSprites) + end + + #============================================================================= + # Opens the Bag screen and chooses an item to use + #============================================================================= + def pbItemMenu(idxBattler,firstAction) + # Fade out and hide all sprites + visibleSprites = pbFadeOutAndHide(@sprites) + # Set Bag starting positions + oldLastPocket = $PokemonBag.lastpocket + oldChoices = $PokemonBag.getAllChoices + $PokemonBag.lastpocket = @bagLastPocket if @bagLastPocket!=nil + $PokemonBag.setAllChoices(@bagChoices) if @bagChoices!=nil + # Start Bag screen + itemScene = PokemonBag_Scene.new + itemScene.pbStartScene($PokemonBag,true,Proc.new { |item| + useType = pbGetItemData(item,ITEM_BATTLE_USE) + next useType && useType>0 + },false) + # Loop while in Bag screen + wasTargeting = false + loop do + # Select an item + item = itemScene.pbChooseItem + break if item==0 + # Choose a command for the selected item + itemName = PBItems.getName(item) + useType = pbGetItemData(item,ITEM_BATTLE_USE) + cmdUse = -1 + commands = [] + commands[cmdUse = commands.length] = _INTL("Use") if useType && useType!=0 + commands[commands.length] = _INTL("Cancel") + command = itemScene.pbShowCommands(_INTL("{1} is selected.",itemName),commands) + next unless cmdUse>=0 && command==cmdUse # Use + # Use types: + # 0 = not usable in battle + # 1 = use on Pokémon (lots of items), consumed + # 2 = use on Pokémon's move (Ethers), consumed + # 3 = use on battler (X items, Persim Berry), consumed + # 4 = use on opposing battler (Poké Balls), consumed + # 5 = use no target (Poké Doll, Guard Spec., Launcher items), consumed + # 6 = use on Pokémon (Blue Flute), not consumed + # 7 = use on Pokémon's move, not consumed + # 8 = use on battler (Red/Yellow Flutes), not consumed + # 9 = use on opposing battler, not consumed + # 10 = use no target (Poké Flute), not consumed + case useType + when 1, 2, 3, 6, 7, 8 # Use on Pokémon/Pokémon's move/battler + # Auto-choose the Pokémon/battler whose action is being decided if they + # are the only available Pokémon/battler to use the item on + case useType + when 1, 6 # Use on Pokémon + if @battle.pbTeamLengthFromBattlerIndex(idxBattler)==1 + break if yield item, useType, @battle.battlers[idxBattler].pokemonIndex, -1, itemScene + end + when 3, 8 # Use on battler + if @battle.pbPlayerBattlerCount==1 + break if yield item, useType, @battle.battlers[idxBattler].pokemonIndex, -1, itemScene + end + end + # Fade out and hide Bag screen + itemScene.pbFadeOutScene + # Get player's party + party = @battle.pbParty(idxBattler) + partyPos = @battle.pbPartyOrder(idxBattler) + partyStart, partyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler) + modParty = @battle.pbPlayerDisplayParty(idxBattler) + # Start party screen + pkmnScene = PokemonParty_Scene.new + pkmnScreen = PokemonPartyScreen.new(pkmnScene,modParty) + pkmnScreen.pbStartScene(_INTL("Use on which Pokémon?"),@battle.pbNumPositions(0,0)) + idxParty = -1 + # Loop while in party screen + loop do + # Select a Pokémon + pkmnScene.pbSetHelpText(_INTL("Use on which Pokémon?")) + idxParty = pkmnScreen.pbChoosePokemon + break if idxParty<0 + idxPartyRet = -1 + partyPos.each_with_index do |pos,i| + next if pos!=idxParty+partyStart + idxPartyRet = i + break + end + next if idxPartyRet<0 + pkmn = party[idxPartyRet] + next if !pkmn || pkmn.egg? + idxMove = -1 + if useType==2 || useType==7 # Use on Pokémon's move + idxMove = pkmnScreen.pbChooseMove(pkmn,_INTL("Restore which move?")) + next if idxMove<0 + end + break if yield item, useType, idxPartyRet, idxMove, pkmnScene + end + pkmnScene.pbEndScene + break if idxParty>=0 + # Cancelled choosing a Pokémon; show the Bag screen again + itemScene.pbFadeInScene + when 4, 9 # Use on opposing battler (Poké Balls) + idxTarget = -1 + if @battle.pbOpposingBattlerCount(idxBattler)==1 + @battle.eachOtherSideBattler(idxBattler) { |b| idxTarget = b.index } + break if yield item, useType, idxTarget, -1, itemScene + else + wasTargeting = true + # Fade out and hide Bag screen + itemScene.pbFadeOutScene + # Fade in and show the battle screen, choosing a target + tempVisibleSprites = visibleSprites.clone + tempVisibleSprites["commandWindow"] = false + tempVisibleSprites["targetWindow"] = true + idxTarget = pbChooseTarget(idxBattler,PBTargets::Foe,tempVisibleSprites) + if idxTarget>=0 + break if yield item, useType, idxTarget, -1, self + end + # Target invalid/cancelled choosing a target; show the Bag screen again + wasTargeting = false + pbFadeOutAndHide(@sprites) + itemScene.pbFadeInScene + end + when 5, 10 # Use with no target + break if yield item, useType, idxBattler, -1, itemScene + end + end + @bagLastPocket = $PokemonBag.lastpocket + @bagChoices = $PokemonBag.getAllChoices + $PokemonBag.lastpocket = oldLastPocket + $PokemonBag.setAllChoices(oldChoices) + # Close Bag screen + itemScene.pbEndScene + # Fade back into battle screen (if not already showing it) + pbFadeInAndShow(@sprites,visibleSprites) if !wasTargeting + end + + #============================================================================= + # The player chooses a target battler for a move/item (non-single battles only) + #============================================================================= + # Returns an array containing battler names to display when choosing a move's + # target. + # nil means can't select that position, "" means can select that position but + # there is no battler there, otherwise is a battler's name. + def pbCreateTargetTexts(idxBattler,targetType) + texts = Array.new(@battle.battlers.length) do |i| + next nil if !@battle.battlers[i] + showName = false + case targetType + when PBTargets::None, PBTargets::User, PBTargets::RandomNearFoe + showName = (i==idxBattler) + when PBTargets::UserSide, PBTargets::UserAndAllies + showName = !@battle.opposes?(i,idxBattler) + when PBTargets::FoeSide, PBTargets::AllFoes + showName = @battle.opposes?(i,idxBattler) + when PBTargets::BothSides, PBTargets::AllBattlers + showName = true + else + showName = @battle.pbMoveCanTarget?(i,idxBattler,targetType) + end + next nil if !showName + next (@battle.battlers[i].fainted?) ? "" : @battle.battlers[i].name + end + return texts + end + + # Returns the initial position of the cursor when choosing a target for a move + # in a non-single battle. + def pbFirstTarget(idxBattler,targetType) + case targetType + when PBTargets::NearAlly + @battle.eachSameSideBattler(idxBattler) do |b| + next if b.index==idxBattler || !@battle.nearBattlers?(b,idxBattler) + next if b.fainted? + return b.index + end + @battle.eachSameSideBattler(idxBattler) do |b| + next if b.index==idxBattler || !@battle.nearBattlers?(b,idxBattler) + return b.index + end + when PBTargets::NearFoe, PBTargets::NearOther + indices = @battle.pbGetOpposingIndicesInOrder(idxBattler) + indices.each { |i| return i if @battle.nearBattlers?(i,idxBattler) && !@battle.battlers[i].fainted? } + indices.each { |i| return i if @battle.nearBattlers?(i,idxBattler) } + when PBTargets::Foe, PBTargets::Other + indices = @battle.pbGetOpposingIndicesInOrder(idxBattler) + indices.each { |i| return i if !@battle.battlers[i].fainted? } + indices.each { |i| return i } + end + return idxBattler + end + + def pbChooseTarget(idxBattler,targetType,visibleSprites=nil) + pbShowWindow(TARGET_BOX) + cw = @sprites["targetWindow"] + # Create an array of battler names (only valid targets are named) + texts = pbCreateTargetTexts(idxBattler,targetType) + # Determine mode based on targetType + mode = (PBTargets.oneTarget?(targetType)) ? 0 : 1 + cw.setDetails(texts,mode) + cw.index = pbFirstTarget(idxBattler,targetType) + pbSelectBattler((mode==0) ? cw.index : texts,2) # Select initial battler/data box + pbFadeInAndShow(@sprites,visibleSprites) if visibleSprites + ret = -1 + loop do + oldIndex = cw.index + pbUpdate(cw) + # Update selected command + if mode==0 # Choosing just one target, can change index + if Input.trigger?(Input::LEFT) || Input.trigger?(Input::RIGHT) + inc = ((cw.index%2)==0) ? -2 : 2 + inc *= -1 if Input.trigger?(Input::RIGHT) + indexLength = @battle.sideSizes[cw.index%2]*2 + newIndex = cw.index + loop do + newIndex += inc + break if newIndex<0 || newIndex>=indexLength + next if texts[newIndex].nil? + cw.index = newIndex + break + end + elsif (Input.trigger?(Input::UP) && (cw.index%2)==0) || + (Input.trigger?(Input::DOWN) && (cw.index%2)==1) + tryIndex = @battle.pbGetOpposingIndicesInOrder(cw.index) + tryIndex.each do |idxBattlerTry| + next if texts[idxBattlerTry].nil? + cw.index = idxBattlerTry + break + end + end + if cw.index!=oldIndex + pbPlayCursorSE + pbSelectBattler(cw.index,2) # Select the new battler/data box + end + end + if Input.trigger?(Input::C) # Confirm + ret = cw.index + pbPlayDecisionSE + break + elsif Input.trigger?(Input::B) # Cancel + ret = -1 + pbPlayCancelSE + break + end + end + pbSelectBattler(-1) # Deselect all battlers/data boxes + return ret + end + + #============================================================================= + # Opens a Pokémon's summary screen to try to learn a new move + #============================================================================= + # Called whenever a Pokémon should forget a move. It should return -1 if the + # selection is canceled, or 0 to 3 to indicate the move to forget. It should + # not allow HM moves to be forgotten. + def pbForgetMove(pkmn,moveToLearn) + ret = -1 + pbFadeOutIn { + scene = PokemonSummary_Scene.new + screen = PokemonSummaryScreen.new(scene) + ret = screen.pbStartForgetScreen([pkmn],0,moveToLearn) + } + return ret + end + + #============================================================================= + # Opens the nicknaming screen for a newly caught Pokémon + #============================================================================= + def pbNameEntry(helpText,pkmn) + return pbEnterPokemonName(helpText,0,PokeBattle_Pokemon::MAX_POKEMON_NAME_SIZE,"",pkmn) + end + + #============================================================================= + # Shows the Pokédex entry screen for a newly caught Pokémon + #============================================================================= + def pbShowPokedex(species) + pbFadeOutIn { + scene = PokemonPokedexInfo_Scene.new + screen = PokemonPokedexInfoScreen.new(scene) + screen.pbDexEntry(species) + } + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/009_Scene_Animations.rb b/Data/Scripts/011_Battle/005_Battle scene/009_Scene_Animations.rb new file mode 100644 index 000000000..36a3b381b --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/009_Scene_Animations.rb @@ -0,0 +1,542 @@ +class PokeBattle_Scene + #============================================================================= + # Animates the battle intro + #============================================================================= + def pbBattleIntroAnimation + # Make everything appear + introAnim = BattleIntroAnimation.new(@sprites,@viewport,@battle) + loop do + introAnim.update + pbUpdate + break if introAnim.animDone? + end + introAnim.dispose + # Post-appearance activities + # Trainer battle: get ready to show the party lineups (they are brought + # on-screen by a separate animation) + if @battle.trainerBattle? + # NOTE: Here is where you'd make trainer sprites animate if they had an + # entrance animation. Be sure to set it up like a Pokémon entrance + # animation, i.e. add them to @animations so that they can play out + # while party lineups appear and messages show. + pbShowPartyLineup(0,true) + pbShowPartyLineup(1,true) + return + end + # Wild battle: play wild Pokémon's intro animations (including cry), show + # data box(es), return the wild Pokémon's sprite(s) to normal colour, show + # shiny animation(s) + # Set up data box animation + for i in 0...@battle.sideSizes[1] + idxBattler = 2*i+1 + next if !@battle.battlers[idxBattler] + dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,idxBattler) + @animations.push(dataBoxAnim) + end + # Set up wild Pokémon returning to normal colour and playing intro + # animations (including cry) + @animations.push(BattleIntroAnimation2.new(@sprites,@viewport,@battle.sideSizes[1])) + # Play all the animations + while inPartyAnimation?; pbUpdate; end + # Show shiny animation for wild Pokémon + if @battle.showAnims + for i in 0...@battle.sideSizes[1] + idxBattler = 2*i+1 + next if !@battle.battlers[idxBattler] || !@battle.battlers[idxBattler].shiny? + pbCommonAnimation("Shiny",@battle.battlers[idxBattler]) + end + end + end + + #============================================================================= + # Animates a party lineup appearing for the given side + #============================================================================= + def pbShowPartyLineup(side,fullAnim=false) + @animations.push(LineupAppearAnimation.new(@sprites,@viewport, + side,@battle.pbParty(side),@battle.pbPartyStarts(side),fullAnim)) + if !fullAnim + while inPartyAnimation?; pbUpdate; end + end + end + + #============================================================================= + # Animates an opposing trainer sliding in from off-screen. Will animate a + # previous trainer that is already on-screen slide off first. Used at the end + # of battle. + #============================================================================= + def pbShowOpponent(idxTrainer) + # Set up trainer appearing animation + appearAnim = TrainerAppearAnimation.new(@sprites,@viewport,idxTrainer) + @animations.push(appearAnim) + # Play the animation + while inPartyAnimation?; pbUpdate; end + end + + #============================================================================= + # Animates a trainer's sprite and party lineup hiding (if they are visible). + # Animates a Pokémon being sent out into battle, then plays the shiny + # animation for it if relevant. + # sendOuts is an array; each element is itself an array: [idxBattler,pkmn] + #============================================================================= + def pbSendOutBattlers(sendOuts,startBattle=false) + return if sendOuts.length==0 + # If party balls are still appearing, wait for them to finish showing up, as + # the FadeAnimation will make them disappear. + while inPartyAnimation?; pbUpdate; end + @briefMessage = false + # Make all trainers and party lineups disappear (player-side trainers may + # animate throwing a Poké Ball) + if @battle.opposes?(sendOuts[0][0]) + fadeAnim = TrainerFadeAnimation.new(@sprites,@viewport,startBattle) + else + fadeAnim = PlayerFadeAnimation.new(@sprites,@viewport,startBattle) + end + # For each battler being sent out, set the battler's sprite and create two + # animations (the Poké Ball moving and battler appearing from it, and its + # data box appearing) + sendOutAnims = [] + sendOuts.each_with_index do |b,i| + pkmn = @battle.battlers[b[0]].effects[PBEffects::Illusion] || b[1] + pbChangePokemon(b[0],pkmn) + pbRefresh + if @battle.opposes?(b[0]) + sendOutAnim = PokeballTrainerSendOutAnimation.new(@sprites,@viewport, + @battle.pbGetOwnerIndexFromBattlerIndex(b[0])+1, + @battle.battlers[b[0]],startBattle,i) + else + sendOutAnim = PokeballPlayerSendOutAnimation.new(@sprites,@viewport, + @battle.pbGetOwnerIndexFromBattlerIndex(b[0])+1, + @battle.battlers[b[0]],startBattle,i) + end + dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,b[0]) + sendOutAnims.push([sendOutAnim,dataBoxAnim,false]) + end + # Play all animations + loop do + fadeAnim.update + sendOutAnims.each do |a| + next if a[2] + a[0].update + a[1].update if a[0].animDone? + a[2] = true if a[1].animDone? + end + pbUpdate + if !inPartyAnimation? + break if !sendOutAnims.any? { |a| !a[2] } + end + end + fadeAnim.dispose + sendOutAnims.each { |a| a[0].dispose; a[1].dispose } + # Play shininess animations for shiny Pokémon + sendOuts.each do |b| + next if !@battle.showAnims || !@battle.battlers[b[0]].shiny? + pbCommonAnimation("Shiny",@battle.battlers[b[0]]) + end + end + + #============================================================================= + # Animates a Pokémon being recalled into its Poké Ball and its data box hiding + #============================================================================= + def pbRecall(idxBattler) + @briefMessage = false + # Recall animation + recallAnim = BattlerRecallAnimation.new(@sprites,@viewport,idxBattler) + loop do + recallAnim.update if recallAnim + pbUpdate + break if recallAnim.animDone? + end + recallAnim.dispose + # Data box disappear animation + dataBoxAnim = DataBoxDisappearAnimation.new(@sprites,@viewport,idxBattler) + loop do + dataBoxAnim.update + pbUpdate + break if dataBoxAnim.animDone? + end + dataBoxAnim.dispose + end + + #============================================================================= + # Ability splash bar animations + #============================================================================= + def pbShowAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + side = battler.index%2 + pbHideAbilitySplash(battler) if @sprites["abilityBar_#{side}"].visible + @sprites["abilityBar_#{side}"].battler = battler + abilitySplashAnim = AbilitySplashAppearAnimation.new(@sprites,@viewport,side) + loop do + abilitySplashAnim.update + pbUpdate + break if abilitySplashAnim.animDone? + end + abilitySplashAnim.dispose + end + + def pbHideAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + side = battler.index%2 + return if !@sprites["abilityBar_#{side}"].visible + abilitySplashAnim = AbilitySplashDisappearAnimation.new(@sprites,@viewport,side) + loop do + abilitySplashAnim.update + pbUpdate + break if abilitySplashAnim.animDone? + end + abilitySplashAnim.dispose + end + + def pbReplaceAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + pbShowAbilitySplash(battler) + end + + #============================================================================= + # HP change animations + #============================================================================= + # Shows a HP-changing common animation and animates a data box's HP bar. + # Called by def pbReduceHP, def pbRecoverHP. + def pbHPChanged(battler,oldHP,showAnim=false) + @briefMessage = false + if battler.hp>oldHP + pbCommonAnimation("HealthUp",battler) if showAnim && @battle.showAnims + elsif battler.hp+{1}\r\nAttack+{2}\r\nDefense+{3}\r\nSp. Atk+{4}\r\nSp. Def+{5}\r\nSpeed+{6}", + pkmn.totalhp-oldTotalHP,pkmn.attack-oldAttack,pkmn.defense-oldDefense, + pkmn.spatk-oldSpAtk,pkmn.spdef-oldSpDef,pkmn.speed-oldSpeed)) + pbTopRightWindow( + _INTL("Max. HP{1}\r\nAttack{2}\r\nDefense{3}\r\nSp. Atk{4}\r\nSp. Def{5}\r\nSpeed{6}", + pkmn.totalhp,pkmn.attack,pkmn.defense,pkmn.spatk,pkmn.spdef,pkmn.speed)) + end + + #============================================================================= + # Animates a Pokémon fainting + #============================================================================= + def pbFaintBattler(battler) + @briefMessage = false + # Pokémon plays cry and drops down, data box disappears + faintAnim = BattlerFaintAnimation.new(@sprites,@viewport,battler.index,@battle) + dataBoxAnim = DataBoxDisappearAnimation.new(@sprites,@viewport,battler.index) + loop do + faintAnim.update + dataBoxAnim.update + pbUpdate + break if faintAnim.animDone? && dataBoxAnim.animDone? + end + faintAnim.dispose + dataBoxAnim.dispose + end + + #============================================================================= + # Animates throwing a Poké Ball at a Pokémon in an attempt to catch it + #============================================================================= + def pbThrow(ball,shakes,critical,targetBattler,showPlayer=false) + @briefMessage = false + captureAnim = PokeballThrowCaptureAnimation.new(@sprites,@viewport, + pbGetBallType(ball),shakes,critical,@battle.battlers[targetBattler],showPlayer) + loop do + captureAnim.update + pbUpdate + break if captureAnim.animDone? && !inPartyAnimation? + end + captureAnim.dispose + end + + def pbThrowSuccess + return if @battle.opponent + @briefMessage = false + pbMEPlay(pbGetWildCaptureME) + i = 0 + loop do + pbUpdate + break if i>=Graphics.frame_rate*3.5 # 3.5 seconds + i += 1 + end + pbMEStop + end + + def pbHideCaptureBall(idxBattler) + # NOTE: It's not really worth writing a whole PokeBattle_Animation class for + # making the capture ball fade out. + ball = @sprites["captureBall"] + return if !ball + # Data box disappear animation + dataBoxAnim = DataBoxDisappearAnimation.new(@sprites,@viewport,idxBattler) + loop do + dataBoxAnim.update + ball.opacity -= 12*20/Graphics.frame_rate if ball.opacity>0 + pbUpdate + break if dataBoxAnim.animDone? && ball.opacity<=0 + end + dataBoxAnim.dispose + end + + def pbThrowAndDeflect(ball,idxBattler) + @briefMessage = false + throwAnim = PokeballThrowDeflectAnimation.new(@sprites,@viewport, + pbGetBallType(ball),@battle.battlers[idxBattler]) + loop do + throwAnim.update + pbUpdate + break if throwAnim.animDone? + end + throwAnim.dispose + end + + #============================================================================= + # Hides all battler shadows before yielding to a move animation, and then + # restores the shadows afterwards + #============================================================================= + def pbSaveShadows + # Remember which shadows were visible + shadows = Array.new(@battle.battlers.length) do |i| + shadow = @sprites["shadow_#{i}"] + ret = (shadow) ? shadow.visible : false + shadow.visible = false if shadow + next ret + end + # Yield to other code, i.e. playing an animation + yield + # Restore shadow visibility + for i in 0...@battle.battlers.length + shadow = @sprites["shadow_#{i}"] + shadow.visible = shadows[i] if shadow + end + end + + #============================================================================= + # Loads a move/common animation + #============================================================================= + # Returns the animation ID to use for a given move/user. Returns nil if that + # move has no animations defined for it. + def pbFindMoveAnimDetails(move2anim,moveID,idxUser,hitNum=0) + noFlip = false + if (idxUser&1)==0 # On player's side + anim = move2anim[0][moveID] + else # On opposing side + anim = move2anim[1][moveID] + noFlip = true if anim + anim = move2anim[0][moveID] if !anim + end + return [anim+hitNum,noFlip] if anim + return nil + end + + # Returns the animation ID to use for a given move. If the move has no + # animations, tries to use a default move animation depending on the move's + # type. If that default move animation doesn't exist, trues to use Tackle's + # move animation. Returns nil if it can't find any of these animations to use. + def pbFindMoveAnimation(moveID,idxUser,hitNum) + begin + move2anim = pbLoadMoveToAnim + # Find actual animation requested (an opponent using the animation first + # looks for an OppMove version then a Move version) + anim = pbFindMoveAnimDetails(move2anim,moveID,idxUser,hitNum) + return anim if anim + # Actual animation not found, get the default animation for the move's type + moveData = pbGetMoveData(moveID) + moveType = moveData[MOVE_TYPE] + moveKind = moveData[MOVE_CATEGORY] + moveKind += 3 if PBTargets.multipleTargets?(moveData[MOVE_TARGET]) || + PBTargets.targetsFoeSide?(moveData[MOVE_TARGET]) + moveKind += 3 if moveKind==2 && moveData[MOVE_TARGET]!=PBTargets::User && + moveData[MOVE_TARGET]!=PBTargets::UserSide + # [one target physical, one target special, user status, + # multiple targets physical, multiple targets special, non-user status] + typeDefaultAnim = { + :NORMAL => [:TACKLE,:SONICBOOM,:DEFENSECURL,:EXPLOSION,:SWIFT,:TAILWHIP], + :FIGHTING => [:MACHPUNCH,:AURASPHERE,:DETECT,nil,nil,nil], + :FLYING => [:WINGATTACK,:GUST,:ROOST,nil,:AIRCUTTER,:FEATHERDANCE], + :POISON => [:POISONSTING,:SLUDGE,:ACIDARMOR,nil,:ACID,:POISONPOWDER], + :GROUND => [:SANDTOMB,:MUDSLAP,nil,:EARTHQUAKE,:EARTHPOWER,:MUDSPORT], + :ROCK => [:ROCKTHROW,:POWERGEM,:ROCKPOLISH,:ROCKSLIDE,nil,:SANDSTORM], + :BUG => [:TWINEEDLE,:BUGBUZZ,:QUIVERDANCE,nil,:STRUGGLEBUG,:STRINGSHOT], + :GHOST => [:LICK,:SHADOWBALL,:GRUDGE,nil,nil,:CONFUSERAY], + :STEEL => [:IRONHEAD,:MIRRORSHOT,:IRONDEFENSE,nil,nil,:METALSOUND], + :FIRE => [:FIREPUNCH,:EMBER,:SUNNYDAY,nil,:INCINERATE,:WILLOWISP], + :WATER => [:CRABHAMMER,:WATERGUN,:AQUARING,nil,:SURF,:WATERSPORT], + :GRASS => [:VINEWHIP,:MEGADRAIN,:COTTONGUARD,:RAZORLEAF,nil,:SPORE], + :ELECTRIC => [:THUNDERPUNCH,:THUNDERSHOCK,:CHARGE,nil,:DISCHARGE,:THUNDERWAVE], + :PSYCHIC => [:ZENHEADBUTT,:CONFUSION,:CALMMIND,nil,:SYNCHRONOISE,:MIRACLEEYE], + :ICE => [:ICEPUNCH,:ICEBEAM,:MIST,nil,:POWDERSNOW,:HAIL], + :DRAGON => [:DRAGONCLAW,:DRAGONRAGE,:DRAGONDANCE,nil,:TWISTER,nil], + :DARK => [:PURSUIT,:DARKPULSE,:HONECLAWS,nil,:SNARL,:EMBARGO], + :FAIRY => [:TACKLE,:FAIRYWIND,:MOONLIGHT,nil,:SWIFT,:SWEETKISS] + } + typeDefaultAnim.each do |type, anims| + next if !isConst?(moveType,PBTypes,type) + if anims[moveKind] && hasConst?(PBMoves,anims[moveKind]) + anim = pbFindMoveAnimDetails(move2anim,getConst(PBMoves,anims[moveKind]),idxUser) + end + break if anim + if moveKind>=3 && anims[moveKind-3] && hasConst?(PBMoves,anims[moveKind-3]) + anim = pbFindMoveAnimDetails(move2anim,getConst(PBMoves,anims[moveKind-3]),idxUser) + end + break if anim + if anims[2] && hasConst?(PBMoves,anims[2]) + anim = pbFindMoveAnimDetails(move2anim,getConst(PBMoves,anims[2]),idxUser) + end + break + end + return anim if anim + # Default animation for the move's type not found, use Tackle's animation + if hasConst?(PBMoves,:TACKLE) + return pbFindMoveAnimDetails(move2anim,getConst(PBMoves,:TACKLE),idxUser) + end + rescue + end + return nil + end + + #============================================================================= + # Plays a move/common animation + #============================================================================= + # Plays a move animation. + def pbAnimation(moveID,user,targets,hitNum=0) + animID = pbFindMoveAnimation(moveID,user.index,hitNum) + return if !animID + anim = animID[0] + target = (targets && targets.is_a?(Array)) ? targets[0] : targets + animations = pbLoadBattleAnimations + return if !animations + pbSaveShadows { + if animID[1] # On opposing side and using OppMove animation + pbAnimationCore(animations[anim],target,user,true) + else # On player's side, and/or using Move animation + pbAnimationCore(animations[anim],user,target) + end + } + end + + # Plays a common animation. + def pbCommonAnimation(animName,user=nil,target=nil,hitNum=0) + return if !animName || animName=="" + target = target[0] if target && target.is_a?(Array) + animations = pbLoadBattleAnimations + return if !animations + animations.each do |a| + next if !a || a.name!="Common:"+animName + pbAnimationCore(a,user,(target!=nil) ? target : user) + return + end + end + + def pbAnimationCore(animation,user,target,oppMove=false) + return if !animation + @briefMessage = false + userSprite = (user) ? @sprites["pokemon_#{user.index}"] : nil + targetSprite = (target) ? @sprites["pokemon_#{target.index}"] : nil + # Remember the original positions of Pokémon sprites + oldUserX = (userSprite) ? userSprite.x : 0 + oldUserY = (userSprite) ? userSprite.y : 0 + oldTargetX = (targetSprite) ? targetSprite.x : oldUserX + oldTargetY = (targetSprite) ? targetSprite.y : oldUserY + # Create the animation player + animPlayer = PBAnimationPlayerX.new(animation,user,target,self,oppMove) + # Apply a transformation to the animation based on where the user and target + # actually are. Get the centres of each sprite. + userHeight = (userSprite && userSprite.bitmap && !userSprite.bitmap.disposed?) ? userSprite.bitmap.height : 128 + if targetSprite + targetHeight = (targetSprite.bitmap && !targetSprite.bitmap.disposed?) ? targetSprite.bitmap.height : 128 + else + targetHeight = userHeight + end + animPlayer.setLineTransform( + PokeBattle_SceneConstants::FOCUSUSER_X,PokeBattle_SceneConstants::FOCUSUSER_Y, + PokeBattle_SceneConstants::FOCUSTARGET_X,PokeBattle_SceneConstants::FOCUSTARGET_Y, + oldUserX,oldUserY-userHeight/2, + oldTargetX,oldTargetY-targetHeight/2) + # Play the animation + animPlayer.start + loop do + animPlayer.update + pbUpdate + break if animPlayer.animDone? + end + animPlayer.dispose + # Return Pokémon sprites to their original positions + if userSprite + userSprite.x = oldUserX + userSprite.y = oldUserY + userSprite.pbSetOrigin + end + if targetSprite + targetSprite.x = oldTargetX + targetSprite.y = oldTargetY + targetSprite.pbSetOrigin + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_PBEffects.rb b/Data/Scripts/011_Battle/005_PBEffects.rb new file mode 100644 index 000000000..32b4bbfb9 --- /dev/null +++ b/Data/Scripts/011_Battle/005_PBEffects.rb @@ -0,0 +1,182 @@ +begin + module PBEffects + #=========================================================================== + # These effects apply to a battler + #=========================================================================== + AquaRing = 0 + Attract = 1 + BanefulBunker = 2 + BeakBlast = 3 + Bide = 4 + BideDamage = 5 + BideTarget = 6 + BurnUp = 7 + Charge = 8 + ChoiceBand = 9 + Confusion = 10 + Counter = 11 + CounterTarget = 12 + Curse = 13 + Dancer = 14 + DefenseCurl = 15 + DestinyBond = 16 + DestinyBondPrevious = 17 + DestinyBondTarget = 18 + Disable = 19 + DisableMove = 20 + Electrify = 21 + Embargo = 22 + Encore = 23 + EncoreMove = 24 + Endure = 25 + FirstPledge = 26 + FlashFire = 27 + Flinch = 28 + FocusEnergy = 29 + FocusPunch = 30 + FollowMe = 31 + Foresight = 32 + FuryCutter = 33 + GastroAcid = 34 + GemConsumed = 35 + Grudge = 36 + HealBlock = 37 + HelpingHand = 38 + HyperBeam = 39 + Illusion = 40 + Imprison = 41 + Ingrain = 42 + Instruct = 43 + Instructed = 44 + KingsShield = 45 + LaserFocus = 46 + LeechSeed = 47 + LockOn = 48 + LockOnPos = 49 + MagicBounce = 50 + MagicCoat = 51 + MagnetRise = 52 + MeanLook = 53 + MeFirst = 54 + Metronome = 55 + MicleBerry = 56 + Minimize = 57 + MiracleEye = 58 + MirrorCoat = 59 + MirrorCoatTarget = 60 + MoveNext = 61 + MudSport = 62 + Nightmare = 63 + Outrage = 64 + ParentalBond = 65 + PerishSong = 66 + PerishSongUser = 67 + PickupItem = 68 + PickupUse = 69 + Pinch = 70 # Battle Palace only + Powder = 71 + PowerTrick = 72 + Prankster = 73 + PriorityAbility = 74 + PriorityItem = 75 + Protect = 76 + ProtectRate = 77 + Pursuit = 78 + Quash = 79 + Rage = 80 + RagePowder = 81 # Used along with FollowMe + Revenge = 82 + Rollout = 83 + Roost = 84 + ShellTrap = 85 + SkyDrop = 86 + SlowStart = 87 + SmackDown = 88 + Snatch = 89 + SpikyShield = 90 + Spotlight = 91 + Stockpile = 92 + StockpileDef = 93 + StockpileSpDef = 94 + Substitute = 95 + Taunt = 96 + Telekinesis = 97 + ThroatChop = 98 + Torment = 99 + Toxic = 100 + Transform = 101 + TransformSpecies = 102 + Trapping = 103 # Trapping move + TrappingMove = 104 + TrappingUser = 105 + Truant = 106 + TwoTurnAttack = 107 + Type3 = 108 + Unburden = 109 + Uproar = 110 + WaterSport = 111 + WeightChange = 112 + Yawn = 113 + + #=========================================================================== + # These effects apply to a battler position + #=========================================================================== + FutureSightCounter = 0 + FutureSightMove = 1 + FutureSightUserIndex = 2 + FutureSightUserPartyIndex = 3 + HealingWish = 4 + LunarDance = 5 + Wish = 6 + WishAmount = 7 + WishMaker = 8 + + #=========================================================================== + # These effects apply to a side + #=========================================================================== + AuroraVeil = 0 + CraftyShield = 1 + EchoedVoiceCounter = 2 + EchoedVoiceUsed = 3 + LastRoundFainted = 4 + LightScreen = 5 + LuckyChant = 6 + MatBlock = 7 + Mist = 8 + QuickGuard = 9 + Rainbow = 10 + Reflect = 11 + Round = 12 + Safeguard = 13 + SeaOfFire = 14 + Spikes = 15 + StealthRock = 16 + StickyWeb = 17 + Swamp = 18 + Tailwind = 19 + ToxicSpikes = 20 + WideGuard = 21 + + #=========================================================================== + # These effects apply to the battle (i.e. both sides) + #=========================================================================== + AmuletCoin = 0 + FairyLock = 1 + FusionBolt = 2 + FusionFlare = 3 + Gravity = 4 + HappyHour = 5 + IonDeluge = 6 + MagicRoom = 7 + MudSportField = 8 + PayDay = 9 + TrickRoom = 10 + WaterSportField = 11 + WonderRoom = 12 + end + +rescue Exception + if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset" + raise $! + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_BattleHandlers.rb b/Data/Scripts/011_Battle/006_BattleHandlers.rb new file mode 100644 index 000000000..cc87b028a --- /dev/null +++ b/Data/Scripts/011_Battle/006_BattleHandlers.rb @@ -0,0 +1,611 @@ +module BattleHandlers + # Battler's speed calculation + SpeedCalcAbility = AbilityHandlerHash.new + SpeedCalcItem = ItemHandlerHash.new + # Battler's weight calculation + WeightCalcAbility = AbilityHandlerHash.new + WeightCalcItem = ItemHandlerHash.new # Float Stone + # Battler's HP changed + HPHealItem = ItemHandlerHash.new + AbilityOnHPDroppedBelowHalf = AbilityHandlerHash.new + # Battler's status problem + StatusCheckAbilityNonIgnorable = AbilityHandlerHash.new # Comatose + StatusImmunityAbility = AbilityHandlerHash.new + StatusImmunityAbilityNonIgnorable = AbilityHandlerHash.new + StatusImmunityAllyAbility = AbilityHandlerHash.new + AbilityOnStatusInflicted = AbilityHandlerHash.new # Synchronize + StatusCureItem = ItemHandlerHash.new + StatusCureAbility = AbilityHandlerHash.new + # Battler's stat stages + StatLossImmunityAbility = AbilityHandlerHash.new + StatLossImmunityAbilityNonIgnorable = AbilityHandlerHash.new # Full Metal Body + StatLossImmunityAllyAbility = AbilityHandlerHash.new # Flower Veil + AbilityOnStatGain = AbilityHandlerHash.new # None! + AbilityOnStatLoss = AbilityHandlerHash.new + # Priority and turn order + PriorityChangeAbility = AbilityHandlerHash.new + PriorityBracketChangeAbility = AbilityHandlerHash.new # Stall + PriorityBracketChangeItem = ItemHandlerHash.new + PriorityBracketUseAbility = AbilityHandlerHash.new # None! + PriorityBracketUseItem = ItemHandlerHash.new + # Move usage failures + AbilityOnFlinch = AbilityHandlerHash.new # Steadfast + MoveBlockingAbility = AbilityHandlerHash.new + MoveImmunityTargetAbility = AbilityHandlerHash.new + # Move usage + MoveBaseTypeModifierAbility = AbilityHandlerHash.new + # Accuracy calculation + AccuracyCalcUserAbility = AbilityHandlerHash.new + AccuracyCalcUserAllyAbility = AbilityHandlerHash.new # Victory Star + AccuracyCalcTargetAbility = AbilityHandlerHash.new + AccuracyCalcUserItem = ItemHandlerHash.new + AccuracyCalcTargetItem = ItemHandlerHash.new + # Damage calculation + DamageCalcUserAbility = AbilityHandlerHash.new + DamageCalcUserAllyAbility = AbilityHandlerHash.new + DamageCalcTargetAbility = AbilityHandlerHash.new + DamageCalcTargetAbilityNonIgnorable = AbilityHandlerHash.new + DamageCalcTargetAllyAbility = AbilityHandlerHash.new + DamageCalcUserItem = ItemHandlerHash.new + DamageCalcTargetItem = ItemHandlerHash.new + # Critical hit calculation + CriticalCalcUserAbility = AbilityHandlerHash.new + CriticalCalcTargetAbility = AbilityHandlerHash.new + CriticalCalcUserItem = ItemHandlerHash.new + CriticalCalcTargetItem = ItemHandlerHash.new # None! + # Upon a move hitting a target + TargetAbilityOnHit = AbilityHandlerHash.new + UserAbilityOnHit = AbilityHandlerHash.new # Poison Touch + TargetItemOnHit = ItemHandlerHash.new + TargetItemOnHitPositiveBerry = ItemHandlerHash.new + # Abilities/items that trigger at the end of using a move + UserAbilityEndOfMove = AbilityHandlerHash.new + TargetItemAfterMoveUse = ItemHandlerHash.new + UserItemAfterMoveUse = ItemHandlerHash.new + TargetAbilityAfterMoveUse = AbilityHandlerHash.new + EndOfMoveItem = ItemHandlerHash.new # Leppa Berry + EndOfMoveStatRestoreItem = ItemHandlerHash.new # White Herb + # Experience and EV gain + ExpGainModifierItem = ItemHandlerHash.new # Lucky Egg + EVGainModifierItem = ItemHandlerHash.new + # Weather and terrin + WeatherExtenderItem = ItemHandlerHash.new + TerrainExtenderItem = ItemHandlerHash.new # Terrain Extender + TerrainStatBoostItem = ItemHandlerHash.new + # End Of Round + EORWeatherAbility = AbilityHandlerHash.new + EORHealingAbility = AbilityHandlerHash.new + EORHealingItem = ItemHandlerHash.new + EOREffectAbility = AbilityHandlerHash.new + EOREffectItem = ItemHandlerHash.new + EORGainItemAbility = AbilityHandlerHash.new + # Switching and fainting + CertainSwitchingUserAbility = AbilityHandlerHash.new # None! + CertainSwitchingUserItem = ItemHandlerHash.new # Shed Shell + TrappingTargetAbility = AbilityHandlerHash.new + TrappingTargetItem = ItemHandlerHash.new # None! + AbilityOnSwitchIn = AbilityHandlerHash.new + ItemOnSwitchIn = ItemHandlerHash.new # Air Balloon + ItemOnIntimidated = ItemHandlerHash.new # Adrenaline Orb + AbilityOnSwitchOut = AbilityHandlerHash.new + AbilityChangeOnBattlerFainting = AbilityHandlerHash.new + AbilityOnBattlerFainting = AbilityHandlerHash.new # Soul-Heart + # Running from battle + RunFromBattleAbility = AbilityHandlerHash.new # Run Away + RunFromBattleItem = ItemHandlerHash.new # Smoke Ball + + #============================================================================= + + def self.triggerSpeedCalcAbility(ability,battler,mult) + ret = SpeedCalcAbility.trigger(ability,battler,mult) + return (ret!=nil) ? ret : mult + end + + def self.triggerSpeedCalcItem(item,battler,mult) + ret = SpeedCalcItem.trigger(item,battler,mult) + return (ret!=nil) ? ret : mult + end + + #============================================================================= + + def self.triggerWeightCalcAbility(ability,battler,w) + ret = WeightCalcAbility.trigger(ability,battler,w) + return (ret!=nil) ? ret : w + end + + def self.triggerWeightCalcItem(item,battler,w) + ret = WeightCalcItem.trigger(item,battler,w) + return (ret!=nil) ? ret : w + end + + #============================================================================= + + def self.triggerHPHealItem(item,battler,battle,forced) + ret = HPHealItem.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnHPDroppedBelowHalf(ability,user,battle) + ret = AbilityOnHPDroppedBelowHalf.trigger(ability,user,battle) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerStatusCheckAbilityNonIgnorable(ability,battler,status) + ret = StatusCheckAbilityNonIgnorable.trigger(ability,battler,status) + return (ret!=nil) ? ret : false + end + + def self.triggerStatusImmunityAbility(ability,battler,status) + ret = StatusImmunityAbility.trigger(ability,battler,status) + return (ret!=nil) ? ret : false + end + + def self.triggerStatusImmunityAbilityNonIgnorable(ability,battler,status) + ret = StatusImmunityAbilityNonIgnorable.trigger(ability,battler,status) + return (ret!=nil) ? ret : false + end + + def self.triggerStatusImmunityAllyAbility(ability,battler,status) + ret = StatusImmunityAllyAbility.trigger(ability,battler,status) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnStatusInflicted(ability,battler,user,status) + AbilityOnStatusInflicted.trigger(ability,battler,user,status) + end + + def self.triggerStatusCureItem(item,battler,battle,forced) + ret = StatusCureItem.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + def self.triggerStatusCureAbility(ability,battler) + ret = StatusCureAbility.trigger(ability,battler) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerStatLossImmunityAbility(ability,battler,stat,battle,showMessages) + ret = StatLossImmunityAbility.trigger(ability,battler,stat,battle,showMessages) + return (ret!=nil) ? ret : false + end + + def self.triggerStatLossImmunityAbilityNonIgnorable(ability,battler,stat,battle,showMessages) + ret = StatLossImmunityAbilityNonIgnorable.trigger(ability,battler,stat,battle,showMessages) + return (ret!=nil) ? ret : false + end + + def self.triggerStatLossImmunityAllyAbility(ability,bearer,battler,stat,battle,showMessages) + ret = StatLossImmunityAllyAbility.trigger(ability,bearer,battler,stat,battle,showMessages) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnStatGain(ability,battler,stat,user) + AbilityOnStatGain.trigger(ability,battler,stat,user) + end + + def self.triggerAbilityOnStatLoss(ability,battler,stat,user) + AbilityOnStatLoss.trigger(ability,battler,stat,user) + end + + #============================================================================= + + def self.triggerPriorityChangeAbility(ability,battler,move,pri) + ret = PriorityChangeAbility.trigger(ability,battler,move,pri) + return (ret!=nil) ? ret : pri + end + + def self.triggerPriorityBracketChangeAbility(ability,battler,subPri,battle) + ret = PriorityBracketChangeAbility.trigger(ability,battler,subPri,battle) + return (ret!=nil) ? ret : subPri + end + + def self.triggerPriorityBracketChangeItem(item,battler,subPri,battle) + ret = PriorityBracketChangeItem.trigger(item,battler,subPri,battle) + return (ret!=nil) ? ret : subPri + end + + def self.triggerPriorityBracketUseAbility(ability,battler,battle) + PriorityBracketUseAbility.trigger(ability,battler,battle) + end + + def self.triggerPriorityBracketUseItem(item,battler,battle) + PriorityBracketUseItem.trigger(item,battler,battle) + end + + #============================================================================= + + def self.triggerAbilityOnFlinch(ability,battler,battle) + AbilityOnFlinch.trigger(ability,battler,battle) + end + + def self.triggerMoveBlockingAbility(ability,bearer,user,targets,move,battle) + ret = MoveBlockingAbility.trigger(ability,bearer,user,targets,move,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerMoveImmunityTargetAbility(ability,user,target,move,type,battle) + ret = MoveImmunityTargetAbility.trigger(ability,user,target,move,type,battle) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerMoveBaseTypeModifierAbility(ability,user,move,type) + ret = MoveBaseTypeModifierAbility.trigger(ability,user,move,type) + return (ret!=nil) ? ret : type + end + + #============================================================================= + + def self.triggerAccuracyCalcUserAbility(ability,mods,user,target,move,type) + AccuracyCalcUserAbility.trigger(ability,mods,user,target,move,type) + end + + def self.triggerAccuracyCalcUserAllyAbility(ability,mods,user,target,move,type) + AccuracyCalcUserAllyAbility.trigger(ability,mods,user,target,move,type) + end + + def self.triggerAccuracyCalcTargetAbility(ability,mods,user,target,move,type) + AccuracyCalcTargetAbility.trigger(ability,mods,user,target,move,type) + end + + def self.triggerAccuracyCalcUserItem(item,mods,user,target,move,type) + AccuracyCalcUserItem.trigger(item,mods,user,target,move,type) + end + + def self.triggerAccuracyCalcTargetItem(item,mods,user,target,move,type) + AccuracyCalcTargetItem.trigger(item,mods,user,target,move,type) + end + + #============================================================================= + + def self.triggerDamageCalcUserAbility(ability,user,target,move,mults,baseDmg,type) + DamageCalcUserAbility.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcUserAllyAbility(ability,user,target,move,mults,baseDmg,type) + DamageCalcUserAllyAbility.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcTargetAbility(ability,user,target,move,mults,baseDmg,type) + DamageCalcTargetAbility.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcTargetAbilityNonIgnorable(ability,user,target,move,mults,baseDmg,type) + DamageCalcTargetAbilityNonIgnorable.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcTargetAllyAbility(ability,user,target,move,mults,baseDmg,type) + DamageCalcTargetAllyAbility.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcUserItem(item,user,target,move,mults,baseDmg,type) + DamageCalcUserItem.trigger(item,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcTargetItem(item,user,target,move,mults,baseDmg,type) + DamageCalcTargetItem.trigger(item,user,target,move,mults,baseDmg,type) + end + + #============================================================================= + + def self.triggerCriticalCalcUserAbility(ability,user,target,c) + ret = CriticalCalcUserAbility.trigger(ability,user,target,c) + return (ret!=nil) ? ret : c + end + + def self.triggerCriticalCalcTargetAbility(ability,user,target,c) + ret = CriticalCalcTargetAbility.trigger(ability,user,target,c) + return (ret!=nil) ? ret : c + end + + def self.triggerCriticalCalcUserItem(item,user,target,c) + ret = CriticalCalcUserItem.trigger(item,user,target,c) + return (ret!=nil) ? ret : c + end + + def self.triggerCriticalCalcTargetItem(item,user,target,c) + ret = CriticalCalcTargetItem.trigger(item,user,target,c) + return (ret!=nil) ? ret : c + end + + #============================================================================= + + def self.triggerTargetAbilityOnHit(ability,user,target,move,battle) + TargetAbilityOnHit.trigger(ability,user,target,move,battle) + end + + def self.triggerUserAbilityOnHit(ability,user,target,move,battle) + UserAbilityOnHit.trigger(ability,user,target,move,battle) + end + + def self.triggerTargetItemOnHit(item,user,target,move,battle) + TargetItemOnHit.trigger(item,user,target,move,battle) + end + + def self.triggerTargetItemOnHitPositiveBerry(item,battler,battle,forced) + ret = TargetItemOnHitPositiveBerry.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerUserAbilityEndOfMove(ability,user,targets,move,battle) + UserAbilityEndOfMove.trigger(ability,user,targets,move,battle) + end + + def self.triggerTargetItemAfterMoveUse(item,battler,user,move,switched,battle) + TargetItemAfterMoveUse.trigger(item,battler,user,move,switched,battle) + end + + def self.triggerUserItemAfterMoveUse(item,user,targets,move,numHits,battle) + UserItemAfterMoveUse.trigger(item,user,targets,move,numHits,battle) + end + + def self.triggerTargetAbilityAfterMoveUse(ability,target,user,move,switched,battle) + TargetAbilityAfterMoveUse.trigger(ability,target,user,move,switched,battle) + end + + def self.triggerEndOfMoveItem(item,battler,battle,forced) + ret = EndOfMoveItem.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + def self.triggerEndOfMoveStatRestoreItem(item,battler,battle,forced) + ret = EndOfMoveStatRestoreItem.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerExpGainModifierItem(item,battler,exp) + ret = ExpGainModifierItem.trigger(item,battler,exp) + return (ret!=nil) ? ret : -1 + end + + def self.triggerEVGainModifierItem(item,battler,evarray) + return false if !EVGainModifierItem[item] + EVGainModifierItem.trigger(item,battler,evarray) + return true + end + + #============================================================================= + + def self.triggerWeatherExtenderItem(item,weather,duration,battler,battle) + ret = WeatherExtenderItem.trigger(item,weather,duration,battler,battle) + return (ret!=nil) ? ret : duration + end + + def self.triggerTerrainExtenderItem(item,terrain,duration,battler,battle) + ret = TerrainExtenderItem.trigger(item,terrain,duration,battler,battle) + return (ret!=nil) ? ret : duration + end + + def self.triggerTerrainStatBoostItem(item,battler,battle) + ret = TerrainStatBoostItem.trigger(item,battler,battle) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerEORWeatherAbility(ability,weather,battler,battle) + EORWeatherAbility.trigger(ability,weather,battler,battle) + end + + def self.triggerEORHealingAbility(ability,battler,battle) + EORHealingAbility.trigger(ability,battler,battle) + end + + def self.triggerEORHealingItem(item,battler,battle) + EORHealingItem.trigger(item,battler,battle) + end + + def self.triggerEOREffectAbility(ability,battler,battle) + EOREffectAbility.trigger(ability,battler,battle) + end + + def self.triggerEOREffectItem(item,battler,battle) + EOREffectItem.trigger(item,battler,battle) + end + + def self.triggerEORGainItemAbility(ability,battler,battle) + EORGainItemAbility.trigger(ability,battler,battle) + end + + #============================================================================= + + def self.triggerCertainSwitchingUserAbility(ability,switcher,battle) + ret = CertainSwitchingUserAbility.trigger(ability,switcher,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerCertainSwitchingUserItem(item,switcher,battle) + ret = CertainSwitchingUserItem.trigger(item,switcher,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerTrappingTargetAbility(ability,switcher,bearer,battle) + ret = TrappingTargetAbility.trigger(ability,switcher,bearer,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerTrappingTargetItem(item,switcher,bearer,battle) + ret = TrappingTargetItem.trigger(item,switcher,bearer,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnSwitchIn(ability,battler,battle) + AbilityOnSwitchIn.trigger(ability,battler,battle) + end + + def self.triggerItemOnSwitchIn(item,battler,battle) + ItemOnSwitchIn.trigger(item,battler,battle) + end + + def self.triggerItemOnIntimidated(item,battler,battle) + ret = ItemOnIntimidated.trigger(item,battler,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnSwitchOut(ability,battler,endOfBattle) + AbilityOnSwitchOut.trigger(ability,battler,endOfBattle) + end + + def self.triggerAbilityChangeOnBattlerFainting(ability,battler,fainted,battle) + AbilityChangeOnBattlerFainting.trigger(ability,battler,fainted,battle) + end + + def self.triggerAbilityOnBattlerFainting(ability,battler,fainted,battle) + AbilityOnBattlerFainting.trigger(ability,battler,fainted,battle) + end + + #============================================================================= + + def self.triggerRunFromBattleAbility(ability,battler) + ret = RunFromBattleAbility.trigger(ability,battler) + return (ret!=nil) ? ret : false + end + + def self.triggerRunFromBattleItem(item,battler) + ret = RunFromBattleItem.trigger(item,battler) + return (ret!=nil) ? ret : false + end +end + + + +BASE_ACC = 0 +ACC_STAGE = 1 +EVA_STAGE = 2 +ACC_MULT = 3 +EVA_MULT = 4 + +BASE_DMG_MULT = 0 +ATK_MULT = 1 +DEF_MULT = 2 +FINAL_DMG_MULT = 3 + +def pbBattleConfusionBerry(battler,battle,item,forced,flavor,confuseMsg) + return false if !forced && !battler.pbCanConsumeBerry?(item,false) + itemName = PBItems.getName(item) + battle.pbCommonAnimation("EatBerry",battler) if !forced + amt = (NEWEST_BATTLE_MECHANICS) ? battler.pbRecoverHP(battler.totalhp/2) : battler.pbRecoverHP(battler.totalhp/8) + if amt>0 + if forced + PBDebug.log("[Item triggered] #{battler.pbThis}'s #{itemName}") + battle.pbDisplay(_INTL("{1}'s HP was restored.",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1} restored its health using its {2}!",battler.pbThis,itemName)) + end + end + nUp = PBNatures.getStatRaised(battler.nature) + nDn = PBNatures.getStatLowered(battler.nature) + if nUp!=nDn && nDn-1==flavor + battle.pbDisplay(confuseMsg) + battler.pbConfuse if battler.pbCanConfuseSelf?(false) + end + return true +end + +def pbBattleStatIncreasingBerry(battler,battle,item,forced,stat,increment=1) + return false if !forced && !battler.pbCanConsumeBerry?(item) + return false if !battler.pbCanRaiseStatStage?(stat,battler) + itemName = PBItems.getName(item) + if !forced + battle.pbCommonAnimation("EatBerry",battler) + return battler.pbRaiseStatStageByCause(stat,increment,battler,itemName) + end + PBDebug.log("[Item triggered] #{battler.pbThis}'s #{itemName}") + return battler.pbRaiseStatStage(stat,increment,battler) +end + +# For abilities that grant immunity to moves of a particular type, and raises +# one of the ability's bearer's stats instead. +def pbBattleMoveImmunityStatAbility(user,target,move,moveType,immuneType,stat,increment,battle) + return false if user.index==target.index + return false if !isConst?(moveType,PBTypes,immuneType) + battle.pbShowAbilitySplash(target) + if target.pbCanRaiseStatStage?(stat,target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + target.pbRaiseStatStage(stat,increment,target) + else + target.pbRaiseStatStageByCause(stat,increment,target,target.abilityName) + end + else + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!", + target.pbThis,target.abilityName,move.name)) + end + end + battle.pbHideAbilitySplash(target) + return true +end + +# For abilities that grant immunity to moves of a particular type, and heals the +# ability's bearer by 1/4 of its total HP instead. +def pbBattleMoveImmunityHealAbility(user,target,move,moveType,immuneType,battle) + return false if user.index==target.index + return false if !isConst?(moveType,PBTypes,immuneType) + battle.pbShowAbilitySplash(target) + if target.canHeal? && target.pbRecoverHP(target.totalhp/4)>0 + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",target.pbThis,target.abilityName)) + end + else + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!", + target.pbThis,target.abilityName,move.name)) + end + end + battle.pbHideAbilitySplash(target) + return true +end + +def pbBattleGem(user,type,move,mults,moveType) + # Pledge moves never consume Gems + return if move.is_a?(PokeBattle_PledgeMove) + return if !isConst?(moveType,PBTypes,type) + user.effects[PBEffects::GemConsumed] = user.item + if NEWEST_BATTLE_MECHANICS + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.3).round + else + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round + end +end + +def pbBattleTypeWeakingBerry(type,moveType,target,mults) + return if !isConst?(moveType,PBTypes,type) + return if PBTypes.resistant?(target.damageState.typeMod) && !isConst?(moveType,PBTypes,:NORMAL) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]/2).round + target.damageState.berryWeakened = true + target.battle.pbCommonAnimation("EatBerry",target) +end + +def pbBattleWeatherAbility(weather,battler,battle,ignorePrimal=false) + return if !ignorePrimal && + (battle.field.weather==PBWeather::HarshSun || + battle.field.weather==PBWeather::HeavyRain || + battle.field.weather==PBWeather::StrongWinds) + return if battle.field.weather==weather + battle.pbShowAbilitySplash(battler) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} activated!",battler.pbThis,battler.abilityName)) + end + fixedDuration = false + fixedDuration = true if NEWEST_BATTLE_MECHANICS && + weather!=PBWeather::HarshSun && + weather!=PBWeather::HeavyRain && + weather!=PBWeather::StrongWinds + battle.pbStartWeather(battler,weather,fixedDuration) + # NOTE: The ability splash is hidden again in def pbStartWeather. +end diff --git a/Data/Scripts/011_Battle/006_Other battle types/001_PokeBattle_AnimationPlayer.rb b/Data/Scripts/011_Battle/006_Other battle types/001_PokeBattle_AnimationPlayer.rb new file mode 100644 index 000000000..5a9ba8300 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/001_PokeBattle_AnimationPlayer.rb @@ -0,0 +1,879 @@ +#=============================================================================== +# +#=============================================================================== +class AnimFrame + X = 0 + Y = 1 + ZOOMX = 2 + ANGLE = 3 + MIRROR = 4 + BLENDTYPE = 5 + VISIBLE = 6 + PATTERN = 7 + OPACITY = 8 + ZOOMY = 11 + COLORRED = 12 + COLORGREEN = 13 + COLORBLUE = 14 + COLORALPHA = 15 + TONERED = 16 + TONEGREEN = 17 + TONEBLUE = 18 + TONEGRAY = 19 + LOCKED = 20 + FLASHRED = 21 + FLASHGREEN = 22 + FLASHBLUE = 23 + FLASHALPHA = 24 + PRIORITY = 25 + FOCUS = 26 +end + + + +#=============================================================================== +# +#=============================================================================== +def yaxisIntersect(x1,y1,x2,y2,px,py) + dx = x2-x1 + dy = y2-y1 + x = (dx==0) ? 0.0 : (px-x1).to_f/dx + y = (dy==0) ? 0.0 : (py-y1).to_f/dy + return [x,y] +end + +def repositionY(x1,y1,x2,y2,tx,ty) + dx = x2-x1 + dy = y2-y1 + x = x1+tx*dx.to_f + y = y1+ty*dy.to_f + return [x,y] +end + +def transformPoint(x1,y1,x2,y2, # Source line + x3,y3,x4,y4, # Destination line + px,py) # Source point + ret = yaxisIntersect(x1,y1,x2,y2,px,py) + ret2 = repositionY(x3,y3,x4,y4,ret[0],ret[1]) + return ret2 +end + +def getSpriteCenter(sprite) + return [0,0] if !sprite || sprite.disposed? + return [sprite.x,sprite.y] if !sprite.bitmap || sprite.bitmap.disposed? + centerX = sprite.src_rect.width/2 + centerY = sprite.src_rect.height/2 + offsetX = (centerX-sprite.ox)*sprite.zoom_x + offsetY = (centerY-sprite.oy)*sprite.zoom_y + return [sprite.x+offsetX,sprite.y+offsetY] +end + +def isReversed(src0,src1,dst0,dst1) + return false if src0==src1 + return (dst0>dst1) if src0=self.frames.length + totalframes = frame+otherAnim.frames.length+1 + for i in self.frames.length...totalframes + self.frames.push(RPG::Animation::Frame.new) + end + end + self.frame_max = self.frames.length + for i in 0...otherAnim.frame_max + thisframe = self.frames[frame+i] + otherframe = otherAnim.frames[i] + cellStart = thisframe.cell_max + thisframe.cell_max += otherframe.cell_max + thisframe.cell_data.resize(thisframe.cell_max,8) + for j in 0...otherframe.cell_max + thisframe.cell_data[cellStart+j,0] = otherframe.cell_data[j,0] + thisframe.cell_data[cellStart+j,1] = otherframe.cell_data[j,1]+x + thisframe.cell_data[cellStart+j,2] = otherframe.cell_data[j,2]+y + thisframe.cell_data[cellStart+j,3] = otherframe.cell_data[j,3] + thisframe.cell_data[cellStart+j,4] = otherframe.cell_data[j,4] + thisframe.cell_data[cellStart+j,5] = otherframe.cell_data[j,5] + thisframe.cell_data[cellStart+j,6] = otherframe.cell_data[j,6] + thisframe.cell_data[cellStart+j,7] = otherframe.cell_data[j,7] + end + end + for i in 0...otherAnim.timings.length + timing = RPG::Animation::Timing.new + othertiming = otherAnim.timings[i] + timing.frame = frame+othertiming.frame + timing.se = RPG::AudioFile.new( + othertiming.se.name.clone, + othertiming.se.volume, + othertiming.se.pitch) + timing.flash_scope = othertiming.flash_scope + timing.flash_color = othertiming.flash_color.clone + timing.flash_duration = othertiming.flash_duration + timing.condition = othertiming.condition + self.timings.push(timing) + end + self.timings.sort! { |a,b| a.frame<=>b.frame } + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PBAnimTiming + attr_accessor :frame + attr_accessor :timingType # 0=play SE, 1=set bg, 2=bg mod + attr_accessor :name # Name of SE file or BG file + attr_accessor :volume + attr_accessor :pitch + attr_accessor :bgX # x coordinate of bg (or to move bg to) + attr_accessor :bgY # y coordinate of bg (or to move bg to) + attr_accessor :opacity # Opacity of bg (or to change bg to) + attr_accessor :colorRed # Color of bg (or to change bg to) + attr_accessor :colorGreen # Color of bg (or to change bg to) + attr_accessor :colorBlue # Color of bg (or to change bg to) + attr_accessor :colorAlpha # Color of bg (or to change bg to) + attr_accessor :duration # How long to spend changing to the new bg coords/color + attr_accessor :flashScope + attr_accessor :flashColor + attr_accessor :flashDuration + + def initialize(type=0) + @frame = 0 + @timingType = type + @name = "" + @volume = 80 + @pitch = 100 + @bgX = nil + @bgY = nil + @opacity = nil + @colorRed = nil + @colorGreen = nil + @colorBlue = nil + @colorAlpha = nil + @duration = 5 + @flashScope = 0 + @flashColor = Color.new(255,255,255,255) + @flashDuration = 5 + end + + def timingType + @timingType = 0 if !@timingType + return @timingType + end + + def duration + @duration = 5 if !@duration + return @duration + end + + def to_s + case self.timingType + when 0 + return "[#{@frame+1}] Play SE: #{name} (volume #{@volume}, pitch #{@pitch})" + when 1 + text = sprintf("[%d] Set BG: \"%s\"",@frame+1,name) + text += sprintf(" (color=%s,%s,%s,%s)", + (@colorRed!=nil) ? @colorRed.to_i : "-", + (@colorGreen!=nil) ? @colorGreen.to_i : "-", + (@colorBlue!=nil) ? @colorBlue.to_i : "-", + (@colorAlpha!=nil) ? @colorAlpha.to_i : "-") + text += sprintf(" (opacity=%s)",@opacity.to_i) + text += sprintf(" (coords=%s,%s)", + (@bgX!=nil) ? @bgX : "-", + (@bgY!=nil) ? @bgY : "-") + return text + when 2 + text = sprintf("[%d] Change BG: @%d",@frame+1,duration) + if @colorRed!=nil || @colorGreen!=nil || @colorBlue!=nil || @colorAlpha!=nil + text += sprintf(" (color=%s,%s,%s,%s)", + (@colorRed!=nil) ? @colorRed.to_i : "-", + (@colorGreen!=nil) ? @colorGreen.to_i : "-", + (@colorBlue!=nil) ? @colorBlue.to_i : "-", + (@colorAlpha!=nil) ? @colorAlpha.to_i : "-") + end + text += sprintf(" (opacity=%s)",@opacity.to_i) if @opacity!=nil + if @bgX!=nil || @bgY!=nil + text += sprintf(" (coords=%s,%s)", + (@bgX!=nil) ? @bgX : "-", + (@bgY!=nil) ? @bgY : "-") + end + return text + when 3 + text = sprintf("[%d] Set FG: \"%s\"",@frame+1,name) + text += sprintf(" (color=%s,%s,%s,%s)", + (@colorRed!=nil) ? @colorRed.to_i : "-", + (@colorGreen!=nil) ? @colorGreen.to_i : "-", + (@colorBlue!=nil) ? @colorBlue.to_i : "-", + (@colorAlpha!=nil) ? @colorAlpha.to_i : "-") + text += sprintf(" (opacity=%s)",@opacity.to_i) + text += sprintf(" (coords=%s,%s)", + (@bgX!=nil) ? @bgX : "-", + (@bgY!=nil) ? @bgY : "-") + return text + when 4 + text = sprintf("[%d] Change FG: @%d",@frame+1,duration) + if @colorRed!=nil || @colorGreen!=nil || @colorBlue!=nil || @colorAlpha!=nil + text += sprintf(" (color=%s,%s,%s,%s)", + (@colorRed!=nil) ? @colorRed.to_i : "-", + (@colorGreen!=nil) ? @colorGreen.to_i : "-", + (@colorBlue!=nil) ? @colorBlue.to_i : "-", + (@colorAlpha!=nil) ? @colorAlpha.to_i : "-") + end + text += sprintf(" (opacity=%s)",@opacity.to_i) if @opacity!=nil + if @bgX!=nil || @bgY!=nil + text += sprintf(" (coords=%s,%s)", + (@bgX!=nil) ? @bgX : "-", + (@bgY!=nil) ? @bgY : "-") + end + return text + end + return "" + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PBAnimations < Array + include Enumerable + attr_reader :array + attr_accessor :selected + + def initialize(size=1) + @array = [] + @selected = 0 + size = 1 if size<1 # Always create at least one animation + size.times do + @array.push(PBAnimation.new) + end + end + + def length + return @array.length + end + + def each + @array.each { |i| yield i } + end + + def [](i) + return @array[i] + end + + def []=(i,value) + @array[i] = value + end + + def compact + @array.compact! + end + + def insert(index,val) + @array.insert(index,val) + end + + def delete_at(index) + @array.delete_at(index) + end + + def resize(len) + idxStart = @array.length + idxEnd = len + if idxStart>idxEnd + for i in idxEnd...idxStart + @array.pop + end + else + for i in idxStart...idxEnd + @array.push(PBAnimation.new) + end + end + self.selected = len if self.selected>=len + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PBAnimation < Array + include Enumerable + attr_accessor :id + attr_accessor :name + attr_accessor :graphic + attr_accessor :hue + attr_accessor :position + attr_writer :speed + attr_reader :array + attr_reader :timing + MAX_SPRITES = 60 + + def speed + return @speed || 20 + end + + def initialize(size=1) + @id = -1 + @name = "" + @graphic = "" + @hue = 0 + @position = 4 # 1=target, 2=user, 3=user and target, 4=screen + @array = [] + size = 1 if size<1 # Always create at least one frame + size.times do; addFrame; end + @timing = [] + @scope = 0 + end + + def length + return @array.length + end + + def each + @array.each { |i| yield i } + end + + def [](i) + return @array[i] + end + + def []=(i,value) + @array[i] = value + end + + def insert(*arg) + return @array.insert(*arg) + end + + def delete_at(*arg) + return @array.delete_at(*arg) + end + + def resize(len) + if len<@array.length + @array[len,@array.length-len] = [] + elsif len>@array.length + (len-@array.length).times do + addFrame + end + end + end + + def addFrame + pos = @array.length + @array[pos] = [] + # Move's user + @array[pos][0] = pbCreateCel( + PokeBattle_SceneConstants::FOCUSUSER_X, + PokeBattle_SceneConstants::FOCUSUSER_Y,-1) + @array[pos][0][AnimFrame::FOCUS] = 2 + @array[pos][0][AnimFrame::LOCKED] = 1 + # Move's target + @array[pos][1] = pbCreateCel( + PokeBattle_SceneConstants::FOCUSTARGET_X, + PokeBattle_SceneConstants::FOCUSTARGET_Y,-2) + @array[pos][1][AnimFrame::FOCUS] = 1 + @array[pos][1][AnimFrame::LOCKED] = 1 + return @array[pos] + end + + def playTiming(frame,bgGraphic,bgColor,foGraphic,foColor,oldbg=[],oldfo=[],user=nil) + for i in @timing + next if i.frame!=frame + case i.timingType + when 0 # Play SE + if i.name && i.name!="" + pbSEPlay("Anim/"+i.name,i.volume,i.pitch) + else + poke = (user && user.pokemon) ? user.pokemon : 1 + name = (pbCryFile(poke) rescue nil) + pbSEPlay(name,i.volume,i.pitch) if name + end +# if sprite +# sprite.flash(i.flashColor,i.flashDuration*2) if i.flashScope==1 +# sprite.flash(nil,i.flashDuration*2) if i.flashScope==3 +# end + when 1 # Set background graphic (immediate) + if i.name && i.name!="" + bgGraphic.setBitmap("Graphics/Animations/"+i.name) + bgGraphic.ox = -i.bgX || 0 + bgGraphic.oy = -i.bgY || 0 + bgGraphic.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0) + bgGraphic.opacity = i.opacity || 0 + bgColor.opacity = 0 + else + bgGraphic.setBitmap(nil) + bgGraphic.opacity = 0 + bgColor.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0) + bgColor.opacity = i.opacity || 0 + end + when 2 # Move/recolour background graphic + if bgGraphic.bitmap!=nil + oldbg[0] = bgGraphic.ox || 0 + oldbg[1] = bgGraphic.oy || 0 + oldbg[2] = bgGraphic.opacity || 0 + oldbg[3] = bgGraphic.color.clone || Color.new(0,0,0,0) + else + oldbg[0] = 0 + oldbg[1] = 0 + oldbg[2] = bgColor.opacity || 0 + oldbg[3] = bgColor.color.clone || Color.new(0,0,0,0) + end + when 3 # Set foreground graphic (immediate) + if i.name && i.name!="" + foGraphic.setBitmap("Graphics/Animations/"+i.name) + foGraphic.ox = -i.bgX || 0 + foGraphic.oy = -i.bgY || 0 + foGraphic.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0) + foGraphic.opacity = i.opacity || 0 + foColor.opacity = 0 + else + foGraphic.setBitmap(nil) + foGraphic.opacity = 0 + foColor.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0) + foColor.opacity = i.opacity || 0 + end + when 4 # Move/recolour foreground graphic + if foGraphic.bitmap!=nil + oldfo[0] = foGraphic.ox || 0 + oldfo[1] = foGraphic.oy || 0 + oldfo[2] = foGraphic.opacity || 0 + oldfo[3] = foGraphic.color.clone || Color.new(0,0,0,0) + else + oldfo[0] = 0 + oldfo[1] = 0 + oldfo[2] = foColor.opacity || 0 + oldfo[3] = foColor.color.clone || Color.new(0,0,0,0) + end + end + end + for i in @timing + case i.timingType + when 2 + next if !i.duration || i.duration<=0 + next if framei.frame+i.duration + fraction = (frame-i.frame).to_f/i.duration + if bgGraphic.bitmap!=nil + bgGraphic.ox = oldbg[0]-(i.bgX-oldbg[0])*fraction if i.bgX!=nil + bgGraphic.oy = oldbg[1]-(i.bgY-oldbg[1])*fraction if i.bgY!=nil + bgGraphic.opacity = oldbg[2]+(i.opacity-oldbg[2])*fraction if i.opacity!=nil + cr = (i.colorRed!=nil) ? oldbg[3].red+(i.colorRed-oldbg[3].red)*fraction : oldbg[3].red + cg = (i.colorGreen!=nil) ? oldbg[3].green+(i.colorGreen-oldbg[3].green)*fraction : oldbg[3].green + cb = (i.colorBlue!=nil) ? oldbg[3].blue+(i.colorBlue-oldbg[3].blue)*fraction : oldbg[3].blue + ca = (i.colorAlpha!=nil) ? oldbg[3].alpha+(i.colorAlpha-oldbg[3].alpha)*fraction : oldbg[3].alpha + bgGraphic.color = Color.new(cr,cg,cb,ca) + else + bgColor.opacity = oldbg[2]+(i.opacity-oldbg[2])*fraction if i.opacity!=nil + cr = (i.colorRed!=nil) ? oldbg[3].red+(i.colorRed-oldbg[3].red)*fraction : oldbg[3].red + cg = (i.colorGreen!=nil) ? oldbg[3].green+(i.colorGreen-oldbg[3].green)*fraction : oldbg[3].green + cb = (i.colorBlue!=nil) ? oldbg[3].blue+(i.colorBlue-oldbg[3].blue)*fraction : oldbg[3].blue + ca = (i.colorAlpha!=nil) ? oldbg[3].alpha+(i.colorAlpha-oldbg[3].alpha)*fraction : oldbg[3].alpha + bgColor.color = Color.new(cr,cg,cb,ca) + end + when 4 + next if !i.duration || i.duration<=0 + next if framei.frame+i.duration + fraction = (frame-i.frame).to_f/i.duration + if foGraphic.bitmap!=nil + foGraphic.ox = oldfo[0]-(i.bgX-oldfo[0])*fraction if i.bgX!=nil + foGraphic.oy = oldfo[1]-(i.bgY-oldfo[1])*fraction if i.bgY!=nil + foGraphic.opacity = oldfo[2]+(i.opacity-oldfo[2])*fraction if i.opacity!=nil + cr = (i.colorRed!=nil) ? oldfo[3].red+(i.colorRed-oldfo[3].red)*fraction : oldfo[3].red + cg = (i.colorGreen!=nil) ? oldfo[3].green+(i.colorGreen-oldfo[3].green)*fraction : oldfo[3].green + cb = (i.colorBlue!=nil) ? oldfo[3].blue+(i.colorBlue-oldfo[3].blue)*fraction : oldfo[3].blue + ca = (i.colorAlpha!=nil) ? oldfo[3].alpha+(i.colorAlpha-oldfo[3].alpha)*fraction : oldfo[3].alpha + foGraphic.color = Color.new(cr,cg,cb,ca) + else + foColor.opacity = oldfo[2]+(i.opacity-oldfo[2])*fraction if i.opacity!=nil + cr = (i.colorRed!=nil) ? oldfo[3].red+(i.colorRed-oldfo[3].red)*fraction : oldfo[3].red + cg = (i.colorGreen!=nil) ? oldfo[3].green+(i.colorGreen-oldfo[3].green)*fraction : oldfo[3].green + cb = (i.colorBlue!=nil) ? oldfo[3].blue+(i.colorBlue-oldfo[3].blue)*fraction : oldfo[3].blue + ca = (i.colorAlpha!=nil) ? oldfo[3].alpha+(i.colorAlpha-oldfo[3].alpha)*fraction : oldfo[3].alpha + foColor.color = Color.new(cr,cg,cb,ca) + end + end + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +def pbSpriteSetAnimFrame(sprite,frame,user=nil,target=nil,inEditor=false) + return if !sprite + if !frame + sprite.visible = false + sprite.src_rect = Rect.new(0,0,1,1) + return + end + sprite.blend_type = frame[AnimFrame::BLENDTYPE] + sprite.angle = frame[AnimFrame::ANGLE] + sprite.mirror = (frame[AnimFrame::MIRROR]>0) + sprite.opacity = frame[AnimFrame::OPACITY] + sprite.visible = true + if !frame[AnimFrame::VISIBLE]==1 && inEditor + sprite.opacity /= 2 + else + sprite.visible = (frame[AnimFrame::VISIBLE]==1) + end + pattern = frame[AnimFrame::PATTERN] + if pattern>=0 + animwidth = 192 + sprite.src_rect.set((pattern%5)*animwidth,(pattern/5)*animwidth, + animwidth,animwidth) + else + sprite.src_rect.set(0,0, + (sprite.bitmap) ? sprite.bitmap.width : 128, + (sprite.bitmap) ? sprite.bitmap.height : 128) + end + sprite.zoom_x = frame[AnimFrame::ZOOMX]/100.0 + sprite.zoom_y = frame[AnimFrame::ZOOMY]/100.0 + sprite.color.set( + frame[AnimFrame::COLORRED], + frame[AnimFrame::COLORGREEN], + frame[AnimFrame::COLORBLUE], + frame[AnimFrame::COLORALPHA] + ) + sprite.tone.set( + frame[AnimFrame::TONERED], + frame[AnimFrame::TONEGREEN], + frame[AnimFrame::TONEBLUE], + frame[AnimFrame::TONEGRAY] + ) + sprite.ox = sprite.src_rect.width/2 + sprite.oy = sprite.src_rect.height/2 + sprite.x = frame[AnimFrame::X] + sprite.y = frame[AnimFrame::Y] + if sprite!=user && sprite!=target + case frame[AnimFrame::PRIORITY] + when 0 # Behind everything + sprite.z = 10 + when 1 # In front of everything + sprite.z = 80 + when 2 # Just behind focus + case frame[AnimFrame::FOCUS] + when 1 # Focused on target + sprite.z = (target) ? target.z-1 : 20 + when 2 # Focused on user + sprite.z = (user) ? user.z-1 : 20 + else # Focused on user and target, or screen + sprite.z = 20 + end + when 3 # Just in front of focus + case frame[AnimFrame::FOCUS] + when 1 # Focused on target + sprite.z = (target) ? target.z+1 : 80 + when 2 # Focused on user + sprite.z = (user) ? user.z+1 : 80 + else # Focused on user and target, or screen + sprite.z = 80 + end + else + sprite.z = 80 + end + end +end + + + +#=============================================================================== +# Animation player +#=============================================================================== +class PBAnimationPlayerX + attr_accessor :looping + MAX_SPRITES = 60 + + def initialize(animation,user,target,scene=nil,oppMove=false,inEditor=false) + @animation = animation + @user = (oppMove) ? target : user # Just used for playing user's cry + @usersprite = (user) ? scene.sprites["pokemon_#{user.index}"] : nil + @targetsprite = (target) ? scene.sprites["pokemon_#{target.index}"] : nil + @userbitmap = (@usersprite && @usersprite.bitmap) ? @usersprite.bitmap : nil # not to be disposed + @targetbitmap = (@targetsprite && @targetsprite.bitmap) ? @targetsprite.bitmap : nil # not to be disposed + @scene = scene + @viewport = (scene) ? scene.viewport : nil + @inEditor = inEditor + @looping = false + @animbitmap = nil # Animation sheet graphic + @frame = -1 + @framesPerTick = [Graphics.frame_rate/20,1].max # 20 ticks per second + @srcLine = nil + @dstLine = nil + @userOrig = getSpriteCenter(@usersprite) + @targetOrig = getSpriteCenter(@targetsprite) + @oldbg = [] + @oldfo = [] + initializeSprites + end + + def initializeSprites + # Create animation sprites (0=user's sprite, 1=target's sprite) + @animsprites = [] + @animsprites[0] = @usersprite + @animsprites[1] = @targetsprite + for i in 2...MAX_SPRITES + @animsprites[i] = Sprite.new(@viewport) + @animsprites[i].bitmap = nil + @animsprites[i].visible = false + end + # Create background colour sprite + @bgColor = ColoredPlane.new(Color.new(0,0,0),@viewport) + @bgColor.borderX = 64 if @inEditor + @bgColor.borderY = 64 if @inEditor + @bgColor.z = 5 + @bgColor.opacity = 0 + @bgColor.refresh + # Create background graphic sprite + @bgGraphic = AnimatedPlane.new(@viewport) + @bgGraphic.setBitmap(nil) + @bgGraphic.borderX = 64 if @inEditor + @bgGraphic.borderY = 64 if @inEditor + @bgGraphic.z = 5 + @bgGraphic.opacity = 0 + @bgGraphic.refresh + # Create foreground colour sprite + @foColor = ColoredPlane.new(Color.new(0,0,0),@viewport) + @foColor.borderX = 64 if @inEditor + @foColor.borderY = 64 if @inEditor + @foColor.z = 85 + @foColor.opacity = 0 + @foColor.refresh + # Create foreground graphic sprite + @foGraphic = AnimatedPlane.new(@viewport) + @foGraphic.setBitmap(nil) + @foGraphic.borderX = 64 if @inEditor + @foGraphic.borderY = 64 if @inEditor + @foGraphic.z = 85 + @foGraphic.opacity = 0 + @foGraphic.refresh + end + + def dispose + @animbitmap.dispose if @animbitmap + for i in 2...MAX_SPRITES + @animsprites[i].dispose if @animsprites[i] + end + @bgGraphic.dispose + @bgColor.dispose + @foGraphic.dispose + @foColor.dispose + end + + def start + @frame = 0 + end + + def animDone? + return @frame<0 + end + + def setLineTransform(x1,y1,x2,y2,x3,y3,x4,y4) + @srcLine = [x1,y1,x2,y2] + @dstLine = [x3,y3,x4,y4] + end + + def update + return if @frame<0 + animFrame = @frame/@framesPerTick + + # Loop or end the animation if the animation has reached the end + if animFrame >= @animation.length + @frame = (@looping) ? 0 : -1 + if @frame<0 + @animbitmap.dispose if @animbitmap + @animbitmap = nil + return + end + end + # Load the animation's spritesheet and assign it to all the sprites. + if !@animbitmap || @animbitmap.disposed? + @animbitmap = AnimatedBitmap.new("Graphics/Animations/"+@animation.graphic, + @animation.hue).deanimate + for i in 0...MAX_SPRITES + @animsprites[i].bitmap = @animbitmap if @animsprites[i] + end + end + # Update background and foreground graphics + @bgGraphic.update + @bgColor.update + @foGraphic.update + @foColor.update + + # Update all the sprites to depict the animation's next frame + if @framesPerTick==1 || (@frame%@framesPerTick)==0 + thisframe = @animation[animFrame] + # Make all cel sprites invisible + for i in 0...MAX_SPRITES + @animsprites[i].visible = false if @animsprites[i] + end + # Set each cel sprite acoordingly + for i in 0...thisframe.length + cel = thisframe[i] + next if !cel + sprite = @animsprites[i] + next if !sprite + # Set cel sprite's graphic + case cel[AnimFrame::PATTERN] + when -1 + sprite.bitmap = @userbitmap + when -2 + sprite.bitmap = @targetbitmap + else + sprite.bitmap = @animbitmap + end + # Apply settings to the cel sprite + pbSpriteSetAnimFrame(sprite,cel,@usersprite,@targetsprite) + case cel[AnimFrame::FOCUS] + when 1 # Focused on target + sprite.x = cel[AnimFrame::X]+@targetOrig[0]-PokeBattle_SceneConstants::FOCUSTARGET_X + sprite.y = cel[AnimFrame::Y]+@targetOrig[1]-PokeBattle_SceneConstants::FOCUSTARGET_Y + when 2 # Focused on user + sprite.x = cel[AnimFrame::X]+@userOrig[0]-PokeBattle_SceneConstants::FOCUSUSER_X + sprite.y = cel[AnimFrame::Y]+@userOrig[1]-PokeBattle_SceneConstants::FOCUSUSER_Y + when 3 # Focused on user and target + next if !@srcLine || !@dstLine + point = transformPoint( + @srcLine[0],@srcLine[1],@srcLine[2],@srcLine[3], + @dstLine[0],@dstLine[1],@dstLine[2],@dstLine[3], + sprite.x,sprite.y) + sprite.x = point[0] + sprite.y = point[1] + if isReversed(@srcLine[0],@srcLine[2],@dstLine[0],@dstLine[2]) && + cel[AnimFrame::PATTERN]>=0 + # Reverse direction + sprite.mirror = !sprite.mirror + end + end + sprite.x += 64 if @inEditor + sprite.y += 64 if @inEditor + end + # Play timings + @animation.playTiming(animFrame,@bgGraphic,@bgColor,@foGraphic,@foColor,@oldbg,@oldfo,@user) + end + @frame += 1 + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/002_PokeBattle_SafariZone.rb b/Data/Scripts/011_Battle/006_Other battle types/002_PokeBattle_SafariZone.rb new file mode 100644 index 000000000..072e14591 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/002_PokeBattle_SafariZone.rb @@ -0,0 +1,494 @@ +#=============================================================================== +# Simple battler class for the wild Pokémon in a Safari Zone battle +#=============================================================================== +class PokeBattle_FakeBattler + attr_reader :battle + attr_reader :index + attr_reader :pokemon + attr_reader :owned + + def initialize(battle,index) + @battle = battle + @pokemon = battle.party2[0] + @index = index + end + + def pokemonIndex; return 0; end + def species; return @pokemon.species; end + def gender; return @pokemon.gender; end + def status; return @pokemon.status; end + def hp; return @pokemon.hp; end + def level; return @pokemon.level; end + def name; return @pokemon.name; end + def totalhp; return @pokemon.totalhp; end + def displayGender; return @pokemon.gender; end + def shiny?; return @pokemon.shiny?; end + alias isShiny? shiny? + + def fainted?; return false; end + alias isFainted? fainted? + def shadowPokemon?; return false; end + alias isShadow? shadowPokemon? + def hasMega?; return false; end + def mega?; return false; end + alias isMega? mega? + def hasPrimal?; return false; end + def primal?; return false; end + alias isPrimal? primal? + def captured; return false; end + def captured=(value); end + + def owned? + return $Trainer.owned[pokemon.species] + end + + def pbThis(lowerCase=false) + return (lowerCase) ? _INTL("the wild {1}",name) : _INTL("The wild {1}",name) + end + + def opposes?(i) + i = i.index if i.is_a?(PokeBattle_FakeBattler) + return (@index&1)!=(i&1) + end + + def pbReset; end +end + + + +#=============================================================================== +# Data box for safari battles +#=============================================================================== +class SafariDataBox < SpriteWrapper + attr_accessor :selected + + def initialize(battle,viewport=nil) + super(viewport) + @selected = 0 + @battle = battle + @databox = AnimatedBitmap.new("Graphics/Pictures/Battle/databox_safari") + self.x = Graphics.width - 232 + self.y = Graphics.height - 184 + @contents = BitmapWrapper.new(@databox.width,@databox.height) + self.bitmap = @contents + self.visible = false + self.z = 50 + pbSetSystemFont(self.bitmap) + refresh + end + + def refresh + self.bitmap.clear + self.bitmap.blt(0,0,@databox.bitmap,Rect.new(0,0,@databox.width,@databox.height)) + base = Color.new(72,72,72) + shadow = Color.new(184,184,184) + textpos = [] + textpos.push([_INTL("Safari Balls"),30,8,false,base,shadow]) + textpos.push([_INTL("Left: {1}",@battle.ballCount),30,38,false,base,shadow]) + pbDrawTextPositions(self.bitmap,textpos) + end + + def update(frameCounter=0) + super() + end +end + + + +#=============================================================================== +# Shows the player throwing bait at a wild Pokémon in a Safari battle. +#=============================================================================== +class ThrowBaitAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,battler) + @battler = battler + @trainer = battler.battle.pbGetOwnerFromBattlerIndex(battler.index) + super(sprites,viewport) + end + + def createProcesses + # Calculate start and end coordinates for battler sprite movement + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + traSprite = @sprites["player_1"] + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + ballStartX = traSprite.x + ballStartY = traSprite.y-traSprite.bitmap.height/2 + ballMidX = 0 # Unused in arc calculation + ballMidY = 122 + ballEndX = ballPos[0]-40 + ballEndY = ballPos[1]-4 + # Set up trainer sprite + trainer = addSprite(traSprite,PictureOrigin::Bottom) + # Set up bait sprite + ball = addNewSprite(ballStartX,ballStartY, + "Graphics/Battle animations/safari_bait",PictureOrigin::Center) + ball.setZ(0,batSprite.z+1) + # Trainer animation + if traSprite.bitmap.width>=traSprite.bitmap.height*2 + ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite) + end + delay = ball.totalDuration # 0 or 7 + # Bait arc animation + ball.setSE(delay,"Battle throw") + createBallTrajectory(ball,delay,12, + ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) + ball.setZ(9,batSprite.z+1) + delay = ball.totalDuration + ball.moveOpacity(delay+8,2,0) + ball.setVisible(delay+10,false) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + # Show Pokémon jumping before eating the bait + delay = ball.totalDuration+3 + 2.times do + battler.setSE(delay,"player jump") + battler.moveDelta(delay,3,0,-16) + battler.moveDelta(delay+4,3,0,16) + delay = battler.totalDuration+1 + end + # Show Pokémon eating the bait + delay = battler.totalDuration+3 + 2.times do + battler.moveAngle(delay,7,5) + battler.moveDelta(delay,7,0,6) + battler.moveAngle(delay+7,7,0) + battler.moveDelta(delay+7,7,0,-6) + delay = battler.totalDuration + end + end +end + + + +#=============================================================================== +# Shows the player throwing a rock at a wild Pokémon in a Safari battle. +#=============================================================================== +class ThrowRockAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,battler) + @battler = battler + @trainer = battler.battle.pbGetOwnerFromBattlerIndex(battler.index) + super(sprites,viewport) + end + + def createProcesses + # Calculate start and end coordinates for battler sprite movement + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + traSprite = @sprites["player_1"] + ballStartX = traSprite.x + ballStartY = traSprite.y-traSprite.bitmap.height/2 + ballMidX = 0 # Unused in arc calculation + ballMidY = 122 + ballEndX = batSprite.x + ballEndY = batSprite.y-batSprite.bitmap.height/2 + # Set up trainer sprite + trainer = addSprite(traSprite,PictureOrigin::Bottom) + # Set up bait sprite + ball = addNewSprite(ballStartX,ballStartY, + "Graphics/Battle animations/safari_rock",PictureOrigin::Center) + ball.setZ(0,batSprite.z+1) + # Trainer animation + if traSprite.bitmap.width>=traSprite.bitmap.height*2 + ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite) + end + delay = ball.totalDuration # 0 or 7 + # Bait arc animation + ball.setSE(delay,"Battle throw") + createBallTrajectory(ball,delay,12, + ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) + ball.setZ(9,batSprite.z+1) + delay = ball.totalDuration + ball.setSE(delay,"Battle damage weak") + ball.moveOpacity(delay+2,2,0) + ball.setVisible(delay+4,false) + # Set up anger sprite + anger = addNewSprite(ballEndX-42,ballEndY-36, + "Graphics/Battle animations/safari_anger",PictureOrigin::Center) + anger.setVisible(0,false) + anger.setZ(0,batSprite.z+1) + # Show anger appearing + delay = ball.totalDuration+5 + 2.times do + anger.setSE(delay,"Player jump") + anger.setVisible(delay,true) + anger.moveZoom(delay,3,130) + anger.moveZoom(delay+3,3,100) + anger.setVisible(delay+6,false) + anger.setDelta(delay+6,96,-16) + delay = anger.totalDuration+3 + end + end +end + + + +#=============================================================================== +# Safari Zone battle scene (the visuals of the battle) +#=============================================================================== +class PokeBattle_Scene + def pbSafariStart + @briefMessage = false + @sprites["dataBox_0"] = SafariDataBox.new(@battle,@viewport) + dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,0) + loop do + dataBoxAnim.update + pbUpdate + break if dataBoxAnim.animDone? + end + dataBoxAnim.dispose + pbRefresh + end + + def pbSafariCommandMenu(index) + pbCommandMenuEx(index,[ + _INTL("What will\n{1} throw?",@battle.pbPlayer.name), + _INTL("Ball"), + _INTL("Bait"), + _INTL("Rock"), + _INTL("Run") + ],3) + end + + def pbThrowBait + @briefMessage = false + baitAnim = ThrowBaitAnimation.new(@sprites,@viewport,@battle.battlers[1]) + loop do + baitAnim.update + pbUpdate + break if baitAnim.animDone? + end + baitAnim.dispose + end + + def pbThrowRock + @briefMessage = false + rockAnim = ThrowRockAnimation.new(@sprites,@viewport,@battle.battlers[1]) + loop do + rockAnim.update + pbUpdate + break if rockAnim.animDone? + end + rockAnim.dispose + end + + alias __safari__pbThrowSuccess pbThrowSuccess + def pbThrowSuccess + __safari__pbThrowSuccess + pbWildBattleSuccess if @battle.is_a?(PokeBattle_SafariZone) + end +end + + + +#=============================================================================== +# Safari Zone battle class +#=============================================================================== +class PokeBattle_SafariZone + attr_reader :battlers # Array of fake battler objects + attr_accessor :sideSizes # Array of number of battlers per side + attr_accessor :backdrop # Filename fragment used for background graphics + attr_accessor :backdropBase # Filename fragment used for base graphics + attr_accessor :time # Time of day (0=day, 1=eve, 2=night) + attr_accessor :environment # Battle surroundings (for mechanics purposes) + attr_reader :weather + attr_reader :player + attr_accessor :party2 + attr_accessor :canRun # True if player can run from battle + attr_accessor :canLose # True if player won't black out if they lose + attr_accessor :switchStyle # Switch/Set "battle style" option + attr_accessor :showAnims # "Battle scene" option (show anims) + attr_accessor :expGain # Whether Pokémon can gain Exp/EVs + attr_accessor :moneyGain # Whether the player can gain/lose money + attr_accessor :rules + attr_accessor :ballCount + + include PokeBattle_BattleCommon + + def pbRandom(x); return rand(x); end + + #============================================================================= + # Initialize the battle class + #============================================================================= + def initialize(scene,player,party2) + @scene = scene + @peer = PokeBattle_BattlePeer.create() + @backdrop = "" + @backdropBase = nil + @time = 0 + @environment = PBEnvironment::None # e.g. Tall grass, cave, still water + @weather = PBWeather::None + @decision = 0 + @caughtPokemon = [] + @player = [player] + @party2 = party2 + @sideSizes = [1,1] + @battlers = [ + PokeBattle_FakeBattler.new(self,0), + PokeBattle_FakeBattler.new(self,1) + ] + @rules = {} + @ballCount = 0 + end + + def defaultWeather=(value); @weather = value; end + def defaultTerrain=(value); end + + #============================================================================= + # Information about the type and size of the battle + #============================================================================= + def wildBattle?; return true; end + def trainerBattle?; return false; end + + def setBattleMode(mode); end + + def pbSideSize(index) + return @sideSizes[index%2] + end + + #============================================================================= + # Trainers and owner-related + #============================================================================= + def pbPlayer; return @player[0]; end + def opponent; return nil; end + + def pbGetOwnerFromBattlerIndex(idxBattler); return pbPlayer; end + + #============================================================================= + # Get party info (counts all teams on the same side) + #============================================================================= + def pbParty(idxBattler) + return (opposes?(idxBattler)) ? @party2 : nil + end + + def pbAllFainted?(idxBattler=0); return false; end + + #============================================================================= + # Battler-related + #============================================================================= + def opposes?(idxBattler1,idxBattler2=0) + idxBattler1 = idxBattler1.index if idxBattler1.respond_to?("index") + idxBattler2 = idxBattler2.index if idxBattler2.respond_to?("index") + return (idxBattler1&1)!=(idxBattler2&1) + end + + def pbRemoveFromParty(idxBattler,idxParty); end + def pbGainExp; end + + #============================================================================= + # Messages and animations + #============================================================================= + def pbDisplay(msg,&block) + @scene.pbDisplayMessage(msg,&block) + end + + def pbDisplayPaused(msg,&block) + @scene.pbDisplayPausedMessage(msg,&block) + end + + def pbDisplayBrief(msg) + @scene.pbDisplayMessage(msg,true) + end + + def pbDisplayConfirm(msg) + return @scene.pbDisplayConfirmMessage(msg) + end + + + + class BattleAbortedException < Exception; end + + def pbAbort + raise BattleAbortedException.new("Battle aborted") + end + + #============================================================================= + # Safari battle-specific methods + #============================================================================= + def pbEscapeRate(rareness) + return 125 if rareness<=45 # Escape factor 9 (45%) + return 100 if rareness<=60 # Escape factor 7 (35%) + return 75 if rareness<=120 # Escape factor 5 (25%) + return 50 if rareness<=250 # Escape factor 3 (15%) + return 25 # Escape factor 2 (10%) + end + + def pbStartBattle + begin + wildpoke = @party2[0] + self.pbPlayer.seen[wildpoke.species] = true + pbSeenForm(wildpoke) + @scene.pbStartBattle(self) + pbDisplayPaused(_INTL("Wild {1} appeared!",wildpoke.name)) + @scene.pbSafariStart + @scene.pbCommonAnimation(PBWeather.animationName(@weather)) + safariBall = getConst(PBItems,:SAFARIBALL) + rareness = pbGetSpeciesData(wildpoke.species,wildpoke.form,SpeciesRareness) + catchFactor = (rareness*100)/1275 + catchFactor = [[catchFactor,3].max,20].min + escapeFactor = (pbEscapeRate(rareness)*100)/1275 + escapeFactor = [[escapeFactor,2].max,20].min + begin + cmd = @scene.pbSafariCommandMenu(0) + case cmd + when 0 # Ball + if pbBoxesFull? + pbDisplay(_INTL("The boxes are full! You can't catch any more Pokémon!")) + next + end + @ballCount -= 1 + @scene.pbRefresh + rare = (catchFactor*1275)/100 + if safariBall + pbThrowPokeBall(1,safariBall,rare,true) + if @caughtPokemon.length>0 + pbRecordAndStoreCaughtPokemon + @decision = 4 + end + end + when 1 # Bait + pbDisplayBrief(_INTL("{1} threw some bait at the {2}!",self.pbPlayer.name,wildpoke.name)) + @scene.pbThrowBait + catchFactor /= 2 if pbRandom(100)<90 # Harder to catch + escapeFactor /= 2 # Less likely to escape + when 2 # Rock + pbDisplayBrief(_INTL("{1} threw a rock at the {2}!",self.pbPlayer.name,wildpoke.name)) + @scene.pbThrowRock + catchFactor *= 2 # Easier to catch + escapeFactor *= 2 if pbRandom(100)<90 # More likely to escape + when 3 # Run + pbDisplayPaused(_INTL("You got away safely!")) { pbSEPlay("Battle flee") } + @decision = 3 + end + catchFactor = [[catchFactor,3].max,20].min + escapeFactor = [[escapeFactor,2].max,20].min + # End of round + if @decision==0 + if @ballCount<=0 + pbDisplay(_INTL("PA: You have no Safari Balls left! Game over!")) + @decision = 2 + elsif pbRandom(100)<5*escapeFactor + pbDisplay(_INTL("{1} fled!",wildpoke.name)) { pbSEPlay("Battle flee") } + @decision = 3 + elsif cmd==1 # Bait + pbDisplay(_INTL("{1} is eating!",wildpoke.name)) + elsif cmd==2 # Rock + pbDisplay(_INTL("{1} is angry!",wildpoke.name)) + else + pbDisplay(_INTL("{1} is watching carefully!",wildpoke.name)) + end + # Weather continues + @scene.pbCommonAnimation(PBWeather.animationName(@weather)) + end + end while @decision==0 + @scene.pbEndBattle(@decision) + rescue BattleAbortedException + @decision = 0 + @scene.pbEndBattle(@decision) + end + return @decision + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/003_PokeBattle_BugContest.rb b/Data/Scripts/011_Battle/006_Other battle types/003_PokeBattle_BugContest.rb new file mode 100644 index 000000000..cddc5f4a1 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/003_PokeBattle_BugContest.rb @@ -0,0 +1,87 @@ +#=============================================================================== +# Bug Catching Contest battle scene (the visuals of the battle) +#=============================================================================== +class PokeBattle_Scene + alias _bugContest_pbInitSprites pbInitSprites + + def pbInitSprites + _bugContest_pbInitSprites + # "helpwindow" shows the currently caught Pokémon's details when asking if + # you want to replace it with a newly caught Pokémon. + @sprites["helpwindow"] = Window_UnformattedTextPokemon.newWithSize("",0,0,32,32,@viewport) + @sprites["helpwindow"].z = 90 + @sprites["helpwindow"].visible = false + end + + def pbShowHelp(text) + @sprites["helpwindow"].resizeToFit(text,Graphics.width) + @sprites["helpwindow"].y = 0 + @sprites["helpwindow"].x = 0 + @sprites["helpwindow"].text = text + @sprites["helpwindow"].visible = true + end + + def pbHideHelp + @sprites["helpwindow"].visible = false + end +end + + + +#=============================================================================== +# Bug Catching Contest battle class +#=============================================================================== +class PokeBattle_BugContestBattle < PokeBattle_Battle + attr_accessor :ballCount + + def initialize(*arg) + @ballCount = 0 + @ballConst = getConst(PBItems,:SPORTBALL) || -1 + super(*arg) + end + + def pbItemMenu(idxBattler,firstAction) + return pbRegisterItem(idxBattler,@ballConst,1) + end + + def pbCommandMenu(idxBattler,firstAction) + return @scene.pbCommandMenuEx(idxBattler,[ + _INTL("Sport Balls: {1}",@ballCount), + _INTL("Fight"), + _INTL("Ball"), + _INTL("Pokémon"), + _INTL("Run") + ],4) + end + + def pbConsumeItemInBag(item,idxBattler) + @ballCount -= 1 if @ballCount>0 + end + + def pbStorePokemon(pkmn) + if pbBugContestState.lastPokemon + lastPokemon = pbBugContestState.lastPokemon + pbDisplayPaused(_INTL("You already caught a {1}.",lastPokemon.name)) + helptext = _INTL("STOCK POKéMON:\n {1} Lv.{2} MaxHP: {3}\nTHIS POKéMON:\n {4} Lv.{5} MaxHP: {6}", + lastPokemon.name,lastPokemon.level,lastPokemon.totalhp, + pkmn.name,pkmn.level,pkmn.totalhp + ) + @scene.pbShowHelp(helptext) + if pbDisplayConfirm(_INTL("Switch Pokémon?")) + pbBugContestState.lastPokemon = pkmn + @scene.pbHideHelp + else + @scene.pbHideHelp + return + end + else + pbBugContestState.lastPokemon = pkmn + end + pbDisplay(_INTL("Caught {1}!",pkmn.name)) + end + + def pbEndOfRoundPhase + super + @decision = 3 if @ballCount<=0 && @decision==0 + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/004_PokeBattle_BattlePalace.rb b/Data/Scripts/011_Battle/006_Other battle types/004_PokeBattle_BattlePalace.rb new file mode 100644 index 000000000..b73849dd7 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/004_PokeBattle_BattlePalace.rb @@ -0,0 +1,275 @@ +#=============================================================================== +# +#=============================================================================== +class PokeBattle_BattlePalace < PokeBattle_Battle + @@BattlePalaceUsualTable = [ + 61, 7, 32, + 20, 25, 55, + 70, 15, 15, + 38, 31, 31, + 20, 70, 10, + 30, 20, 50, + 56, 22, 22, + 25, 15, 60, + 69, 6, 25, + 35, 10, 55, + 62, 10, 28, + 58, 37, 5, + 34, 11, 55, + 35, 5, 60, + 56, 22, 22, + 35, 45, 20, + 44, 50, 6, + 56, 22, 22, + 30, 58, 12, + 30, 13, 57, + 40, 50, 10, + 18, 70, 12, + 88, 6, 6, + 42, 50, 8, + 56, 22, 22 + ] + @@BattlePalacePinchTable = [ + 61, 7, 32, + 84, 8, 8, + 32, 60, 8, + 70, 15, 15, + 70, 22, 8, + 32, 58, 10, + 56, 22, 22, + 75, 15, 10, + 28, 55, 17, + 29, 6, 65, + 30, 20, 50, + 88, 6, 6, + 29, 11, 60, + 35, 60, 5, + 56, 22, 22, + 34, 60, 6, + 34, 6, 60, + 56, 22, 22, + 30, 58, 12, + 27, 6, 67, + 25, 62, 13, + 90, 5, 5, + 22, 20, 58, + 42, 5, 53, + 56, 22, 22 + ] + + def initialize(*arg) + super + @justswitched = [false,false,false,false] + @battleAI.battlePalace = true + end + + def pbMoveCategory(move) + if move.target==PBTargets::User || move.function=="0D4" # Bide + return 1 + elsif move.statusMove? || + move.function=="071" || move.function=="072" # Counter, Mirror Coat + return 2 + else + return 0 + end + end + + # Different implementation of pbCanChooseMove, ignores Imprison/Torment/Taunt/Disable/Encore + def pbCanChooseMovePartial?(idxPokemon,idxMove) + thispkmn = @battlers[idxPokemon] + thismove = thispkmn.moves[idxMove] + return false if !thismove || thismove.id==0 + return false if thismove.pp<=0 + if thispkmn.effects[PBEffects::ChoiceBand]>=0 && + thismove.id!=thispkmn.effects[PBEffects::ChoiceBand] && + thispkmn.hasActiveItem?(:CHOICEBAND) + return false + end + # though incorrect, just for convenience (actually checks Torment later) + if thispkmn.effects[PBEffects::Torment] + return false if thismove.id==thispkmn.lastMoveUsed + end + return true + end + + def pbPinchChange(idxPokemon) + thispkmn = @battlers[idxPokemon] + if !thispkmn.effects[PBEffects::Pinch] && thispkmn.status!=PBStatuses::SLEEP && + thispkmn.hp<=thispkmn.totalhp/2 + nature = thispkmn.nature + thispkmn.effects[PBEffects::Pinch] = true + if nature==PBNatures::QUIET || + nature==PBNatures::BASHFUL || + nature==PBNatures::NAIVE || + nature==PBNatures::QUIRKY || + nature==PBNatures::HARDY || + nature==PBNatures::DOCILE || + nature==PBNatures::SERIOUS + pbDisplay(_INTL("{1} is eager for more!",thispkmn.pbThis)) + end + if nature==PBNatures::CAREFUL || + nature==PBNatures::RASH || + nature==PBNatures::LAX || + nature==PBNatures::SASSY || + nature==PBNatures::MILD || + nature==PBNatures::TIMID + pbDisplay(_INTL("{1} began growling deeply!",thispkmn.pbThis)) + end + if nature==PBNatures::GENTLE || + nature==PBNatures::ADAMANT || + nature==PBNatures::HASTY || + nature==PBNatures::LONELY || + nature==PBNatures::RELAXED || + nature==PBNatures::NAUGHTY + pbDisplay(_INTL("A glint appears in {1}'s eyes!",thispkmn.pbThis(true))) + end + if nature==PBNatures::JOLLY || + nature==PBNatures::BOLD || + nature==PBNatures::BRAVE || + nature==PBNatures::CALM || + nature==PBNatures::IMPISH || + nature==PBNatures::MODEST + pbDisplay(_INTL("{1} is getting into position!",thispkmn.pbThis)) + end + end + end + + def pbRegisterMove(idxBattler,idxMove,showMessages=true) + thispkmn = @battlers[idxBattler] + if idxMove==-2 + @choices[idxPokemon][0] = :UseMove # Move + @choices[idxPokemon][1] = -2 # "Incapable of using its power..." + @choices[idxPokemon][2] = @struggle + @choices[idxPokemon][3] = -1 + else + @choices[idxPokemon][0] = :UseMove # Move + @choices[idxPokemon][1] = idxMove # Index of move + @choices[idxPokemon][2] = thispkmn.moves[idxMove] # Move object + @choices[idxPokemon][3] = -1 # No target chosen + end + end + + def pbAutoFightMenu(idxBattler) + thispkmn = @battlers[idxBattler] + nature = thispkmn.nature + randnum = @battleAI.pbAIRandom(100) + category = 0 + atkpercent = 0 + defpercent = 0 + if thispkmn.effects[PBEffects::Pinch] + atkpercent = @@BattlePalacePinchTable[nature*3] + defpercent = atkpercent+@@BattlePalacePinchTable[nature*3+1] + else + atkpercent = @@BattlePalaceUsualTable[nature*3] + defpercent = atkpercent+@@BattlePalaceUsualTable[nature*3+1] + end + if randnum5 + shouldswitch = true + else + hppercent = thispkmn.hp*100/thispkmn.totalhp + percents = [] + maxindex = -1 + maxpercent = 0 + factor = 0 + @battle.pbParty(idxBattler).each_with_index do |pkmn,i| + if @battle.pbCanSwitch?(idxBattler,i) + percents[i] = 100*pkmn.hp/pkmn.totalhp + if percents[i]>maxpercent + maxindex = i + maxpercent = percents[i] + end + else + percents[i] = 0 + end + end + if hppercent<50 + factor = (maxpercent=0 + @battle.pbRegisterSwitch(idxBattler,maxindex) + return true + end + end + @justswitched[idxBattler] = shouldswitch + if shouldswitch + @battle.pbParty(idxBattler).each_with_index do |pkmn,i| + next if !@battle.pbCanSwitch?(idxBattler,i) + @battle.pbRegisterSwitch(idxBattler,i) + return true + end + end + return false + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/005_PokeBattle_BattleArena.rb b/Data/Scripts/011_Battle/006_Other battle types/005_PokeBattle_BattleArena.rb new file mode 100644 index 000000000..0a450e5f9 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/005_PokeBattle_BattleArena.rb @@ -0,0 +1,313 @@ +#=============================================================================== +# +#=============================================================================== +class PokeBattle_BattleArena < PokeBattle_Battle + def initialize(*arg) + super + @battlersChanged = true + @mind = [0,0] + @skill = [0,0] + @starthp = [0,0] + @count = 0 + @partyindexes = [0,0] + @battleAI.battleArena = true + end + + def pbCanSwitchLax?(idxBattler,idxParty,partyScene=nil) + if partyScene + partyScene.pbDisplay(_INTL("{1} can't be switched out!",@battlers[idxBattler].pbThis)) + end + return false + end + + def pbEORSwitch(favorDraws=false) + return if favorDraws && @decision==5 + return if !favorDraws && @decision>0 + pbJudge + return if @decision>0 + for side in 0...2 + next if !@battlers[side].fainted? + next if @partyindexes[side]+1>=self.pbParty(side).length + @partyindexes[side] += 1 + newpoke = @partyindexes[side] + pbMessagesOnReplace(side,newpoke) + pbReplace(side,newpoke) + pbOnActiveOne(@battlers[side]) + @battlers[side].pbEffectsOnSwitchIn(true) + end + end + + def pbOnActiveAll + @battlersChanged = true + for side in 0...2 + @mind[side] = 0 + @skill[side] = 0 + @starthp[side] = battlers[side].hp + end + @count = 0 + return super + end + + def pbOnActiveOne(*arg) + @battlersChanged = true + for side in 0...2 + @mind[side] = 0 + @skill[side] = 0 + @starthp[side] = battlers[side].hp + end + @count = 0 + return super + end + + def pbMindScore(move) + if move.function=="0AA" || # Detect/Protect + move.function=="0E8" || # Endure + move.function=="012" # Fake Out + return -1 + end + if move.function=="071" || # Counter + move.function=="072" || # Mirror Coat + move.function=="0D4" # Bide + return 0 + end + return 0 if move.statusMove? + return 1 + end + + def pbCommandPhase + if @battlersChanged + @scene.pbBattleArenaBattlers(@battlers[0],@battlers[1]) + @battlersChanged = false + @count = 0 + end + super + return if @decision!=0 + # Update mind rating (asserting that a move was chosen) + # TODO: Actually done at Pokémon's turn + for side in 0...2 + if @choices[side][2] && @choices[side][0]==:UseMove + @mind[side] += pbMindScore(@choices[side][2]) + end + end + end + + def pbEndOfRoundPhase + super + return if @decision!=0 + @count += 1 + # Update skill rating + for side in 0...2 + @skill[side] += self.successStates[side].skill + end +# PBDebug.log("[Mind: #{@mind.inspect}, Skill: #{@skill.inspect}]") + if @count==3 + points = [0,0] + @battlers[0].pbCancelMoves + @battlers[1].pbCancelMoves + ratings1 = [0,0,0] + ratings2 = [0,0,0] + if @mind[0]==@mind[1] + ratings1[0] = 1 + ratings2[0] = 1 + elsif @mind[0]>@mind[1] + ratings1[0] = 2 + else + ratings2[0] = 2 + end + if @skill[0]==@skill[1] + ratings1[1] = 1 + ratings2[1] = 1 + elsif @skill[0]>@skill[1] + ratings1[1] = 2 + else + ratings2[1] = 2 + end + body = [0,0] + body[0] = ((@battlers[0].hp*100)/[@starthp[0],1].max).floor + body[1] = ((@battlers[1].hp*100)/[@starthp[1],1].max).floor + if body[0]==body[1] + ratings1[2] = 1 + ratings2[2] = 1 + elsif body[0]>body[1] + ratings1[2] = 2 + else + ratings2[2] = 2 + end + @scene.pbBattleArenaJudgment(@battlers[0],@battlers[1],ratings1.clone,ratings2.clone) + points = [0,0] + for i in 0...ratings1.length + points[0] += ratings1[i] + points[1] += ratings2[i] + end + if points[0]==points[1] + pbDisplay(_INTL("{1} tied the opponent\n{2} in a referee's decision!", + @battlers[0].name,@battlers[1].name)) + # NOTE: Pokémon doesn't really lose HP, but the effect is mostly the + # same. + @battlers[0].hp = 0 + @battlers[0].pbFaint(false) + @battlers[1].hp = 0 + @battlers[1].pbFaint(false) + elsif points[0]>points[1] + pbDisplay(_INTL("{1} defeated the opponent\n{2} in a referee's decision!", + @battlers[0].name,@battlers[1].name)) + @battlers[1].hp = 0 + @battlers[1].pbFaint(false) + else + pbDisplay(_INTL("{1} lost to the opponent\n{2} in a referee's decision!", + @battlers[0].name,@battlers[1].name)) + @battlers[0].hp = 0 + @battlers[0].pbFaint(false) + end + pbGainExp + pbEORSwitch + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_AI + attr_accessor :battleArena + + alias _battleArena_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw? + + def pbEnemyShouldWithdraw?(idxBattler) + return _battleArena_pbEnemyShouldWithdraw?(idxBattler) if !@battleArena + return false + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_Scene + def pbBattleArenaUpdate + pbGraphicsUpdate + end + + def updateJudgment(window,phase,battler1,battler2,ratings1,ratings2) + total1 = 0 + total2 = 0 + for i in 0...phase + total1 += ratings1[i] + total2 += ratings2[i] + end + window.contents.clear + pbSetSystemFont(window.contents) + textpos = [ + [battler1.name,64,0,2,Color.new(248,0,0),Color.new(208,208,200)], + [_INTL("VS"),144,0,2,Color.new(72,72,72),Color.new(208,208,200)], + [battler2.name,224,0,2,Color.new(72,72,72),Color.new(208,208,200)], + [_INTL("Mind"),144,48,2,Color.new(72,72,72),Color.new(208,208,200)], + [_INTL("Skill"),144,80,2,Color.new(72,72,72),Color.new(208,208,200)], + [_INTL("Body"),144,112,2,Color.new(72,72,72),Color.new(208,208,200)], + [sprintf("%d",total1),64,160,2,Color.new(72,72,72),Color.new(208,208,200)], + [_INTL("Judgment"),144,160,2,Color.new(72,72,72),Color.new(208,208,200)], + [sprintf("%d",total2),224,160,2,Color.new(72,72,72),Color.new(208,208,200)] + ] + pbDrawTextPositions(window.contents,textpos) + images = [] + for i in 0...phase + y = [48,80,112][i] + x = (ratings1[i]==ratings2[i]) ? 64 : ((ratings1[i]>ratings2[i]) ? 0 : 32) + images.push(["Graphics/Pictures/judgment",64-16,y,x,0,32,32]) + x = (ratings1[i]==ratings2[i]) ? 64 : ((ratings1[i]total2 + pbMessageDisplay(msgwindow, + _INTL("REFEREE: Judgment: {1} to {2}!\nThe winner is {3}'s {4}!\\wtnp[40]", + total1,total2,@battle.pbGetOwnerName(battler1.index),battler1.name)) { + pbBattleArenaUpdate; dimmingvp.update; infowindow.update } + else + pbMessageDisplay(msgwindow, + _INTL("REFEREE: Judgment: {1} to {2}!\nThe winner is {3}!\\wtnp[40]", + total1,total2,battler2.name)) { + pbBattleArenaUpdate; dimmingvp.update; infowindow.update } + end + infowindow.visible = false + msgwindow.visible = false + for i in 0..10 + pbGraphicsUpdate + pbInputUpdate + msgwindow.update + dimmingvp.update + dimmingvp.color = Color.new(0,0,0,(10-i)*128/10) + end + ensure + pbDisposeMessageWindow(msgwindow) + dimmingvp.dispose + infowindow.contents.dispose + infowindow.dispose + end + end +end diff --git a/Data/Scripts/011_Battle/006_Other battle types/006_PokeBattle_BattleRecord.rb b/Data/Scripts/011_Battle/006_Other battle types/006_PokeBattle_BattleRecord.rb new file mode 100644 index 000000000..bfe491738 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/006_PokeBattle_BattleRecord.rb @@ -0,0 +1,297 @@ +#=============================================================================== +# +#=============================================================================== +module PokeBattle_RecordedBattleModule + attr_reader :randomnums + attr_reader :rounds + + module Commands + Fight = 0 + Bag = 1 + Pokemon = 2 + Run = 3 + end + + def initialize(*arg) + @randomnumbers = [] + @rounds = [] + @switches = [] + @roundindex = -1 + @properties = {} + super(*arg) + end + + def pbGetBattleType + return 0 # Battle Tower + end + + def pbGetTrainerInfo(trainer) + return nil if !trainer + if trainer.is_a?(Array) + ret = [] + for i in 0...trainer.length + ret.push([trainer[i].trainertype,trainer[i].name.clone,trainer[i].id,trainer[i].badges.clone]) + end + return ret + else + return [ + [trainer.trainertype,trainer.name.clone,trainer.id,trainer.badges.clone] + ] + end + end + + def pbStartBattle + @properties = {} + @properties["internalBattle"] = @internalBattle + @properties["player"] = pbGetTrainerInfo(@player) + @properties["opponent"] = pbGetTrainerInfo(@opponent) + @properties["party1"] = Marshal.dump(@party1) + @properties["party2"] = Marshal.dump(@party2) + @properties["party1starts"] = Marshal.dump(@party1starts) + @properties["party2starts"] = Marshal.dump(@party2starts) + @properties["endSpeeches"] = (@endSpeeches) ? @endSpeeches.clone : "" + @properties["endSpeechesWin"] = (@endSpeechesWin) ? @endSpeechesWin.clone : "" + @properties["weather"] = @field.weather + @properties["weatherDuration"] = @field.weatherDuration + @properties["canRun"] = @canRun + @properties["switchStyle"] = @switchStyle + @properties["showAnims"] = @showAnims + @properties["items"] = Marshal.dump(@items) + @properties["environment"] = @environment + @properties["rules"] = Marshal.dump(@rules) + super + end + + def pbDumpRecord + return Marshal.dump([pbGetBattleType,@properties,@rounds,@randomnumbers,@switches]) + end + + def pbSwitchInBetween(idxBattler,checkLaxOnly=false,canCancel=false) + ret = super + @switches.push(ret) + return ret + end + + def pbRegisterMove(idxBattler,idxMove,showMessages=true) + if super + @rounds[@roundindex][idxBattler] = [Commands::Fight,idxMove] + return true + end + return false + end + + def pbRegisterTarget(idxBattler,idxTarget) + super + @rounds[@roundindex][idxBattler][2] = idxTarget + end + + def pbRun(idxBattler,duringBattle=false) + ret = super + @rounds[@roundindex][idxBattler] = [Commands::Run,@decision] + return ret + end + + def pbAutoChooseMove(idxBattler,showMessages=true) + ret = super + @rounds[@roundindex][idxBattler] = [Commands::Fight,-1] + return ret + end + + def pbRegisterSwitch(idxBattler,idxParty) + if super + @rounds[@roundindex][idxBattler] = [Commands::Pokemon,idxParty] + return true + end + return false + end + + def pbRegisterItem(idxBattler,item,idxTarget=nil,idxMove=nil) + if super + @rounds[@roundindex][idxBattler] = [Commands::Bag,item,idxTarget,idxMove] + return true + end + return false + end + + def pbCommandPhase + @roundindex += 1 + @rounds[@roundindex] = [[],[],[],[]] + super + end + + def pbStorePokemon(pkmn); end + + def pbRandom(num) + ret = super(num) + @randomnumbers.push(ret) + return ret + end +end + + + +#=============================================================================== +# +#=============================================================================== +module BattlePlayerHelper + def self.pbGetOpponent(battle) + return self.pbCreateTrainerInfo(battle[1]["opponent"]) + end + + def self.pbGetBattleBGM(battle) + return self.pbGetTrainerBattleBGM(self.pbGetOpponent(battle)) + end + + def self.pbCreateTrainerInfo(trainer) + return nil if !trainer + if trainer.length>1 + ret = [] + ret[0]=PokeBattle_Trainer.new(trainer[0][1],trainer[0][0]) + ret[0].id = trainer[0][2] + ret[0].badges = trainer[0][3] + ret[1] = PokeBattle_Trainer.new(trainer[1][1],trainer[1][0]) + ret[1].id = trainer[1][2] + ret[1].badges = trainer[1][3] + return ret + else + ret = PokeBattle_Trainer.new(trainer[0][1],trainer[0][0]) + ret.id = trainer[0][2] + ret.badges = trainer[0][3] + return ret + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +module PokeBattle_BattlePlayerModule + module Commands + Fight = 0 + Bag = 1 + Pokemon = 2 + Run = 3 + end + + def initialize(scene,battle) + @battletype = battle[0] + @properties = battle[1] + @rounds = battle[2] + @randomnums = battle[3] + @switches = battle[4] + @roundindex = -1 + @randomindex = 0 + @switchindex = 0 + super(scene, + Marshal.restore(StringInput.new(@properties["party1"])), + Marshal.restore(StringInput.new(@properties["party2"])), + BattlePlayerHelper.pbCreateTrainerInfo(@properties["player"]), + BattlePlayerHelper.pbCreateTrainerInfo(@properties["opponent"]) + ) + end + + def pbStartBattle + @party1starts = @properties["party1starts"] + @party2starts = @properties["party2starts"] + @internalBattle = @properties["internalBattle"] + @endSpeeches = @properties["endSpeeches"] + @endSpeechesWin = @properties["endSpeechesWin"] + @field.weather = @properties["weather"] + @field.weatherDuration = @properties["weatherDuration"] + @canRun = @properties["canRun"] + @switchStyle = @properties["switchStyle"] + @showAnims = @properties["showAnims"] + @environment = @properties["environment"] + @items = Marshal.restore(StringInput.new(@properties["items"])) + @rules = Marshal.restore(StringInput.new(@properties["rules"])) + super + end + + def pbSwitchInBetween(idxBattler,checkLaxOnly=false,canCancel=false) + ret = @switches[@switchindex] + @switchindex += 1 + return ret + end + + def pbRandom(num) + ret = @randomnums[@randomindex] + @randomindex += 1 + return ret + end + + def pbDisplayPaused(str) + pbDisplay(str) + end + + def pbCommandPhaseCore + @roundindex += 1 + for i in 0...4 + next if @rounds[@roundindex][i].length==0 + pbClearChoice(i) + case @rounds[@roundindex][i][0] + when Commands::Fight + if @rounds[@roundindex][i][1]==-1 + pbAutoChooseMove(i,false) + else + pbRegisterMove(i,@rounds[@roundindex][i][1]) + end + if @rounds[@roundindex][i][2] + pbRegisterTarget(i,@rounds[@roundindex][i][2]) + end + when Commands::Bag + pbRegisterItem(i,@rounds[@roundindex][i][1],@rounds[@roundindex][i][2],@rounds[@roundindex][i][3]) + when Commands::Pokemon + pbRegisterSwitch(i,@rounds[@roundindex][i][1]) + when Commands::Run + @decision = @rounds[@roundindex][i][1] + end + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_RecordedBattle < PokeBattle_Battle + include PokeBattle_RecordedBattleModule + + def pbGetBattleType; return 0; end +end + + + +class PokeBattle_RecordedBattlePalace < PokeBattle_BattlePalace + include PokeBattle_RecordedBattleModule + + def pbGetBattleType; return 1; end +end + + + +class PokeBattle_RecordedBattleArena < PokeBattle_BattleArena + include PokeBattle_RecordedBattleModule + + def pbGetBattleType; return 2; end +end + + + +class PokeBattle_BattlePlayer < PokeBattle_Battle + include PokeBattle_BattlePlayerModule +end + + + +class PokeBattle_BattlePalacePlayer < PokeBattle_BattlePalace + include PokeBattle_BattlePlayerModule +end + + + +class PokeBattle_BattleArenaPlayer < PokeBattle_BattleArena + include PokeBattle_BattlePlayerModule +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/007_PokeBattle_DebugScene.rb b/Data/Scripts/011_Battle/006_Other battle types/007_PokeBattle_DebugScene.rb new file mode 100644 index 000000000..f09dfba1b --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/007_PokeBattle_DebugScene.rb @@ -0,0 +1,85 @@ +#=============================================================================== +# Used when generating new trainers for battle challenges +#=============================================================================== +class PokeBattle_DebugSceneNoLogging + def initialize + @battle = nil + @lastCmd = [0,0,0,0] + @lastMove = [0,0,0,0] + end + + # Called whenever the battle begins. + def pbStartBattle(battle) + @battle = battle + @lastCmd = [0,0,0,0] + @lastMove = [0,0,0,0] + end + + def pbBlitz(keys) + return rand(30) + end + + # Called whenever a new round begins. + def pbBeginCommandPhase; end + def pbBeginAttackPhase; end + def pbShowOpponent(idxTrainer); end + def pbDamageAnimation(battler,effectiveness=0); end + def pbCommonAnimation(animName,user=nil,target=nil,hitNum=0); end + def pbAnimation(moveID,user,targets,hitNum=0); end + def pbEndBattle(result); end + def pbWildBattleSuccess; end + def pbTrainerBattleSuccess; end + def pbBattleArenaJudgment(b1,b2,r1,r2); end + def pbBattleArenaBattlers(b1,b2); end + + def pbRefresh; end + + def pbDisplayMessage(msg,brief=false); end + def pbDisplayPausedMessage(msg); end + def pbDisplayConfirmMessage(msg); return true; end + def pbShowCommands(msg,commands,defaultValue); return 0; end + + def pbSendOutBattlers(sendOuts,startBattle=false); end + def pbRecall(idxBattler); end + def pbItemMenu(idxBattler,firstAction); return -1; end + def pbResetMoveIndex(idxBattler); end + + def pbChatter(user,target); end + def pbHPChanged(battler,oldHP,showAnim=false); end + def pbFaintBattler(battler); end + def pbEXPBar(battler,startExp,endExp,tempExp1,tempExp2); end + def pbLevelUp(pkmn,battler,oldTotalHP,oldAttack,oldDefense, + oldSpAtk,oldSpDef,oldSpeed); end + def pbForgetMove(pkmn,moveToLearn); return 0; end # Always forget first move + + def pbCommandMenu(idxBattler,firstAction) + return 1 if rand(15)==0 # Bag + return 4 if rand(10)==0 # Call + return 0 # Fight + end + + def pbFightMenu(idxBattler,megaEvoPossible=false) + battler = @battle.battlers[idxBattler] + 50.times do + break if yield rand(battler.move.length) + end + end + + def pbChooseTarget(idxBattler,targetType,visibleSprites=nil) + targets = [] + @battle.eachOtherSideBattler(idxBattler) { |b| targets.push(b.index) } + return -1 if targets.length==0 + return targets[rand(targets.length)] + end + + def pbPartyScreen(idxBattler,canCancel=false) + replacements = [] + @battle.eachInTeamFromBattlerIndex(idxBattler) do |b,idxParty| + replacements.push(idxParty) if !@battle.pbFindBattler(idxParty,idxBattler) + end + return if replacements.length==0 + 50.times do + break if yield replacements[rand(replacements.length)],self + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/008_PokeBattle_BattlePeer.rb b/Data/Scripts/011_Battle/006_Other battle types/008_PokeBattle_BattlePeer.rb new file mode 100644 index 000000000..d20c90768 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/008_PokeBattle_BattlePeer.rb @@ -0,0 +1,65 @@ +#=============================================================================== +# +#=============================================================================== +# Unused class. +class PokeBattle_NullBattlePeer + def pbOnEnteringBattle(battle,pkmn,wild=false); end + def pbOnLeavingBattle(battle,pkmn,usedInBattle,endBattle=false); end + + def pbStorePokemon(player,pkmn) + player.party[player.party.length] = pkmn if player.party.length<6 + return -1 + end + + def pbGetStorageCreatorName; return nil; end + def pbCurrentBox; return -1; end + def pbBoxName(box); return ""; end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_RealBattlePeer + def pbStorePokemon(player,pkmn) + if player.party.length<6 + player.party[player.party.length] = pkmn + return -1 + end + pkmn.heal + oldCurBox = pbCurrentBox + storedBox = $PokemonStorage.pbStoreCaught(pkmn) + if storedBox<0 + # NOTE: Poké Balls can't be used if storage is full, so you shouldn't ever + # see this message. + pbDisplayPaused(_INTL("Can't catch any more...")) + return oldCurBox + end + return storedBox + end + + def pbGetStorageCreatorName + return pbGetStorageCreator if $PokemonGlobal.seenStorageCreator + return nil + end + + def pbCurrentBox + return $PokemonStorage.currentBox + end + + def pbBoxName(box) + return (box<0) ? "" : $PokemonStorage[box].name + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_BattlePeer + def self.create + return PokeBattle_RealBattlePeer.new + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/009_PokeBattle_Clauses.rb b/Data/Scripts/011_Battle/006_Other battle types/009_PokeBattle_Clauses.rb new file mode 100644 index 000000000..def92991d --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/009_PokeBattle_Clauses.rb @@ -0,0 +1,244 @@ +#=============================================================================== +# This script modifies the battle system to implement battle rules +#=============================================================================== +class PokeBattle_Battle + unless @__clauses__aliased + alias __clauses__pbDecisionOnDraw pbDecisionOnDraw + alias __clauses__pbEndOfRoundPhase pbEndOfRoundPhase + @__clauses__aliased = true + end + + def pbDecisionOnDraw + if @rules["selfkoclause"] + if self.lastMoveUser<0 + # in extreme cases there may be no last move user + return 5 # game is a draw + elsif opposes?(self.lastMoveUser) + return 2 # loss + else + return 1 # win + end + end + return __clauses__pbDecisionOnDraw + end + + def pbJudgeCheckpoint(user,move=nil) + if pbAllFainted?(0) && pbAllFainted?(1) + if @rules["drawclause"] # NOTE: Also includes Life Orb (not implemented) + if !(move && move.function=="0DD") # Not a draw if fainting occurred due to Liquid Ooze + @decision = (user.opposes?) ? 1 : 2 # win / loss + end + elsif @rules["modifiedselfdestructclause"] + if move && move.function=="0E0" # Self-Destruct + @decision = (user.opposes?) ? 1 : 2 # win / loss + end + end + end + end + + def pbEndOfRoundPhase + __clauses__pbEndOfRoundPhase + if @rules["suddendeath"] && @decision==0 + p1able = pbAbleCount(0) + p2able = pbAbleCount(1) + if p1able>p2able; @decision = 1 # loss + elsif p1able0 + end +end + + + +class PokeBattle_Move_022 # Double Team + alias __clauses__pbMoveFailed? pbMoveFailed? + + def pbMoveFailed?(user,targets) + if !damagingMove? && @battle.rules["evasionclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbMoveFailed?(user,targets) + end +end + + + +class PokeBattle_Move_034 # Minimize + alias __clauses__pbMoveFailed? pbMoveFailed? + + def pbMoveFailed?(user,targets) + if !damagingMove? && @battle.rules["evasionclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbMoveFailed?(user,targets) + end +end + + + +class PokeBattle_Move_067 # Skill Swap + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["skillswapclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_06A # Sonic Boom + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["sonicboomclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_06B # Dragon Rage + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["sonicboomclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_070 # OHKO moves + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["ohkoclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_0E0 # Self-Destruct + unless @__clauses__aliased + alias __clauses__pbMoveFailed? pbMoveFailed? + @__clauses__aliased = true + end + + def pbMoveFailed?(user,targets) + if @battle.rules["selfkoclause"] + # Check whether no unfainted Pokemon remain in either party + count = @battle.pbAbleNonActiveCount(user.idxOwnSide) + count += @battle.pbAbleNonActiveCount(user.idxOpposingSide) + if count==0 + @battle.pbDisplay("But it failed!") + return false + end + end + if @battle.rules["selfdestructclause"] + # Check whether no unfainted Pokemon remain in either party + count = @battle.pbAbleNonActiveCount(user.idxOwnSide) + count += @battle.pbAbleNonActiveCount(user.idxOpposingSide) + if count==0 + @battle.pbDisplay(_INTL("{1}'s team was disqualified!",user.pbThis)) + @battle.decision = (user.opposes?) ? 1 : 2 + return false + end + end + return __clauses__pbMoveFailed?(user,targets) + end +end + + + +class PokeBattle_Move_0E5 # Perish Song + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["perishsongclause"] && + @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_0E7 # Destiny Bond + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["perishsongclause"] && + @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/007_BattleHandlers_Abilities.rb b/Data/Scripts/011_Battle/007_BattleHandlers_Abilities.rb new file mode 100644 index 000000000..687bdd2d8 --- /dev/null +++ b/Data/Scripts/011_Battle/007_BattleHandlers_Abilities.rb @@ -0,0 +1,2507 @@ +#=============================================================================== +# SpeedCalcAbility handlers +#=============================================================================== + +BattleHandlers::SpeedCalcAbility.add(:CHLOROPHYLL, + proc { |ability,battler,mult| + w = battler.battle.pbWeather + next mult*2 if w==PBWeather::Sun || w==PBWeather::HarshSun + } +) + +BattleHandlers::SpeedCalcAbility.add(:QUICKFEET, + proc { |ability,battler,mult| + next (mult*1.5).round if battler.pbHasAnyStatus? + } +) + +BattleHandlers::SpeedCalcAbility.add(:SANDRUSH, + proc { |ability,battler,mult| + w = battler.battle.pbWeather + next mult*2 if w==PBWeather::Sandstorm + } +) + +BattleHandlers::SpeedCalcAbility.add(:SLOWSTART, + proc { |ability,battler,mult| + next mult/2 if battler.turnCount<=5 + } +) + +BattleHandlers::SpeedCalcAbility.add(:SLUSHRUSH, + proc { |ability,battler,mult| + w = battler.battle.pbWeather + next mult*2 if w==PBWeather::Hail + } +) + +BattleHandlers::SpeedCalcAbility.add(:SURGESURFER, + proc { |ability,battler,mult| + next mult*2 if battler.battle.field.terrain==PBBattleTerrains::Electric + } +) + +BattleHandlers::SpeedCalcAbility.add(:SWIFTSWIM, + proc { |ability,battler,mult| + w = battler.battle.pbWeather + next mult*2 if w==PBWeather::Rain || w==PBWeather::HeavyRain + } +) + +BattleHandlers::SpeedCalcAbility.add(:UNBURDEN, + proc { |ability,battler,mult| + next mult*2 if battler.effects[PBEffects::Unburden] && battler.item==0 + } +) + +#=============================================================================== +# WeightCalcAbility handlers +#=============================================================================== + +BattleHandlers::WeightCalcAbility.add(:HEAVYMETAL, + proc { |ability,battler,w| + next w*2 + } +) + +BattleHandlers::WeightCalcAbility.add(:LIGHTMETAL, + proc { |ability,battler,w| + next [w/2,1].max + } +) + +#=============================================================================== +# AbilityOnHPDroppedBelowHalf handlers +#=============================================================================== + +BattleHandlers::AbilityOnHPDroppedBelowHalf.add(:EMERGENCYEXIT, + proc { |ability,battler,battle| + next false if battler.effects[PBEffects::SkyDrop]>=0 || battler.inTwoTurnAttack?("0CE") # Sky Drop + # In wild battles + if battle.wildBattle? + next false if battler.opposes? && battle.pbSideBattlerCount(battler.index)>1 + next false if !battle.pbCanRun?(battler.index) + battle.pbShowAbilitySplash(battler,true) + battle.pbHideAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} fled from battle!",battler.pbThis)) { pbSEPlay("Battle flee") } + battle.decision = 3 # Escaped + next true + end + # In trainer battles + next false if battle.pbAllFainted?(battler.idxOpposingSide) + next false if !battle.pbCanSwitch?(battler.index) # Battler can't switch out + next false if !battle.pbCanChooseNonActive?(battler.index) # No Pokémon can switch in + battle.pbShowAbilitySplash(battler,true) + battle.pbHideAbilitySplash(battler) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} activated!",battler.pbThis,battler.abilityName)) + end + battle.pbDisplay(_INTL("{1} went back to {2}!", + battler.pbThis,battle.pbGetOwnerName(battler.index))) + if battle.endOfRound # Just switch out + battle.scene.pbRecall(battler.index) if !battler.fainted? + battler.pbAbilitiesOnSwitchOut # Inc. primordial weather check + next true + end + newPkmn = battle.pbGetReplacementPokemonIndex(battler.index) # Owner chooses + next false if newPkmn<0 # Shouldn't ever do this + battle.pbRecallAndReplace(battler.index,newPkmn) + battle.pbClearChoice(battler.index) # Replacement Pokémon does nothing this round + next true + } +) + +BattleHandlers::AbilityOnHPDroppedBelowHalf.copy(:EMERGENCYEXIT,:WIMPOUT) + +#=============================================================================== +# StatusCheckAbilityNonIgnorable handlers +#=============================================================================== + +BattleHandlers::StatusCheckAbilityNonIgnorable.add(:COMATOSE, + proc { |ability,battler,status| + next false if !isConst?(battler.species,PBSpecies,:KOMALA) + next true if status.nil? || status==PBStatuses::SLEEP + } +) + +#=============================================================================== +# StatusImmunityAbility handlers +#=============================================================================== + +BattleHandlers::StatusImmunityAbility.add(:FLOWERVEIL, + proc { |ability,battler,status| + next true if battler.pbHasType?(:GRASS) + } +) + +BattleHandlers::StatusImmunityAbility.add(:IMMUNITY, + proc { |ability,battler,status| + next true if status==PBStatuses::POISON + } +) + +BattleHandlers::StatusImmunityAbility.add(:INSOMNIA, + proc { |ability,battler,status| + next true if status==PBStatuses::SLEEP + } +) + +BattleHandlers::StatusImmunityAbility.copy(:INSOMNIA,:SWEETVEIL,:VITALSPIRIT) + +BattleHandlers::StatusImmunityAbility.add(:LEAFGUARD, + proc { |ability,battler,status| + w = battler.battle.pbWeather + next true if w==PBWeather::Sun || w==PBWeather::HarshSun + } +) + +BattleHandlers::StatusImmunityAbility.add(:LIMBER, + proc { |ability,battler,status| + next true if status==PBStatuses::PARALYSIS + } +) + +BattleHandlers::StatusImmunityAbility.add(:MAGMAARMOR, + proc { |ability,battler,status| + next true if status==PBStatuses::FROZEN + } +) + +BattleHandlers::StatusImmunityAbility.add(:WATERVEIL, + proc { |ability,battler,status| + next true if status==PBStatuses::BURN + } +) + +BattleHandlers::StatusImmunityAbility.copy(:WATERVEIL,:WATERBUBBLE) + +#=============================================================================== +# StatusImmunityAbilityNonIgnorable handlers +#=============================================================================== + +BattleHandlers::StatusImmunityAbilityNonIgnorable.add(:COMATOSE, + proc { |ability,battler,status| + next true if isConst?(battler.species,PBSpecies,:KOMALA) + } +) + +BattleHandlers::StatusImmunityAbilityNonIgnorable.add(:SHIELDSDOWN, + proc { |ability,battler,status| + next true if isConst?(battler.species,PBSpecies,:MINIOR) && battler.form<7 + } +) + +#=============================================================================== +# StatusImmunityAllyAbility handlers +#=============================================================================== + +BattleHandlers::StatusImmunityAllyAbility.add(:FLOWERVEIL, + proc { |ability,battler,status| + next true if battler.pbHasType?(:GRASS) + } +) + +BattleHandlers::StatusImmunityAbility.add(:SWEETVEIL, + proc { |ability,battler,status| + next true if status==PBStatuses::SLEEP + } +) + +#=============================================================================== +# AbilityOnStatusInflicted handlers +#=============================================================================== + +BattleHandlers::AbilityOnStatusInflicted.add(:SYNCHRONIZE, + proc { |ability,battler,user,status| + next if !user || user.index==battler.index + case status + when PBStatuses::POISON + if user.pbCanPoisonSynchronize?(battler) + battler.battle.pbShowAbilitySplash(battler) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} poisoned {3}!",battler.pbThis,battler.abilityName,user.pbThis(true)) + end + user.pbPoison(nil,msg,(battler.statusCount>0)) + battler.battle.pbHideAbilitySplash(battler) + end + when PBStatuses::BURN + if user.pbCanBurnSynchronize?(battler) + battler.battle.pbShowAbilitySplash(battler) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} burned {3}!",battler.pbThis,battler.abilityName,user.pbThis(true)) + end + user.pbBurn(nil,msg) + battler.battle.pbHideAbilitySplash(battler) + end + when PBStatuses::PARALYSIS + if user.pbCanParalyzeSynchronize?(battler) + battler.battle.pbShowAbilitySplash(battler) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} paralyzed {3}! It may be unable to move!", + battler.pbThis,battler.abilityName,user.pbThis(true)) + end + user.pbParalyze(nil,msg) + battler.battle.pbHideAbilitySplash(battler) + end + end + } +) + +#=============================================================================== +# StatusCureAbility handlers +#=============================================================================== + +BattleHandlers::StatusCureAbility.add(:IMMUNITY, + proc { |ability,battler| + next if battler.status!=PBStatuses::POISON + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} cured its poisoning!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:INSOMNIA, + proc { |ability,battler| + next if battler.status!=PBStatuses::SLEEP + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} woke it up!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.copy(:INSOMNIA,:VITALSPIRIT) + +BattleHandlers::StatusCureAbility.add(:LIMBER, + proc { |ability,battler| + next if battler.status!=PBStatuses::PARALYSIS + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} cured its paralysis!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:MAGMAARMOR, + proc { |ability,battler| + next if battler.status!=PBStatuses::FROZEN + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} defrosted it!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:OBLIVIOUS, + proc { |ability,battler| + next if battler.effects[PBEffects::Attract]<0 && + (battler.effects[PBEffects::Taunt]==0 || !NEWEST_BATTLE_MECHANICS) + battler.battle.pbShowAbilitySplash(battler) + if battler.effects[PBEffects::Attract]>=0 + battler.pbCureAttract + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1} got over its infatuation.",battler.pbThis)) + else + battler.battle.pbDisplay(_INTL("{1}'s {2} cured its infatuation status!", + battler.pbThis,battler.abilityName)) + end + end + if battler.effects[PBEffects::Taunt]>0 && NEWEST_BATTLE_MECHANICS + battler.effects[PBEffects::Taunt] = 0 + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s Taunt wore off!",battler.pbThis)) + else + battler.battle.pbDisplay(_INTL("{1}'s {2} made its taunt wear off!", + battler.pbThis,battler.abilityName)) + end + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:OWNTEMPO, + proc { |ability,battler| + next if battler.effects[PBEffects::Confusion]==0 + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureConfusion + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1} snapped out of its confusion.",battler.pbThis)) + else + battler.battle.pbDisplay(_INTL("{1}'s {2} snapped it out of its confusion!", + battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:WATERVEIL, + proc { |ability,battler| + next if battler.status!=PBStatuses::BURN + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} healed its burn!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.copy(:WATERVEIL,:WATERBUBBLE) + +#=============================================================================== +# StatLossImmunityAbility handlers +#=============================================================================== + +BattleHandlers::StatLossImmunityAbility.add(:BIGPECKS, + proc { |ability,battler,stat,battle,showMessages| + next false if stat!=PBStats::DEFENSE + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} cannot be lowered!",battler.pbThis,PBStats.getName(stat))) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents {3} loss!",battler.pbThis, + battler.abilityName,PBStats.getName(stat))) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +BattleHandlers::StatLossImmunityAbility.add(:CLEARBODY, + proc { |ability,battler,stat,battle,showMessages| + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s stats cannot be lowered!",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents stat loss!",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +BattleHandlers::StatLossImmunityAbility.copy(:CLEARBODY,:WHITESMOKE) + +BattleHandlers::StatLossImmunityAbility.add(:FLOWERVEIL, + proc { |ability,battler,stat,battle,showMessages| + next false if !battler.pbHasType?(:GRASS) + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s stats cannot be lowered!",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents stat loss!",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +BattleHandlers::StatLossImmunityAbility.add(:HYPERCUTTER, + proc { |ability,battler,stat,battle,showMessages| + next false if stat!=PBStats::ATTACK + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} cannot be lowered!",battler.pbThis,PBStats.getName(stat))) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents {3} loss!",battler.pbThis, + battler.abilityName,PBStats.getName(stat))) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +BattleHandlers::StatLossImmunityAbility.add(:KEENEYE, + proc { |ability,battler,stat,battle,showMessages| + next false if stat!=PBStats::ACCURACY + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} cannot be lowered!",battler.pbThis,PBStats.getName(stat))) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents {3} loss!",battler.pbThis, + battler.abilityName,PBStats.getName(stat))) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +#=============================================================================== +# StatLossImmunityAbilityNonIgnorable handlers +#=============================================================================== + +BattleHandlers::StatLossImmunityAbilityNonIgnorable.add(:FULLMETALBODY, + proc { |ability,battler,stat,battle,showMessages| + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s stats cannot be lowered!",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents stat loss!",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +#=============================================================================== +# StatLossImmunityAllyAbility handlers +#=============================================================================== + +BattleHandlers::StatLossImmunityAllyAbility.add(:FLOWERVEIL, + proc { |ability,bearer,battler,stat,battle,showMessages| + next false if !battler.pbHasType?(:GRASS) + if showMessages + battle.pbShowAbilitySplash(bearer) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s stats cannot be lowered!",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents {3}'s stat loss!", + bearer.pbThis,bearer.abilityName,battler.pbThis(true))) + end + battle.pbHideAbilitySplash(bearer) + end + next true + } +) + +#=============================================================================== +# AbilityOnStatGain handlers +#=============================================================================== + +# There aren't any! + +#=============================================================================== +# AbilityOnStatLoss handlers +#=============================================================================== + +BattleHandlers::AbilityOnStatLoss.add(:COMPETITIVE, + proc { |ability,battler,stat,user| + next if user && !user.opposes?(battler) + battler.pbRaiseStatStageByAbility(PBStats::SPATK,2,battler) + } +) + +BattleHandlers::AbilityOnStatLoss.add(:DEFIANT, + proc { |ability,battler,stat,user| + next if user && !user.opposes?(battler) + battler.pbRaiseStatStageByAbility(PBStats::ATTACK,2,battler) + } +) + +#=============================================================================== +# PriorityChangeAbility handlers +#=============================================================================== + +BattleHandlers::PriorityChangeAbility.add(:GALEWINGS, + proc { |ability,battler,move,pri| + next pri+1 if battler.hp==battler.totalhp && isConst?(move.type,PBTypes,:FLYING) + } +) + +BattleHandlers::PriorityChangeAbility.add(:PRANKSTER, + proc { |ability,battler,move,pri| + if move.statusMove? + battler.effects[PBEffects::Prankster] = true + next pri+1 + end + } +) + +BattleHandlers::PriorityChangeAbility.add(:TRIAGE, + proc { |ability,battler,move,pri| + next pri+3 if move.healingMove? + } +) + +#=============================================================================== +# PriorityBracketChangeAbility handlers +#=============================================================================== + +BattleHandlers::PriorityBracketChangeAbility.add(:STALL, + proc { |ability,battler,subPri,battle| + next -1 if subPri==0 + } +) + +#=============================================================================== +# PriorityBracketUseAbility handlers +#=============================================================================== + +# There aren't any! + +#=============================================================================== +# AbilityOnFlinch handlers +#=============================================================================== + +BattleHandlers::AbilityOnFlinch.add(:STEADFAST, + proc { |ability,battler,battle| + battler.pbRaiseStatStageByAbility(PBStats::SPEED,1,battler) + } +) + +#=============================================================================== +# MoveBlockingAbility handlers +#=============================================================================== + +BattleHandlers::MoveBlockingAbility.add(:DAZZLING, + proc { |ability,bearer,user,targets,move,battle| + next false if battle.choices[user.index][4]<=0 + next false if !bearer.opposes?(user) + ret = false + targets.each do |b| + next if !b.opposes?(user) + ret = true + end + next ret + } +) + +BattleHandlers::MoveBlockingAbility.copy(:DAZZLING,:QUEENLYMAJESTY) + +#=============================================================================== +# MoveImmunityTargetAbility handlers +#=============================================================================== + +BattleHandlers::MoveImmunityTargetAbility.add(:BULLETPROOF, + proc { |ability,user,target,move,type,battle| + next false if !move.bombMove? + battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!", + target.pbThis,target.abilityName,move.name)) + end + battle.pbHideAbilitySplash(target) + next true + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:FLASHFIRE, + proc { |ability,user,target,move,type,battle| + next false if user.index==target.index + next false if !isConst?(type,PBTypes,:FIRE) + battle.pbShowAbilitySplash(target) + if !target.effects[PBEffects::FlashFire] + target.effects[PBEffects::FlashFire] = true + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("The power of {1}'s Fire-type moves rose!",target.pbThis(true))) + else + battle.pbDisplay(_INTL("The power of {1}'s Fire-type moves rose because of its {2}!", + target.pbThis(true),target.abilityName)) + end + else + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!", + target.pbThis,target.abilityName,move.name)) + end + end + battle.pbHideAbilitySplash(target) + next true + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:LIGHTNINGROD, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityStatAbility(user,target,move,type,:ELECTRIC,PBStats::SPATK,1,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:MOTORDRIVE, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityStatAbility(user,target,move,type,:ELECTRIC,PBStats::SPEED,1,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:SAPSIPPER, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityStatAbility(user,target,move,type,:GRASS,PBStats::ATTACK,1,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:SOUNDPROOF, + proc { |ability,user,target,move,type,battle| + next false if !move.soundMove? + battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} blocks {3}!",target.pbThis,target.abilityName,move.name)) + end + battle.pbHideAbilitySplash(target) + next true + + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:STORMDRAIN, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityStatAbility(user,target,move,type,:WATER,PBStats::SPATK,1,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:TELEPATHY, + proc { |ability,user,target,move,type,battle| + next false if move.statusMove? + next false if user.index==target.index || target.opposes?(user) + battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} avoids attacks by its ally Pokémon!",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1} avoids attacks by its ally Pokémon with {2}!", + target.pbThis,target.abilityName)) + end + battle.pbHideAbilitySplash(target) + next true + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:VOLTABSORB, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityHealAbility(user,target,move,type,:ELECTRIC,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:WATERABSORB, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityHealAbility(user,target,move,type,:WATER,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.copy(:WATERABSORB,:DRYSKIN) + +BattleHandlers::MoveImmunityTargetAbility.add(:WONDERGUARD, + proc { |ability,user,target,move,type,battle| + next false if move.statusMove? + next false if type<0 || PBTypes.superEffective?(target.damageState.typeMod) + battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1} avoided damage with {2}!",target.pbThis,target.abilityName)) + end + battle.pbHideAbilitySplash(target) + next true + } +) + +#=============================================================================== +# MoveBaseTypeModifierAbility handlers +#=============================================================================== + +BattleHandlers::MoveBaseTypeModifierAbility.add(:AERILATE, + proc { |ability,user,move,type| + next if !isConst?(type,PBTypes,:NORMAL) || !hasConst?(PBTypes,:FLYING) + move.powerBoost = true + next getConst(PBTypes,:FLYING) + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:GALVANIZE, + proc { |ability,user,move,type| + next if !isConst?(type,PBTypes,:NORMAL) || !hasConst?(PBTypes,:ELECTRIC) + move.powerBoost = true + next getConst(PBTypes,:ELECTRIC) + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:LIQUIDVOICE, + proc { |ability,user,move,type| + next getConst(PBTypes,:WATER) if hasConst?(PBTypes,:WATER) && move.soundMove? + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:NORMALIZE, + proc { |ability,user,move,type| + next if !hasConst?(PBTypes,:NORMAL) + move.powerBoost = true if NEWEST_BATTLE_MECHANICS + next getConst(PBTypes,:NORMAL) + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:PIXILATE, + proc { |ability,user,move,type| + next if !isConst?(type,PBTypes,:NORMAL) || !hasConst?(PBTypes,:FAIRY) + move.powerBoost = true + next getConst(PBTypes,:FAIRY) + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:REFRIGERATE, + proc { |ability,user,move,type| + next if !isConst?(type,PBTypes,:NORMAL) || !hasConst?(PBTypes,:ICE) + move.powerBoost = true + next getConst(PBTypes,:ICE) + } +) + +#=============================================================================== +# AccuracyCalcUserAbility handlers +#=============================================================================== + +BattleHandlers::AccuracyCalcUserAbility.add(:COMPOUNDEYES, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] = (mods[ACC_MULT]*1.3).round + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:HUSTLE, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] = (mods[ACC_MULT]*0.8).round if move.physicalMove? + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:KEENEYE, + proc { |ability,mods,user,target,move,type| + mods[EVA_STAGE] = 0 if mods[EVA_STAGE]>0 && NEWEST_BATTLE_MECHANICS + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:NOGUARD, + proc { |ability,mods,user,target,move,type| + mods[BASE_ACC] = 0 + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:UNAWARE, + proc { |ability,mods,user,target,move,type| + mods[EVA_STAGE] = 0 if move.damagingMove? + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:VICTORYSTAR, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] = (mods[ACC_MULT]*1.1).round + } +) + +#=============================================================================== +# AccuracyCalcUserAllyAbility handlers +#=============================================================================== + +BattleHandlers::AccuracyCalcUserAllyAbility.add(:VICTORYSTAR, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] = (mods[ACC_MULT]*1.1).round + } +) + +#=============================================================================== +# AccuracyCalcTargetAbility handlers +#=============================================================================== + +BattleHandlers::AccuracyCalcTargetAbility.add(:LIGHTNINGROD, + proc { |ability,mods,user,target,move,type| + mods[BASE_ACC] = 0 if isConst?(type,PBTypes,:ELECTRIC) + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:NOGUARD, + proc { |ability,mods,user,target,move,type| + mods[BASE_ACC] = 0 + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:SANDVEIL, + proc { |ability,mods,user,target,move,type| + if target.battle.pbWeather==PBWeather::Sandstorm + mods[EVA_MULT] = (mods[EVA_MULT]*1.25).round + end + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:SNOWCLOAK, + proc { |ability,mods,user,target,move,type| + if target.battle.pbWeather==PBWeather::Hail + mods[EVA_MULT] = (mods[EVA_MULT]*1.25).round + end + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:STORMDRAIN, + proc { |ability,mods,user,target,move,type| + mods[BASE_ACC] = 0 if isConst?(type,PBTypes,:WATER) + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:TANGLEDFEET, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] /= 2 if target.effects[PBEffects::Confusion]>0 + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:UNAWARE, + proc { |ability,mods,user,target,move,type| + mods[ACC_STAGE] = 0 if move.damagingMove? + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:WONDERSKIN, + proc { |ability,mods,user,target,move,type| + if move.statusMove? && user.opposes?(target) + mods[BASE_ACC] = 0 if mods[BASE_ACC]>50 + end + } +) + +#=============================================================================== +# DamageCalcUserAbility handlers +#=============================================================================== + +BattleHandlers::DamageCalcUserAbility.add(:AERILATE, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.2).round if move.powerBoost + } +) + +BattleHandlers::DamageCalcUserAbility.copy(:AERILATE,:PIXILATE,:REFRIGERATE,:GALVANIZE) + +BattleHandlers::DamageCalcUserAbility.add(:ANALYTIC, + proc { |ability,user,target,move,mults,baseDmg,type| + if (target.battle.choices[target.index][0]!=:UseMove && + target.battle.choices[target.index][0]!=:Shift) || + target.movedThisRound? + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.3).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:BLAZE, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.hp<=user.totalhp/3 && isConst?(type,PBTypes,:FIRE) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:DEFEATIST, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] = (mults[ATK_MULT]*0.5).round if user.hp<=user.totalhp/2 + } +) + +BattleHandlers::DamageCalcUserAbility.add(:FLAREBOOST, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.burned? && move.specialMove? + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:FLASHFIRE, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.effects[PBEffects::FlashFire] && isConst?(type,PBTypes,:FIRE) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:FLOWERGIFT, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.physicalMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:GUTS, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.pbHasAnyStatus? && move.physicalMove? + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:HUGEPOWER, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] *= 2 if move.physicalMove? + } +) + +BattleHandlers::DamageCalcUserAbility.copy(:HUGEPOWER,:PUREPOWER) + +BattleHandlers::DamageCalcUserAbility.add(:HUSTLE, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round if move.physicalMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:IRONFIST, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.2).round if move.punchingMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:MEGALAUNCHER, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round if move.pulseMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:MINUS, + proc { |ability,user,target,move,mults,baseDmg,type| + next if !move.specialMove? + user.eachAlly do |b| + next if !b.hasActiveAbility?([:MINUS,:PLUS]) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + break + end + } +) + +BattleHandlers::DamageCalcUserAbility.copy(:MINUS,:PLUS) + +BattleHandlers::DamageCalcUserAbility.add(:NEUROFORCE, + proc { |ability,user,target,move,mults,baseDmg,type| + if PBTypes.superEffective?(target.damageState.typeMod) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*1.25).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:OVERGROW, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.hp<=user.totalhp/3 && isConst?(type,PBTypes,:GRASS) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:RECKLESS, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.2).round if move.recoilMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:RIVALRY, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.gender!=2 && target.gender!=2 + if user.gender==target.gender + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.25).round + else + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*0.75).round + end + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SANDFORCE, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.battle.pbWeather==PBWeather::Sandstorm && + (isConst?(type,PBTypes,:ROCK) || + isConst?(type,PBTypes,:GROUND) || + isConst?(type,PBTypes,:STEEL)) + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.3).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SHEERFORCE, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.3).round if move.addlEffect>0 + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SLOWSTART, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.turnCount<=5 && move.physicalMove? + mults[ATK_MULT] = (mults[ATK_MULT]*0.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SOLARPOWER, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.specialMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SNIPER, + proc { |ability,user,target,move,mults,baseDmg,type| + if target.damageState.critical + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:STAKEOUT, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] *= 2 if target.battle.choices[target.index][0]==:SwitchOut + } +) + +BattleHandlers::DamageCalcUserAbility.add(:STEELWORKER, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round if isConst?(type,PBTypes,:STEEL) + } +) + +BattleHandlers::DamageCalcUserAbility.add(:STRONGJAW, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round if move.bitingMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SWARM, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.hp<=user.totalhp/3 && isConst?(type,PBTypes,:BUG) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TECHNICIAN, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.index!=target.index && move.id>0 && baseDmg*mults[BASE_DMG_MULT]/0x1000<=60 + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TINTEDLENS, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[FINAL_DMG_MULT] *= 2 if PBTypes.resistant?(target.damageState.typeMod) + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TORRENT, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.hp<=user.totalhp/3 && isConst?(type,PBTypes,:WATER) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TOUGHCLAWS, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*4/3.0).round if move.contactMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TOXICBOOST, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.poisoned? && move.physicalMove? + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:WATERBUBBLE, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] *= 2 if isConst?(type,PBTypes,:WATER) + } +) + +#=============================================================================== +# DamageCalcUserAllyAbility handlers +#=============================================================================== + +BattleHandlers::DamageCalcUserAllyAbility.add(:BATTERY, + proc { |ability,user,target,move,mults,baseDmg,type| + next if !move.specialMove? + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*1.3).round + } +) + +BattleHandlers::DamageCalcUserAllyAbility.add(:FLOWERGIFT, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.physicalMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +#=============================================================================== +# DamageCalcTargetAbility handlers +#=============================================================================== + +BattleHandlers::DamageCalcTargetAbility.add(:DRYSKIN, + proc { |ability,user,target,move,mults,baseDmg,type| + if isConst?(type,PBTypes,:FIRE) + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.25).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:FILTER, + proc { |ability,user,target,move,mults,baseDmg,type| + if PBTypes.superEffective?(target.damageState.typeMod) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.75).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.copy(:FILTER,:SOLIDROCK) + +BattleHandlers::DamageCalcTargetAbility.add(:FLOWERGIFT, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.specialMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[DEF_MULT] = (mults[DEF_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:FLUFFY, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[FINAL_DMG_MULT] *= 2 if isConst?(move.calcType,PBTypes,:FIRE) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.5).round if move.contactMove? + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:FURCOAT, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[DEF_MULT] *= 2 if move.physicalMove? || move.function=="122" # Psyshock + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:GRASSPELT, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.battle.field.terrain==PBBattleTerrains::Grassy + mults[DEF_MULT] = (mults[DEF_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:HEATPROOF, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*0.5).round if isConst?(type,PBTypes,:FIRE) + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:MARVELSCALE, + proc { |ability,user,target,move,mults,baseDmg,type| + if target.pbHasAnyStatus? && move.physicalMove? + mults[DEF_MULT] = (mults[DEF_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:MULTISCALE, + proc { |ability,user,target,move,mults,baseDmg,type| + if target.hp==target.totalhp + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:THICKFAT, + proc { |ability,user,target,move,mults,baseDmg,type| + if isConst?(type,PBTypes,:FIRE) || isConst?(type,PBTypes,:ICE) + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*0.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:WATERBUBBLE, + proc { |ability,user,target,move,mults,baseDmg,type| + if isConst?(type,PBTypes,:FIRE) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.5).round + end + } +) + +#=============================================================================== +# DamageCalcTargetAbilityNonIgnorable handlers +#=============================================================================== + +BattleHandlers::DamageCalcTargetAbilityNonIgnorable.add(:PRISMARMOR, + proc { |ability,user,target,move,mults,baseDmg,type| + if PBTypes.superEffective?(target.damageState.typeMod) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.75).round + end + } +) + +BattleHandlers::DamageCalcTargetAbilityNonIgnorable.add(:SHADOWSHIELD, + proc { |ability,user,target,move,mults,baseDmg,type| + if target.hp==target.totalhp + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.5).round + end + } +) + +#=============================================================================== +# DamageCalcTargetAllyAbility handlers +#=============================================================================== + +BattleHandlers::DamageCalcTargetAllyAbility.add(:FLOWERGIFT, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.specialMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[DEF_MULT] = (mults[DEF_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAllyAbility.add(:FRIENDGUARD, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.75).round + } +) + +#=============================================================================== +# CriticalCalcUserAbility handlers +#=============================================================================== + +BattleHandlers::CriticalCalcUserAbility.add(:MERCILESS, + proc { |ability,user,target,c| + next 99 if target.poisoned? + } +) + +BattleHandlers::CriticalCalcUserAbility.add(:SUPERLUCK, + proc { |ability,user,target,c| + next c+1 + } +) + +#=============================================================================== +# CriticalCalcTargetAbility handlers +#=============================================================================== + +BattleHandlers::CriticalCalcTargetAbility.add(:BATTLEARMOR, + proc { |ability,user,target,c| + next -1 + } +) + +BattleHandlers::CriticalCalcTargetAbility.copy(:BATTLEARMOR,:SHELLARMOR) + +#=============================================================================== +# TargetAbilityOnHit handlers +#=============================================================================== + +BattleHandlers::TargetAbilityOnHit.add(:AFTERMATH, + proc { |ability,user,target,move,battle| + next if !target.fainted? + next if !move.pbContactMove?(user) + battle.pbShowAbilitySplash(target) + if !battle.moldBreaker + dampBattler = battle.pbCheckGlobalAbility(:DAMP) + if dampBattler + battle.pbShowAbilitySplash(dampBattler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} cannot use {2}!",target.pbThis,target.abilityName)) + else + battle.pbDisplay(_INTL("{1} cannot use {2} because of {3}'s {4}!", + target.pbThis,target.abilityName,dampBattler.pbThis(true),dampBattler.abilityName)) + end + battle.pbHideAbilitySplash(dampBattler) + battle.pbHideAbilitySplash(target) + next + end + end + if user.takesIndirectDamage?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + battle.scene.pbDamageAnimation(user) + user.pbReduceHP(user.totalhp/4,false) + battle.pbDisplay(_INTL("{1} was caught in the aftermath!",user.pbThis)) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:ANGERPOINT, + proc { |ability,user,target,move,battle| + next if !target.damageState.critical + next if !target.pbCanRaiseStatStage?(PBStats::ATTACK,target) + battle.pbShowAbilitySplash(target) + target.stages[PBStats::ATTACK] = 6 + battle.pbCommonAnimation("StatUp",target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} maxed its {2}!",target.pbThis,PBStats.getName(PBStats::ATTACK))) + else + battle.pbDisplay(_INTL("{1}'s {2} maxed its {3}!", + target.pbThis,target.abilityName,PBStats.getName(PBStats::ATTACK))) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:CURSEDBODY, + proc { |ability,user,target,move,battle| + next if user.fainted? + next if user.effects[PBEffects::Disable]>0 + regularMove = nil + user.eachMove do |m| + next if m.id!=user.lastRegularMoveUsed + regularMove = m + break + end + next if !regularMove || (regularMove.pp==0 && regularMove.totalpp>0) + next if battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if !move.pbMoveFailedAromaVeil?(target,user,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + user.effects[PBEffects::Disable] = 3 + user.effects[PBEffects::DisableMove] = regularMove.id + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} was disabled!",user.pbThis,regularMove.name)) + else + battle.pbDisplay(_INTL("{1}'s {2} was disabled by {3}'s {4}!", + user.pbThis,regularMove.name,target.pbThis(true),target.abilityName)) + end + battle.pbHideAbilitySplash(target) + user.pbItemStatusCureCheck + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:CUTECHARM, + proc { |ability,user,target,move,battle| + next if target.fainted? + next if !move.pbContactMove?(user) + next if battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if user.pbCanAttract?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} made {3} fall in love!",target.pbThis, + target.abilityName,user.pbThis(true)) + end + user.pbAttract(target,msg) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:EFFECTSPORE, + proc { |ability,user,target,move,battle| + # NOTE: This ability has a 30% chance of triggering, not a 30% chance of + # inflicting a status condition. It can try (and fail) to inflict a + # status condition that the user is immune to. + next if !move.pbContactMove?(user) + next if battle.pbRandom(100)>=30 + r = battle.pbRandom(3) + next if r==0 && user.asleep? + next if r==1 && user.poisoned? + next if r==2 && user.paralyzed? + battle.pbShowAbilitySplash(target) + if user.affectedByPowder?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + case r + when 0 + if user.pbCanSleep?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} made {3} fall asleep!",target.pbThis, + target.abilityName,user.pbThis(true)) + end + user.pbSleep(msg) + end + when 1 + if user.pbCanPoison?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} poisoned {3}!",target.pbThis, + target.abilityName,user.pbThis(true)) + end + user.pbPoison(target,msg) + end + when 2 + if user.pbCanParalyze?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} paralyzed {3}! It may be unable to move!", + target.pbThis,target.abilityName,user.pbThis(true)) + end + user.pbParalyze(target,msg) + end + end + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:FLAMEBODY, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + next if user.burned? || battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if user.pbCanBurn?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} burned {3}!",target.pbThis,target.abilityName,user.pbThis(true)) + end + user.pbBurn(target,msg) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:GOOEY, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + user.pbLowerStatStageByAbility(PBStats::SPEED,1,target,true,true) + } +) + +BattleHandlers::TargetAbilityOnHit.copy(:GOOEY,:TANGLINGHAIR) + +BattleHandlers::TargetAbilityOnHit.add(:ILLUSION, + proc { |ability,user,target,move,battle| + # NOTE: This intentionally doesn't show the ability splash. + next if !target.effects[PBEffects::Illusion] + target.effects[PBEffects::Illusion] = nil + battle.scene.pbChangePokemon(target,target.pokemon) + battle.pbDisplay(_INTL("{1}'s illusion wore off!",target.pbThis)) + battle.pbSetSeen(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:INNARDSOUT, + proc { |ability,user,target,move,battle| + next if !target.fainted? || user.dummy + battle.pbShowAbilitySplash(target) + if user.takesIndirectDamage?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + battle.scene.pbDamageAnimation(user) + user.pbReduceHP(target.damageState.hpLost,false) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} is hurt!",user.pbThis)) + else + battle.pbDisplay(_INTL("{1} is hurt by {2}'s {3}!",user.pbThis, + target.pbThis(true),target.abilityName)) + end + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:IRONBARBS, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + battle.pbShowAbilitySplash(target) + if user.takesIndirectDamage?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + battle.scene.pbDamageAnimation(user) + user.pbReduceHP(user.totalhp/8,false) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} is hurt!",user.pbThis)) + else + battle.pbDisplay(_INTL("{1} is hurt by {2}'s {3}!",user.pbThis, + target.pbThis(true),target.abilityName)) + end + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.copy(:IRONBARBS,:ROUGHSKIN) + +BattleHandlers::TargetAbilityOnHit.add(:JUSTIFIED, + proc { |ability,user,target,move,battle| + next if !isConst?(move.calcType,PBTypes,:DARK) + target.pbRaiseStatStageByAbility(PBStats::ATTACK,1,target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:MUMMY, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + next if user.fainted? + abilityBlacklist = [ + # This ability + :MUMMY, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be replaced +# :FORECAST, # This can be replaced + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM, + ] + failed = false + abilityBlacklist.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + failed = true + break + end + next if failed + oldAbil = -1 + battle.pbShowAbilitySplash(target) if user.opposes?(target) + if user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + oldAbil = user.ability + battle.pbShowAbilitySplash(user,true,false) if user.opposes?(target) + user.ability = getConst(PBAbilities,:MUMMY) + battle.pbReplaceAbilitySplash(user) if user.opposes?(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s Ability became {2}!",user.pbThis,user.abilityName)) + else + battle.pbDisplay(_INTL("{1}'s Ability became {2} because of {3}!", + user.pbThis,user.abilityName,target.pbThis(true))) + end + battle.pbHideAbilitySplash(user) if user.opposes?(target) + end + battle.pbHideAbilitySplash(target) if user.opposes?(target) + user.pbOnAbilityChanged(oldAbil) if oldAbil>=0 + } +) + +BattleHandlers::TargetAbilityOnHit.add(:POISONPOINT, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + next if user.poisoned? || battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if user.pbCanPoison?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} poisoned {3}!",target.pbThis,target.abilityName,user.pbThis(true)) + end + user.pbPoison(target,msg) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:RATTLED, + proc { |ability,user,target,move,battle| + next if !isConst?(move.calcType,PBTypes,:BUG) && + !isConst?(move.calcType,PBTypes,:DARK) && + !isConst?(move.calcType,PBTypes,:GHOST) + target.pbRaiseStatStageByAbility(PBStats::SPEED,1,target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:STAMINA, + proc { |ability,user,target,move,battle| + target.pbRaiseStatStageByAbility(PBStats::DEFENSE,1,target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:STATIC, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + next if user.paralyzed? || battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if user.pbCanParalyze?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} paralyzed {3}! It may be unable to move!", + target.pbThis,target.abilityName,user.pbThis(true)) + end + user.pbParalyze(target,msg) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:WATERCOMPACTION, + proc { |ability,user,target,move,battle| + next if !isConst?(move.calcType,PBTypes,:WATER) + target.pbRaiseStatStageByAbility(PBStats::DEFENSE,2,target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:WEAKARMOR, + proc { |ability,user,target,move,battle| + next if !move.physicalMove? + next if !target.pbCanLowerStatStage?(PBStats::DEFENSE,target) && + !target.pbCanRaiseStatStage?(PBStats::SPEED,target) + battle.pbShowAbilitySplash(target) + target.pbLowerStatStageByAbility(PBStats::DEFENSE,1,target,false) + target.pbRaiseStatStageByAbility(PBStats::SPEED, + (NEWEST_BATTLE_MECHANICS) ? 2 : 1,target,false) + battle.pbHideAbilitySplash(target) + } +) + +#=============================================================================== +# UserAbilityOnHit handlers +#=============================================================================== + +BattleHandlers::UserAbilityOnHit.add(:POISONTOUCH, + proc { |ability,user,target,move,battle| + next if !move.contactMove? + next if battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(user) + if target.hasActiveAbility?(:SHIELDDUST) && !battle.moldBreaker + battle.pbShowAbilitySplash(target) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + end + battle.pbHideAbilitySplash(target) + elsif target.pbCanPoison?(user,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} poisoned {3}!",user.pbThis,user.abilityName,target.pbThis(true)) + end + target.pbPoison(user,msg) + end + battle.pbHideAbilitySplash(user) + } +) + +#=============================================================================== +# UserAbilityEndOfMove handlers +#=============================================================================== + +BattleHandlers::UserAbilityEndOfMove.add(:BEASTBOOST, + proc { |ability,user,targets,move,battle| + next if battle.pbAllFainted?(user.idxOpposingSide) + numFainted = 0 + targets.each { |b| numFainted += 1 if b.damageState.fainted } + next if numFainted==0 + userStats = user.plainStats + highestStatValue = userStats.max + PBStats.eachMainBattleStat do |s| + next if userStats[s]0 + next if battle.wildBattle? && user.opposes? + targets.each do |b| + next if b.damageState.unaffected || b.damageState.substitute + next if b.item==0 + next if b.unlosableItem?(b.item) || user.unlosableItem?(b.item) + battle.pbShowAbilitySplash(user) + if b.hasActiveAbility?(:STICKYHOLD) + battle.pbShowAbilitySplash(b) if user.opposes?(b) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s item cannot be stolen!",b.pbThis)) + end + battle.pbHideAbilitySplash(b) if user.opposes?(b) + next + end + user.item = b.item + b.item = 0 + b.effects[PBEffects::Unburden] = true + if battle.wildBattle? && user.initialItem==0 && b.initialItem==user.item + user.setInitialItem(user.item) + b.setInitialItem(0) + end + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} stole {2}'s {3}!",user.pbThis, + b.pbThis(true),user.itemName)) + else + battle.pbDisplay(_INTL("{1} stole {2}'s {3} with {4}!",user.pbThis, + b.pbThis(true),user.itemName,user.abilityName)) + end + battle.pbHideAbilitySplash(user) + user.pbHeldItemTriggerCheck + break + end + } +) + +BattleHandlers::UserAbilityEndOfMove.add(:MOXIE, + proc { |ability,user,targets,move,battle| + next if battle.pbAllFainted?(user.idxOpposingSide) + numFainted = 0 + targets.each { |b| numFainted += 1 if b.damageState.fainted } + next if numFainted==0 || !user.pbCanRaiseStatStage?(PBStats::ATTACK,user) + user.pbRaiseStatStageByAbility(PBStats::ATTACK,numFainted,user) + } +) + +#=============================================================================== +# TargetAbilityAfterMoveUse handlers +#=============================================================================== + +BattleHandlers::TargetAbilityAfterMoveUse.add(:BERSERK, + proc { |ability,target,user,move,switched,battle| + next if !move.damagingMove? + next if target.damageState.initialHP=target.totalhp/2 + next if !target.pbCanRaiseStatStage?(PBStats::SPATK,target) + target.pbRaiseStatStageByAbility(PBStats::SPATK,1,target) + } +) + +BattleHandlers::TargetAbilityAfterMoveUse.add(:COLORCHANGE, + proc { |ability,target,user,move,switched,battle| + next if target.damageState.calcDamage==0 || target.damageState.substitute + next if move.calcType<0 || PBTypes.isPseudoType?(move.calcType) + next if target.pbHasType?(move.calcType) && !target.pbHasOtherType?(move.calcType) + typeName = PBTypes.getName(move.calcType) + battle.pbShowAbilitySplash(target) + target.pbChangeTypes(move.calcType) + battle.pbDisplay(_INTL("{1}'s {2} made it the {3} type!",target.pbThis, + target.abilityName,typeName)) + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityAfterMoveUse.add(:PICKPOCKET, + proc { |ability,target,user,move,switched,battle| + # NOTE: According to Bulbapedia, this can still trigger to steal the user's + # item even if it was switched out by a Red Card. This doesn't make + # sense, so this code doesn't do it. + next if battle.wildBattle? && target.opposes? + next if !move.contactMove? + next if switched.include?(user.index) + next if user.effects[PBEffects::Substitute]>0 || target.damageState.substitute + next if target.item>0 || user.item==0 + next if user.unlosableItem?(user.item) || target.unlosableItem?(user.item) + battle.pbShowAbilitySplash(target) + if user.hasActiveAbility?(:STICKYHOLD) + battle.pbShowAbilitySplash(user) if target.opposes?(user) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s item cannot be stolen!",user.pbThis)) + end + battle.pbHideAbilitySplash(user) if target.opposes?(user) + battle.pbHideAbilitySplash(target) + next + end + target.item = user.item + user.item = 0 + user.effects[PBEffects::Unburden] = true + if battle.wildBattle? && target.initialItem==0 && user.initialItem==target.item + target.setInitialItem(target.item) + user.setInitialItem(0) + end + battle.pbDisplay(_INTL("{1} pickpocketed {2}'s {3}!",target.pbThis, + user.pbThis(true),target.itemName)) + battle.pbHideAbilitySplash(target) + target.pbHeldItemTriggerCheck + } +) + +#=============================================================================== +# EORWeatherAbility handlers +#=============================================================================== + +BattleHandlers::EORWeatherAbility.add(:DRYSKIN, + proc { |ability,weather,battler,battle| + case weather + when PBWeather::Sun, PBWeather::HarshSun + battle.pbShowAbilitySplash(battler) + battle.scene.pbDamageAnimation(battler) + battler.pbReduceHP(battler.totalhp/8,false) + battle.pbDisplay(_INTL("{1} was hurt by the sunlight!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + battler.pbItemHPHealCheck + when PBWeather::Rain, PBWeather::HeavyRain + next if !battler.canHeal? + battle.pbShowAbilitySplash(battler) + battler.pbRecoverHP(battler.totalhp/8) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s HP was restored.",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + end + } +) + +BattleHandlers::EORWeatherAbility.add(:ICEBODY, + proc { |ability,weather,battler,battle| + next unless weather==PBWeather::Hail + next if !battler.canHeal? + battle.pbShowAbilitySplash(battler) + battler.pbRecoverHP(battler.totalhp/16) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s HP was restored.",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::EORWeatherAbility.add(:RAINDISH, + proc { |ability,weather,battler,battle| + next unless weather==PBWeather::Rain || weather==PBWeather::HeavyRain + next if !battler.canHeal? + battle.pbShowAbilitySplash(battler) + battler.pbRecoverHP(battler.totalhp/16) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s HP was restored.",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::EORWeatherAbility.add(:SOLARPOWER, + proc { |ability,weather,battler,battle| + next unless weather==PBWeather::Sun || weather==PBWeather::HarshSun + battle.pbShowAbilitySplash(battler) + battle.scene.pbDamageAnimation(battler) + battler.pbReduceHP(battler.totalhp/8,false) + battle.pbDisplay(_INTL("{1} was hurt by the sunlight!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + battler.pbItemHPHealCheck + } +) + +#=============================================================================== +# EORHealingAbility handlers +#=============================================================================== + +BattleHandlers::EORHealingAbility.add(:HEALER, + proc { |ability,battler,battle| + next unless battle.pbRandom(100)<30 + battler.eachAlly do |b| + next if b.status==PBStatuses::NONE + battle.pbShowAbilitySplash(battler) + oldStatus = b.status + b.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + case oldStatus + when PBStatuses::SLEEP + battle.pbDisplay(_INTL("{1}'s {2} woke its partner up!",battler.pbThis,battler.abilityName)) + when PBStatuses::POISON + battle.pbDisplay(_INTL("{1}'s {2} cured its partner's poison!",battler.pbThis,battler.abilityName)) + when PBStatuses::BURN + battle.pbDisplay(_INTL("{1}'s {2} healed its partner's burn!",battler.pbThis,battler.abilityName)) + when PBStatuses::PARALYSIS + battle.pbDisplay(_INTL("{1}'s {2} cured its partner's paralysis!",battler.pbThis,battler.abilityName)) + when PBStatuses::FROZEN + battle.pbDisplay(_INTL("{1}'s {2} defrosted its partner!",battler.pbThis,battler.abilityName)) + end + end + battle.pbHideAbilitySplash(battler) + end + } +) + +BattleHandlers::EORHealingAbility.add(:HYDRATION, + proc { |ability,battler,battle| + next if battler.status==PBStatuses::NONE + curWeather = battle.pbWeather + next if curWeather!=PBWeather::Rain && curWeather!=PBWeather::HeavyRain + battle.pbShowAbilitySplash(battler) + oldStatus = battler.status + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + case oldStatus + when PBStatuses::SLEEP + battle.pbDisplay(_INTL("{1}'s {2} woke it up!",battler.pbThis,battler.abilityName)) + when PBStatuses::POISON + battle.pbDisplay(_INTL("{1}'s {2} cured its poison!",battler.pbThis,battler.abilityName)) + when PBStatuses::BURN + battle.pbDisplay(_INTL("{1}'s {2} healed its burn!",battler.pbThis,battler.abilityName)) + when PBStatuses::PARALYSIS + battle.pbDisplay(_INTL("{1}'s {2} cured its paralysis!",battler.pbThis,battler.abilityName)) + when PBStatuses::FROZEN + battle.pbDisplay(_INTL("{1}'s {2} defrosted it!",battler.pbThis,battler.abilityName)) + end + end + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::EORHealingAbility.add(:SHEDSKIN, + proc { |ability,battler,battle| + next if battler.status==PBStatuses::NONE + next unless battle.pbRandom(100)<30 + battle.pbShowAbilitySplash(battler) + oldStatus = battler.status + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + case oldStatus + when PBStatuses::SLEEP + battle.pbDisplay(_INTL("{1}'s {2} woke it up!",battler.pbThis,battler.abilityName)) + when PBStatuses::POISON + battle.pbDisplay(_INTL("{1}'s {2} cured its poison!",battler.pbThis,battler.abilityName)) + when PBStatuses::BURN + battle.pbDisplay(_INTL("{1}'s {2} healed its burn!",battler.pbThis,battler.abilityName)) + when PBStatuses::PARALYSIS + battle.pbDisplay(_INTL("{1}'s {2} cured its paralysis!",battler.pbThis,battler.abilityName)) + when PBStatuses::FROZEN + battle.pbDisplay(_INTL("{1}'s {2} defrosted it!",battler.pbThis,battler.abilityName)) + end + end + battle.pbHideAbilitySplash(battler) + } +) + +#=============================================================================== +# EOREffectAbility handlers +#=============================================================================== + +BattleHandlers::EOREffectAbility.add(:BADDREAMS, + proc { |ability,battler,battle| + battle.eachOtherSideBattler(battler.index) do |b| + next if !b.near?(battler) || !b.asleep? + battle.pbShowAbilitySplash(battler) + next if !b.takesIndirectDamage?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + oldHP = b.hp + b.pbReduceHP(b.totalhp/8) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} is tormented!",b.pbThis)) + else + battle.pbDisplay(_INTL("{1} is tormented by {2}'s {3}!",b.pbThis, + battler.pbThis(true),battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + } +) + +BattleHandlers::EOREffectAbility.add(:MOODY, + proc { |ability,battler,battle| + randomUp = []; randomDown = [] + PBStats.eachBattleStat do |s| + randomUp.push(s) if battler.pbCanRaiseStatStage?(s,battler) + randomDown.push(s) if battler.pbCanLowerStatStage?(s,battler) + end + next if randomUp.length==0 && randomDown.length==0 + battle.pbShowAbilitySplash(battler) + if randomUp.length>0 + r = battle.pbRandom(randomUp.length) + battler.pbRaiseStatStageByAbility(randomUp[r],2,battler,false) + randomDown.delete(randomUp[r]) + end + if randomDown.length>0 + r = battle.pbRandom(randomDown.length) + battler.pbLowerStatStageByAbility(randomDown[r],1,battler,false) + end + battle.pbHideAbilitySplash(battler) + battler.pbItemStatRestoreCheck if randomDown.length>0 + } +) + +BattleHandlers::EOREffectAbility.add(:SPEEDBOOST, + proc { |ability,battler,battle| + # A Pokémon's turnCount is 0 if it became active after the beginning of a + # round + if battler.turnCount>0 && battler.pbCanRaiseStatStage?(PBStats::SPEED,battler) + battler.pbRaiseStatStageByAbility(PBStats::SPEED,1,battler) + end + } +) + +#=============================================================================== +# EORGainItemAbility handlers +#=============================================================================== + +BattleHandlers::EORGainItemAbility.add(:HARVEST, + proc { |ability,battler,battle| + next if battler.item>0 + next if battler.recycleItem<=0 || !pbIsBerry?(battler.recycleItem) + curWeather = battle.pbWeather + if curWeather!=PBWeather::Sun && curWeather!=PBWeather::HarshSun + next unless battle.pbRandom(100)<50 + end + battle.pbShowAbilitySplash(battler) + battler.item = battler.recycleItem + battler.setRecycleItem(0) + battler.setInitialItem(battler.item) if battler.initialItem==0 + battle.pbDisplay(_INTL("{1} harvested one {2}!",battler.pbThis,battler.itemName)) + battle.pbHideAbilitySplash(battler) + battler.pbHeldItemTriggerCheck + } +) + +BattleHandlers::EORGainItemAbility.add(:PICKUP, + proc { |ability,battler,battle| + next if battler.item>0 + foundItem = 0; fromBattler = nil; use = 0 + battle.eachBattler do |b| + next if b.index==battler.index + next if b.effects[PBEffects::PickupUse]<=use + foundItem = b.effects[PBEffects::PickupItem] + fromBattler = b + use = b.effects[PBEffects::PickupUse] + end + next if foundItem<=0 + battle.pbShowAbilitySplash(battler) + battler.item = foundItem + fromBattler.effects[PBEffects::PickupItem] = 0 + fromBattler.effects[PBEffects::PickupUse] = 0 + fromBattler.setRecycleItem(0) if fromBattler.recycleItem==foundItem + if battle.wildBattle? && battler.initialItem==0 && fromBattler.initialItem==foundItem + battler.setInitialItem(foundItem) + fromBattler.setInitialItem(0) + end + battle.pbDisplay(_INTL("{1} found one {2}!",battler.pbThis,battler.itemName)) + battle.pbHideAbilitySplash(battler) + battler.pbHeldItemTriggerCheck + } +) + +#=============================================================================== +# CertainSwitchingUserAbility handlers +#=============================================================================== + +# There aren't any! + +#=============================================================================== +# TrappingTargetAbility handlers +#=============================================================================== + +BattleHandlers::TrappingTargetAbility.add(:ARENATRAP, + proc { |ability,switcher,bearer,battle| + next true if !switcher.airborne? + } +) + +BattleHandlers::TrappingTargetAbility.add(:MAGNETPULL, + proc { |ability,switcher,bearer,battle| + next true if switcher.pbHasType?(:STEEL) + } +) + +BattleHandlers::TrappingTargetAbility.add(:SHADOWTAG, + proc { |ability,switcher,bearer,battle| + next true if !switcher.hasActiveAbility?(:SHADOWTAG) + } +) + +#=============================================================================== +# AbilityOnSwitchIn handlers +#=============================================================================== + +BattleHandlers::AbilityOnSwitchIn.add(:AIRLOCK, + proc { |ability,battler,battle| + battle.pbShowAbilitySplash(battler) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} has {2}!",battler.pbThis,battler.abilityName)) + end + battle.pbDisplay(_INTL("The effects of the weather disappeared.")) + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::AbilityOnSwitchIn.copy(:AIRLOCK,:CLOUDNINE) + +BattleHandlers::AbilityOnSwitchIn.add(:ANTICIPATION, + proc { |ability,battler,battle| + next if !battler.pbOwnedByPlayer? + battlerTypes = battler.pbTypes(true) + type1 = (battlerTypes.length>0) ? battlerTypes[0] : nil + type2 = (battlerTypes.length>1) ? battlerTypes[1] : type1 + type3 = (battlerTypes.length>2) ? battlerTypes[2] : type2 + found = false + battle.eachOtherSideBattler(battler.index) do |b| + b.eachMove do |m| + next if m.statusMove? + moveData = pbGetMoveData(m.id) + if type1 + moveType = moveData[MOVE_TYPE] + if NEWEST_BATTLE_MECHANICS && isConst?(m.id,PBMoves,:HIDDENPOWER) + moveType = pbHiddenPower(b.pokemon)[0] + end + eff = PBTypes.getCombinedEffectiveness(moveData[MOVE_TYPE],type1,type2,type3) + next if PBTypes.ineffective?(eff) + next if !PBTypes.superEffective?(eff) && moveData[MOVE_FUNCTION_CODE]!="070" # OHKO + else + next if moveData[MOVE_FUNCTION_CODE]!="070" # OHKO + end + found = true + break + end + break if found + end + if found + battle.pbShowAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} shuddered with anticipation!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + end + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:AURABREAK, + proc { |ability,battler,battle| + battle.pbShowAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} reversed all other Pokémon's auras!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:COMATOSE, + proc { |ability,battler,battle| + battle.pbShowAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} is drowsing!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:DARKAURA, + proc { |ability,battler,battle| + battle.pbShowAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} is radiating a dark aura!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:DELTASTREAM, + proc { |ability,battler,battle| + pbBattleWeatherAbility(PBWeather::StrongWinds,battler,battle,true) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:DESOLATELAND, + proc { |ability,battler,battle| + pbBattleWeatherAbility(PBWeather::HarshSun,battler,battle,true) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:DOWNLOAD, + proc { |ability,battler,battle| + oDef = oSpDef = 0 + battle.eachOtherSideBattler(battler.index) do |b| + oDef += b.defense + oSpDef += b.spdef + end + stat = (oDef