Developers Haru,, Posted November 2, 2023 Developers Share Posted November 2, 2023 An Overview Pokemon Rejuvenation and Desolation (and soon Reborn) use an almost completely overhauled version of Essentials which makes regular modding as most of you in the community have known it irrelevant, especially in comparison to old versions of these games. This tutorial aims to cover almost all bases; Pokemon, items, moves, abilities, town map (rejuv), trainers, trainer types, bosses, and anything else I can think of. NOTES: This tutorial assumes you have a basic understanding of coding. This includes general syntax and knowing how to read error messages (VERY IMPORTANT AND VERY SIMPLE). This tutorial is for Rejuvenation V13.5+, Desolation EP6+, and Reborn E19.17+ This tutorial is specifically built off Rejuvenation V13.5. Some graphics used later on may be entirely irrelevant to Desolation and Reborn and may require their own different graphics due to the custom UI. This tutorial is based on Windows operating systems. I do not claim to have knowledge of how MacOS or Linux devices operate and do not guarantee these steps will work exactly as listed. Do not use any online functions of any games while modding. I will not be doing anything inside of RPG Maker XP. Using that software is the easiest part provided you *own* it in the first place. I will not be covering fields. They are very clearly outlined as to how they work in each game's respective fieldtxt.rb file, and frankly have too much to explain implementation for them all. Getting Started So! You want to mod, huh? Cool. Here are a few steps to get you started. Download a text editor other than Notepad. I personally recommend Visual Studio Code, as it is what I will be using. You'll also want a clean copy of any of the games, meaning nothing has been changed and no mods are added. Before we get started on doing anything, I'll walk you through setting up your workspace. Step 1 First, we're going to set up the console, which means yes, the horrid "D"-word. The console window will not let you interact with the game, instead it prints out additional information and lets you utilize a separate print feature that does not cause pop-ups. The main reason for this is to get access to the F6 functionality, which lets us directly run scripts in-game. Steps: Spoiler 1. Right click Game.exe 2. Click Create Shortcut 3. Right click the shortcut that was just made. On a clean install it should be labeled Game.exe - Shortcut 4. There will be a text field labeled Target. Select that text field, and go to the end of the text there. Type debug at the end like so: 5. Click Apply and then close the window. You can now launch the game from the shortcut and it will show with a second screen that we call the console. Step 2 Lastly, we will set up our workspace. Editors such as Notepad++ should have similar capabilities, but for the purposes of this tutorial, I will be using Visual Studio Code, as linked above. Spoiler 1. Open Visual Studio Code. Press File > Open Folder. Navigate to the respective game's root folder and select the Scripts folder inside. 2. On a first launch of VSC, it will prompt you to install a plugin for the language being used. It should automatically find the correct plugin, but you're simply looking for one named Ruby. Here's the link: https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby 3. There should also be another pop-up prompting you to open a workspace. You'll want to go ahead and allow that as well. It should be labeled Perry Script workfolder.code-workspace. This will let you edit the PBS files from VSC as well. Ignore the rest of this step if this is the case. 4. If it is not included, we need to create a workspace. Click File > Add Folder to Workspace and select the PBS folder Click File > Save Workspace As... And that's that! You're ready to start modding! But before we do that, here's a link to every new file being used for this tutorial if you'd like to follow along. I will not be providing any pre-compiled code. To use these graphics, drag the extract folders into the root directory of your game (the one with Game.exe!) Download 2 Link to comment Share on other sites More sharing options...
Developers Haru,, Posted November 2, 2023 Author Developers Share Posted November 2, 2023 Converting PBS Files The Rebornverse games no longer use the PBS files from Essentials, but rather a new data format we've elected to call Data Object Hashes, or DOH. It's a rather straightforward name, the objects are stored in cache as subclasses of the DataObject class, and they are stored as hash files. I have done my best to ensure that the conversion is as simple as possible, but there are still a few things that need to be done by hand unfortunately. This post is strictly for content modders not wishing to lose all of their progress, despite how streamlined modding is now. If this does not apply to you, carry on. Before I detail the steps, here are the things that won't be converted automatically: Types. There's hardly any and they're very easy to set up. Abilities. There's a lot, and when most are adding Gen 8/PLA abilities, we already have them in the game. They're also even easier to set up than types. Items. The formatting is far too different and the amount of flags we have added for items doesn't convert directly from the PBS files. Moves. Same reason for items and abilities. Forms. Unfortunately, literally impossible. If you wanted to manually convert them to Reborn E19.16 format then you could *possibly* convert them to current DOH format, but that's way more effort than just making a new form. Conversion Process To start things off, here's the file for the converter. To use it, place the file in the Data/Mods folder. Open the game Hit F6 and type convertPBS This will open a small little menu where you can select the things to convert being; Pokemon, Trainer Types/Teams, Encounters, Map Connections, and Metadata. I strongly recommend only using Pokemon conversion, but I know those with lots of encounter changes and trainer changes would be left out. Pokemon should largely be unaffected as they merge the PBS into the existing DOH. Trainers do not have their defeat lines included from conversion, which means those need to be manually added. Encounters should be fine to use provided map data has not changed (looking at you, Rejuv). The same applies for Connections and Metadata. Connections should most likely be ignored for most modders and was really only because it was super easily convertible. And that's that for converting. Anything not mentioned will be explained later in the posts. 2 Link to comment Share on other sites More sharing options...
Developers Haru,, Posted November 2, 2023 Author Developers Share Posted November 2, 2023 Adding a Type The first thing we'll do is probably the easiest. We'll add a new type to the game. We'll call it the Light type, to match our new Pokemon later on down the line. A type is defined as such: Spoiler :DARK => { # Type identifier :name => "Dark", # Display name :weaknesses => [:FIGHTING,:BUG,:FAIRY,:SHADOW], # Takes super effective damage from these types :resistances => [:GHOST,:DARK], # Takes not very effective damage from these types :immunities => [:PSYCHIC], # Takes zero damage from these types :specialtype => true # Defines whether this type is a special or physical in Glitch fields. }, To make ours, we need to open the typetext.rb file, and add our new type at the end. I've gone and typed it out for you. Spoiler :LIGHT => { :name => "Light", :weaknesses => [:DARK,:NORMAL], :resistances => [:BUG,:ELECTRIC,:LIGHT], :immunities => [:FIRE], :specialtype => true } After that, we'll need a few different images. I've gone and made them for you already as detailed at the top of post. From there, we can start to compile. Hit F6 and type compileTypes Hit enter and fully close the game You've set up the type! We'll add a new move next. 1 Link to comment Share on other sites More sharing options...
Developers Haru,, Posted November 2, 2023 Author Developers Share Posted November 2, 2023 Adding a Move Moves are fairly simple to add, but get a little extra complicated as you dive deeper. Let's start by breaking down all the flags a move can have. Similar to the above, every move is defined by a symbol for their identifier and flags, as shown by the other moves in movetext.rb. Those flags are: Spoiler :ID # Almost exclusively used for conversion, unneeded otherwise :name # The shortened name for display :longname # The full name, for moves like Light That Burns the Sky. Used in battle exclusively. :desc # The move description :type # The move typing :category # Physical/special/status :basedamage # See later for info :accuracy # See later for info :maxpp # Default max PP, using a PP Max is automatically calculated :target # See later for info :function # See later for info :contact # Whether the move makes contact :kingrock # Whether the move is affected by King's Rock :effect # The percent chance for an effect to trigger :highcrit # Whether the move has a high crit ratio :soundmove # Whether the move is sound based :sharpmove # Whether the move is Sharpness boosted :beammove # Whether the move is a beam move (Claydol Crest) :snatchable # Whether the move is affected by Snatch :nonmirror # Whether the move can be pulled by Mirror Move :healingmove # Whether the move heals :magiccoat # Whether the move is reflected by Magic Coat :recoil # The damage ratio as a decimal for recoil damage :punchmove # Whether the move is Iron Fist boosted :moreeffect # The percent change for a secondary effect to trigger (Think Fire Fang flinch/burn) :gravityblocked # Whether a move gets blocked by gravity :defrost # Whether a move immediately defrosts freeze :bypassprotect # Whether a move ignores Protect :windmove # Whether a move is a wind based move (Flag used by the Genies? :heavymove # Whether the move can be used twice in a row (Gigaton Hammer, Decimation) :zmove # Whether the move is a Z Move :intercept # Whether the move is a Interceptor move (Rejuv) Base Damage can be any value. Values of 0 are reserved for status moves and values of 1 are reserved for moves with custom damage formulas such as Dragon Rage, Heavy Slam, and Nature's Madness. Accuracy can also be any value. Values of 0 are moves that bypass the accuracy check. Target is for selecting targets in double battles. These values are: :SingleNonUser, :AllOpposing, :User, :RandomOpposing, :AllNonUsers, :NoTarget, :BothSides, :UserSide, :OppositeOpposing, :OpposingSide, :UserOrPartner, :Partner, :SingleOpposing, :DragonDarts You can, of course, add in your own flags here. For instance, if you wanted to make a section of moves for an ability that boosted liquid based moves, you could add a :liquid flag for every liquid move! Move functions are the bulk of moves. They are defined by a 3 digit hexadecimal number starting with 0x. Moves free up starting at 0x18A, and we'll be using that one here. They're defined in Battle_MoveEffects.rb and each are subclasses of the general PokeBattle_Move class. You can override every function that PokeBattle_Move calls for your own needs. Move functions must use the respective 3 digit hexadecimal number for its class. We'll be making a move named Photon Slash. It will go off the highest attack and have a chance to lower defense. It'll be defined here: Spoiler :PHOTONSLASH => { :ID => 833, :name => "Photon Slash", :function => 0x18A, :type => :LIGHT, :category => :physical, :basedamage => 70, :accuracy => 100, :maxpp => 10, :effect => 20, :target => :SingleNonUser, :desc => "The target is struck with a condensed beam of light. May lower defense. Uses higher attack stat." }, In addition, the class we'll need will be defined as such, copy and pasting code from Photon Geyser and Tail Whip: Spoiler ################################################################################ # Photon Slash ################################################################################ class PokeBattle_Move_18A < PokeBattle_Move def pbEffect(attacker,opponent,hitnum=0,alltargets=nil,showanimation=true) return super(attacker,opponent,hitnum,alltargets,showanimation) if @basedamage>0 return -1 if !opponent.pbCanReduceStatStage?(PBStats::DEFENSE,true) pbShowAnimation(@move,attacker,opponent,hitnum,alltargets,showanimation) ret=opponent.pbReduceStat(PBStats::DEFENSE,1,abilitymessage:false, statdropper: attacker) return ret ? 0 : -1 end def pbAdditionalEffect(attacker,opponent) if opponent.pbCanReduceStatStage?(PBStats::DEFENSE,false) opponent.pbReduceStat(PBStats::DEFENSE,1,abilitymessage:false, statdropper: attacker) end return true end #Moldbreaking handled elsewhere def pbIsPhysical?(type=@type) attacker = @user 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] # Physical Stuff storedatk = attacker.attack atkstage=6 atkmult = 1.0 if attacker.class == PokeBattle_Battler atkstage=attacker.stages[PBStats::ATTACK]+6 atkmult *= 1.5 if attacker.hasWorkingItem(:CHOICEBAND) atkmult *= 1.5 if attacker.ability == :HUSTLE atkmult *= 1.5 if attacker.ability == :TOXICBOOST && (attacker.status== :POISON || @battle.FE == :CORROSIVE || @battle.FE == :CORROSIVEMIST || @battle.FE == :WASTELAND || @battle.FE == :MURKWATERSURFACE) atkmult *= 1.5 if attacker.ability == :GUTS && !attacker.status.nil? atkmult *= 0.5 if attacker.ability == :SLOWSTART && attacker.turncount<5 && !@battle.FE == :DEEPEARTH atkmult *= 2 if (attacker.ability == :PUREPOWER && @battle.FE != :PSYTERRAIN) || attacker.ability == :HUGEPOWER atkmult *= 2 if attacker.hasWorkingItem(:THICKCLUB) && ((attacker.pokemon.species == :CUBONE) || (attacker.pokemon.species == :MAROWAK)) atkmult *= 0.5 if attacker.status== :BURN && !(attacker.ability == :GUTS && !attacker.status.nil?) end storedatk*=((stagemul[atkstage]/stagediv[atkstage])*atkmult) # Special Stuff storedspatk = attacker.spatk spatkstage=6 spatkmult=1.0 if attacker.class == PokeBattle_Battler spatkstage=attacker.stages[PBStats::SPATK]+6 spatkmult *= 1.5 if attacker.hasWorkingItem(:CHOICESPECS) spatkmult *= 2 if attacker.hasWorkingItem(:DEEPSEATOOTH) && (attacker.pokemon.species == :CLAMPERL) spatkmult *= 2 if attacker.hasWorkingItem(:LIGHTBALL) && (attacker.pokemon.species == :PIKACHU) spatkmult *= 1.5 if attacker.ability == :FLAREBOOST && (attacker.status== :BURN || @battle.FE == :BURNING || @battle.FE == :VOLCANIC || @battle.FE == :INFERNAL) && @battle.FE != :FROZENDIMENSION spatkmult *= 1.5 if attacker.ability == :MINUS && (attacker.pbPartner.ability == :PLUS || @battle.FE == :SHORTCIRCUIT || (Rejuv && @battle.FE == :ELECTERRAIN)) || @battle.state.effects[:ELECTERRAIN] > 0 spatkmult *= 1.5 if attacker.ability == :PLUS && (attacker.pbPartner.ability == :MINUS || @battle.FE == :SHORTCIRCUIT || (Rejuv && @battle.FE == :ELECTERRAIN)) || @battle.state.effects[:ELECTERRAIN] > 0 spatkmult *= 1.5 if attacker.ability == :SOLARPOWER && (@battle.pbWeather== :SUNNYDAY && !attacker.hasWorkingItem(:UTILITYUMBRELLA)) && @battle.FE != :FROZENDIMENSION spatkmult *= 1.3 if attacker.pbPartner.ability == :BATTERY spatkmult *= 2 if attacker.ability == :PUREPOWER && @battle.FE == :PSYTERRAIN end storedspatk*=((stagemul[spatkstage]/stagediv[spatkstage])*spatkmult) storedspatk= attacker.getSpecialStat if @battle.FE == :GLITCH && attacker.class == PokeBattle_Battler # Final selection if storedatk>storedspatk return true else return false end end def pbIsSpecial?(type=@type) return !pbIsPhysical?(type) end end After that, you can run compileMoves and see your move in game! For a quick method of teaching it to a Pokemon, open F6 and type in $Trainer.party[0].pbLearnMove(:PHOTONSLASH) to teach it to the first Pokemon in your party, forgetting the first move it has. Adding a Z-Move Z-Moves are quite simple to add with our new setup. The main bulk is editing various files which I will detail. First, we need to create another new move for our Z-Move. You really only need to copy an existing Z-Move and change values to match your intentions. I've gone ahead and done that for you: Spoiler :BLINDINGLIGHT => { :name => "Blinding Light", :function => 0x000, :type => :LIGHT, :category => :physical, :basedamage => 100, :accuracy => 0, :maxpp => 0, :target => :SingleNonUser, :kingrock => true, :zmove => true, :desc => "The user absorbs the light around it and unleashes it in a wave towards the target. The power varies, depending on the original move." }, Add that to movetext.rb and compile. Then we can move on to the tedious part. If you've added a new type when doing this, you may want add a new form to Silvally or Arceus. Those will be found in Battle_MoveEffects.rb and Battler.rb/Pokemon.rb. They modify the Judgment/Multi Attack and Arceus/Silvally forms respectively. I'll get into adding forms later, however I won't be detailing the animation editor, which can be found in the bottom of the debug menu. I simply have never touched it and therefore will not attempt to explain something I don't know. Back to Z-Moves. The first file we're going to want to check out is ItemEffects.rb. You'll find a line of code in there that says: ItemHandlers::UseOnPokemon.copy(:NORMALIUMZ, :FLYINIUMZ, :GROUNDIUMZ, :BUGINIUMZ, :STEELIUMZ, :WATERIUMZ, :ELECTRIUMZ, :ICIUMZ, :DARKINIUMZ, :FIGHTINIUMZ, :POISONIUMZ, :ROCKIUMZ, :GHOSTIUMZ, :FIRIUMZ, :GRASSIUMZ, :PSYCHIUMZ, :DRAGONIUMZ, :FAIRIUMZ) We'll simply add :LIGHTINIUMZ to the end of this list for the name of our item, which we'll add later. The next place to look is in the file PBStuff.rb. This file is an amalgam of lists used in various places throughout the files. The hashes we're concerned with right now are TYPETOZCRYSTAL, MOVETOZCRYSTAL, and CRYSTALTOZMOVE. We can ignore MOVETOZCRYSTAL as we are making a type based Z-Move and not a Pokemon specific one, but the same process as below applies, just making sure to follow the formatting shown in the hash. To add our Z-Move, we'll need to add the following two lines to TYPETOZCRYSTAL and CRYSTALTOZMOVE respectively. :LIGHT => :LIGHTINIUMZ :LIGHTINIUMZ => :BLINDINGLIGHT I cannot stress this enough but do not forgot to use commas. Commas need to separate hash and array entries, and the code will not run if you forget commas. You'll see this constantly if you're doing big edits at once and forget commas here and there (trust me, everyone on the dev team is guilty of this). Now that we've done this, we can move on to items. 2 Link to comment Share on other sites More sharing options...
Developers Haru,, Posted November 4, 2023 Author Developers Share Posted November 4, 2023 Adding an Item Items are another straightforward process thanks to our changes to the code. In here we'll be adding 3 different items. A Z-Crystal, a TM, and a battle item. The same processes apply to all items and just need certain flags to function as such. First, we can find all our items in itemtext.rb (Are you beginning to see a pattern here?). Here is all the flags for items: Spoiler :ID # Internal item ID. Used to be used for graphics :name # Item display name :desc # Item description :price # Buying price :overworld # Whether the item can be used in overworld :noUseInBattle # Whether the item cannot be used in battle :noUse # Whether the item has no use :evoitem # Whether the item is an evolution item :fossil # Whether the item is a fossil :justsell # Whether the item is a "collector" item {Rare Bone, Relic Gold, etc) :consumehold # Whether the item gets consumed on use in battle :battlehold # Whether the item has a hold effect in battle :incense # Whether the item is an incense item :typeboost # The type as a symbol of what type it boosts when held/consumed :plate # Whether the item is a plate :memory # If the item is a Silvally memory, it stores the typing corresponding to the item :gem # Whether the item is a gem :mint # Whether the item is a mint :utilityhold # Whether the item has an overworld effect when held (Magnetic and Mirror Lures) :pokehold # Whether the item boosts a specific non-legendary Pokemon :legendhold # Whether the item boosts a specific legendary Pokemon :application # Whether the item is an application item (Reborn) :nectar # Whether the item is a nectar item (Oricorio) :quest # Whether the item is a quest item :mail # Whether the item is a mail item :medicine # Whether the item goes in the Medicine pocket :healing # Whether the item has a healing effect :revival # Whether the item has a reivival effect :status # Whether the item cures status :pprestore # Whether the item restores PP :evup # Whether the item raises EVs :levelup # Whether the item grants EXP :ball # Whether the item goes in the Ball pocket :tm # Whether the item goes in the Machines pocket :berry # Whether the item goes in the Berry pocket :pinchberry # Whether the item is a pinch berry :resistberry # Whether the item is a resistance berry :battleitem # Whether the item goes in the Battle Items pocket :crystal # Whether the item goes in the Enhancement Pocket :crest # Whether the item is a crest :zcrystal # Whether the item is a Z-Crystal :keyitem # Whether the item goes in the Key Item pocket :legendary # Whether the item changes forms of Pokemon :evCard # Whether the item grants access to the EV Training Center Rooms (Rejuv) :general # Literally just the Remote PC I honestly don't know :important # Whether the item cannot be sold :niche # Whether the item is niche (Powder Vial, Devon Scope, PokeFlute) :keys # Whether the item is a key (Reborn) :story # Whether the item is a story item :sidequest # Whether the item is a sidequest item Once again, they follow the standard procedure of having a symbol identifier for the item. You can also add your own flags to say that X group of items are from a specific mod. For our items, we'll create them here. We need a Z-Crystal for Light type moves, a TM for them, then a status item. I've written the code here: Spoiler :LIGHTINIUMZ => { :ID => 1106, :name => "Lightinium-Z", :desc => "It converts Z-Power into crystals that upgrade Light-type moves to Light-type Z-Moves.", :price => 0, :crystal => true, :zcrystal => true, :noUseInBattle => true, }, :TM158 => { :ID => 1107, :name => "TM158", :desc => "The target is struck with a condensed beam of light. May lower defense. Uses higher attack stat.", :price => 25000, :tm => :PHOTONSLASH, :noUseInBattle => true, }, :XRESET => { :ID => 1108, :name => "X Reset", :desc => "Removes all negative stat changes.", :price => 500, :battleitem => true, :noUse => true, }, After that you can compile with compileItems and you'll be able to give yourself items via the command Kernel.pbItemBall(:ITEM), where :ITEM is replaced by the symbol you added. Lightinium-Z does not need any further implementation. Our TM needs to be added to the :compatiblemoves attribute of any Pokemon you'd like to learn it, which will be discussed later. The Reset X however does need its full implementation still. We'll be revisiting ItemEffects.rb for this. To explain this file, we need to know a few things. Items use what are known as ItemHandlers. This module is defined in Items.rb and contains all the relevant information we need. There are 6 handlers and 1 extra constant for items you can use multiple of at once. Out of the six handlers; UseFromBag, UseInField, UseOnPokemon, BattleUseOnBattler, BattleUseOnPokemon, and UseInBattle, the one we're interested in BattleUseOnBattler. These handlers are all self explanatory, except for maybe the difference of the battle handlers. BattleUseOnBattler applies to the active Pokemon, BattleUseOnPokemon pull up the party menu in battle, and UseInBattle is for items that affect the field/enemy. Anyway, we'll need to add an effect for our X Reset. The parameters for every item handler is the symbol for the item and a lambda, which is similar to a proc (which will be explained later) except that it only yields values true or false. I've gone ahead and written the code already so you can use it as an example, but each handler uses a different amount of parameters for the lambda. The item itself, the battler, which is a derivative of the Pokemon object used for battles, and the battle scene, which is used to play animations and send messages to the battle scene. The code is as follows: Spoiler ItemHandlers::BattleUseOnBattler.add(:XRESET,lambda{|item,battler,scene| playername=battler.battle.pbPlayer.name itemname=getItemName(item) scene.pbDisplay(_INTL("{1} used the {2}.",playername,itemname)) oldStages = battler.stages.clone for i in PBStats.constants next if i == 0 stat = PBStats.const_get(i) battler.stages[stat] = 0 if battler.stages[stat] < 0 end if oldStages == battler.stages scene.pbDisplay("But it had no effect!") return false end scene.pbDisplay("#{battler.pbThis}'s stats were reset!") return true }) Since this is strictly scripting, we have nothing to compile. These changes take place immediately. And now you've written some items! I'm sure you can figure out the other kinds of items by trial and error. 2 Link to comment Share on other sites More sharing options...
Developers Haru,, Posted November 6, 2023 Author Developers Share Posted November 6, 2023 Adding an Ability Abilities are where it starts to get...complicated. If you want to properly set things up you'll need to modify the AI and Battle files for them to work properly. Fortunately, we can kind of cheat. If you're trying to make a new ability, chances are that some ability accomplishes a similar thing, and we can find where those abilities activate and insert our own. Before we code in an ability, let's break down ability application in Battle_Move.rb with the function pbCalcDamage. Spoiler Initialize various variables, including pulling the base damage from the DOH. Modify base damage if Luvdisc crest or for a move function that directly modifies pbBaseDamage Escape if still no base damage, then check crit chance, initialize boost stages, and get the type of the move user Check if items work Check abilities that modify move power (not pokemon stats, although they are technically equivalent) Check opponent ability for things like Heatproof/Dry Skin Check for items such as type boosting items, gems, other items that affect base damage (Soul Dew, Lustrous Orb, Wise Glasses) Check if the move function modifies pbBaseDamageMultiplier (Fusion Bolt, Facade, Solar Beam) Check crests Check the typing based on effects that modify move power (Charge, Water Sport, Fairy Aura, Helping Hand) Apply field effect modifiers Calculate attackers stats (Foul Play, Body Press, Glitch field, stat boosts, Unaware, Claydol/Dedenne Crest) Pinch Abilities, stat modifying abilities, mid battle crests. Weather Items and field effects that affect stats Calculate opponent defense Sandstorm Abilities that modify defense Items that modify defense Spread move debuff More field effects Field transformations More crests, for extra STAB this time Regular STAB Type effectiveness Damage rolls Final damage multipliers Screens, Tinted Lens, Multiscale, Solid Rock, Type Berries, other crests More multipliers for the final final time, through overriding pbModifyDamage Minimize counters, two-turn move catchers (Gust/Hurricane vs Fly, Surf vs Dive) The actual real damage calculation. It's. A lot. And some of it seems counter productive! But it's the beautiful thing we get to work with. So. New ability time. We'll make a new ability that empowers Light type moves in preparation for our new Pokemon being added later. To do that, we need to crack open abiltext.rb and define our new ability, like so: Spoiler :FLUORESCENT => { :ID => 268, :name => "Fluorescent", :desc => "Powers up Light-type moves...", :fullDesc => "Light-type moves used by the user increase in power by 20%" }, You'll notice two new flags inside this file, :fulldesc and :fullname. These are used for the expanded summary screen. When on the Stats summary page, you can press [C], or whatever your rebinded "A" button is, to view an expanded name and description if one is provided. The team has largely used this to detail exact values for abilities. IDs are also largely irrelevant, still a leftover from the old ID systems, but if you need them they free up at ID 268 as of writing this post, and then again at whatever value the custom abilities per game stop at. Once that's added, you can compile via compileAbilities. Now we'll need to code in this ability. For our case, we're heading to Step 4 as detailed above. Anywhere in that first run through of abilities we can add in the line: when :FLUORESCENT then basemult*=1.2 if @type==:LIGHT Once we're done with this we do not need to do anymore work, however if you intend to properly balance things we need to make edits to the AI as well in the file Battle_AI.rb. The AI is far too large for me to detail in this post, but here's the part where we look for abilities that have similar effects and copy their code for our needs. Luckily, we only need to add a few lines of code to a single section for this ability. We'll be editing where STAB is calculated in the AI for when it gets move scoring. A little of the way down you'll see calculations for Water Bubble, Adaptability, and Steelworker. We'll be adding our code there, or more specifically, copying Water Bubble's code and changing it for Fluorescent. Spoiler # Fluorescent if attacker.ability == :FLUORESCENT && type == :LIGHT damage=(damage*=1.2).round end The AI now properly gets the damage information when a Pokemon has Fluorescent. There is tons of AI nonsense to look through if you want to really balance something, and a good chunk of it is fields. Try checking out what abilities, fields, and moves effect which parts of the AI scores if you're looking for more information. 1 Link to comment Share on other sites More sharing options...
Developers Haru,, Posted November 6, 2023 Author Developers Share Posted November 6, 2023 Adding a Pokemon Probably the most drastic change from previous versions, Pokemon are now easier than ever to implement. Pokemon species and forms are now located inside the file montext.rb, a much more graceful solution than two separate files or one file and not easily modifiable at all. Creating a New Pokemon To start, open up montext.rb inside your workspace, located in your specific game's subfolder. We'll discuss the structure for a Pokemon first. Each field is broken down in the code block below, with further explanation where needed afterwards. Spoiler :VENUSAUR => { # Species name. Identifier when calling information. "Normal Form" => { # Form name. :name => "Venusaur", # Display name :dexnum => 3, # Pokedex number :Type1 => :GRASS, :Type2 => :POISON, :BaseStats => [80, 82, 83, 100, 100, 80], # Base stats, sorted HP/Attack/Defense/Special Attack/Special Defense/Speed :EVs => [0, 0, 0, 2, 1, 0], # EV yield :Abilities => [:OVERGROW, :CHLOROPHYLL], # See after for hidden abilities :GrowthRate => :MediumSlow, # EXP Curve. See after for all values. :GenderRatio => :FemEighth, # Male/Female ratios. See after for all values. :BaseEXP => 236, :CatchRate => 45, :Happiness => 70, # Base happiness when caught :EggSteps => 5355, # Steps required to hatch :preevo => { # Values to store pre-evolution. Added to make it easy to get pre-evolution data. :species => :IVYSAUR, :form => 0 }, :Moveset => [ # Learnset. Stored as [level, move symbol]. 0 is learned on evolution. [0,:PETALDANCE], [1,:TACKLE], [1,:GROWL], [1,:LEECHSEED], [1,:VINEWHIP], [3,:GROWL], [7,:LEECHSEED], [9,:VINEWHIP], [13,:POISONPOWDER], [13,:SLEEPPOWDER], [15,:TAKEDOWN], [20,:RAZORLEAF], [23,:SWEETSCENT], [28,:GROWTH], [31,:DOUBLEEDGE], [39,:WORRYSEED], [45,:SYNTHESIS], [50,:PETALBLIZZARD], # Below is every move that can be learned by the species from TM or tutor. [53,:SOLARBEAM]], # Egg moves are stored specifically within the first stage of the Pokemon. :compatiblemoves => [:AMNESIA,:BIDE,:BIND,:BLOCK,:BODYSLAM,:BULLDOZE,:BULLETSEED,:CELEBRATE,:CHARM,:CURSE,:CUT,:DEFENSECURL,:DOUBLEEDGE,:EARTHPOWER,:EARTHQUAKE,:ECHOEDVOICE,:ENERGYBALL,:FALSESWIPE,:FLASH,:FRENZYPLANT,:FURYCUTTER,:GIGADRAIN,:GIGAIMPACT,:GRASSKNOT,:GRASSPLEDGE,:GRASSYGLIDE,:GRASSYTERRAIN,:HEADBUTT,:HELPINGHAND,:HYPERBEAM,:KNOCKOFF,:LEAFSTORM,:LIGHTSCREEN,:MAGICALLEAF,:MEGADRAIN,:MIMIC,:MUDSLAP,:NATUREPOWER,:OUTRAGE,:POWERWHIP,:RAGE,:RAZORWIND,:REFLECT,:ROAR,:ROCKCLIMB,:ROCKSMASH,:SAFEGUARD,:SEEDBOMB,:SKULLBASH,:SLUDGEBOMB,:SOLARBEAM,:STOMPINGTANTRUM,:STRENGTH,:STRINGSHOT,:SUNNYDAY,:SWEETSCENT,:SWORDSDANCE,:SYNTHESIS,:TAKEDOWN,:TERRAINPULSE,:VENOSHOCK,:WEATHERBALL,:WORKUP,:WORRYSEED,] :moveexceptions => [], # There is an array called PBStuff::UNIVERSALTMS that almost any Pokemon learns. Add those moves here if you want to exclude them from a Pokemon. :Color => "Green", # Flags for color and habitat sorting in Pokedex, which I'm not actually sure are used :Habitat => "Grassland", :EggGroups => [:Monster, :Grass], # Egg Groups :Height => 20, # Height and weight are metric based times 10. Venusaur is 2 meters 100 kilo :Weight => 1000, :kind => "Seed", # Pokedex descriptor and dex entry :dexentry => "Venusaur's flower is said to take on vivid colors if it gets plenty of nutrition and sunlight. The flower's aroma soothes the emotions of people.", :BattlerPlayerY => 16, # Positioning values :BattlerEnemyY => 15, :BattlerAltitude => 0, }, "Mega Form" => { # Another form name :BaseStats => [80, 100, 123, 122, 120, 80], # EVERY single flag used can be changed for forms, except for dex number. :Abilities => [:THICKFAT], :BaseEXP => 281, :Height => 24, :Weight => 1555, }, "Giga Form" => { :BaseStats => [80, 107, 108, 125, 125, 80], :Abilities => [:CHLOROPHYLL], :Height => 240, :Weight => 2216, }, :OnCreation => {}, # The proc for changing forms/stats/anything when creating the Pokemon in game. See after for info. :DefaultForm => 0, # Stores the default forms (can be array, see Toxtricity/Urshifu) for checking if the mon can mega/primal/rift/pulse evo. :MegaForm => { # Stores the resulting form for mega/primal/rift/pulse evo based on held item. :VENUSAURITE => 1, :VENUSAURITEG => 2, }, }, Hidden Abilities are not actually implemented in Rebornverse games, however the ability to add them does exist if you wanted to override our hidden ability override. You'll see some Pokemon use the :HiddenAbility flag. Abilities in this flag are automatically added to the ability list and do not count as real hidden abilities. Growth Rates can be the following values: :Erratic, :Fluctuating, :MediumSlow, :Fast, :MediumFast, :Slow. Further info about these can be gained from sites like Bulbapedia. Gender Ratios can be the following values: :Genderless, :MaleZero, :FemZero, :FemEighth, :FemQuarter, :FemHalf, :MaleQuarter. These values are, in order: Genderless, Always Female, Always Male, 1/8th Female, 1/4th Female, 50/50, 1/4th Male. The creation changing is really useful! It was mainly used to generalize generating regional forms. We use them only for returning form number, but they can be used for a lot of neat things and I'm interested in seeing what you guys can come up with. The general formatting is such: :OnCreation => proc{ next $game_map && Rattata.include?($game_map.map_id) ? 1 : 0 }, We check to make sure $game_map is defined and then check if the current map id matches those included in each regional form array defined in the file SystemConstants.rb found in the game-specific subfolder. We'll add a new Pokemon called Beatote, created by the spriter Samson on PokeCommunity, which is free to use. You likely will be unable to find these sprites without ripping them out of GBA games that already have them. Again, the provided images are located at the start of the thread. These must be named with their dex number, with their respective Battler, Icon, and SE files. Next, you'll need to fill out the Pokemon data. I'll provide the fully written out one here. Spoiler :BEATOTE => { "Normal Form" => { :name => "Beatote", :dexnum => 906, :Type1 => :BUG, :Type2 => :LIGHT, :BaseStats => [75, 85, 110, 50, 95, 80], :EVs => [0, 0, 2, 0, 0, 0], :Abilities => [:ILLUMINATE, :SWARM], :HiddenAbilities => :FLUORESCENT, :GrowthRate => :Erratic, :GenderRatio => :FemZero, :BaseEXP => 184, :CatchRate => 45, :Happiness => 70, :EggSteps => 4080, :preevo => { :species => :VOLBEAT, :form => 0 }, :Moveset => [ [0,:PHOTONSLASH] [1,:FLASH], [1,:TACKLE], [5,:DOUBLETEAM], [8,:CONFUSERAY], [12,:QUICKATTACK], [15,:STRUGGLEBUG], [19,:MOONLIGHT], [22,:TAILGLOW], [26,:SIGNALBEAM], [29,:PROTECT], [33,:ZENHEADBUTT], [36,:HELPINGHAND], [40,:BUGBUZZ], [43,:PLAYROUGH], [47,:DOUBLEEDGE], [50,:INFESTATION]], :compatiblemoves => [:PHOTONSLASH,:AIRSLASH,:DRAININGKISS,:FAKETEARS,:MAGICALLEAF,:POLLENPUFF,:POWERGEM,:SKITTERSMACK,:BATONPASS,:DOUBLEEDGE,:MIMIC,:ACROBATICS,:AERIALACE,:AIRCUTTER,:BODYSLAM,:BRICKBREAK,:BUGBITE,:BUGBUZZ,:CHARGEBEAM,:COUNTER,:DAZZLINGGLEAM,:DEFOG,:DYNAMICPUNCH,:ENCORE,:FLASH,:FLING,:FOCUSPUNCH,:GIGADRAIN,:HELPINGHAND,:ICEPUNCH,:INFESTATION,:LIGHTSCREEN,:MEGAKICK,:MEGAPUNCH,:METRONOME,:MUDSLAP,:OMINOUSWIND,:PLAYROUGH,:POWERUPPUNCH,:PSYCHUP,:RAINDANCE,:ROOST,:SEISMICTOSS,:SHADOWBALL,:SHOCKWAVE,:SIGNALBEAM,:SILVERWIND,:SOLARBEAM,:STRINGSHOT,:STRUGGLEBUG,:SUNNYDAY,:SWIFT,:TAILWIND,:THIEF,:THUNDER,:THUNDERBOLT,:THUNDERPUNCH,:THUNDERWAVE,:TRICK,:UTURN,:WATERPULSE,:ZENHEADBUTT, #Rejuv only moves :IRRITATION,:STACKINGSHOT], :moveexceptions => [], :Color => "Gray", :Habitat => "Forest", :EggGroups => [:Bug, :HumanLike], :Height => 14, :Weight => 423, :WildItemUncommon => :BRIGHTPOWDER, :kind => "Ground Fly", :dexentry => "Beatote live underground, taking over the colonies of the Nincada that live in the area by blinding them.", :BattlerPlayerY => 23, :BattlerEnemyY => 19, :BattlerAltitude => 0, }, :OnCreation => {}, }, After that you can compile via compileMons and give yourself a new Beatote! The Pokedex will get automatically updated if it detects there are more species in the DOH than in the Pokedex itself. For custom forms, you'll need to manually refresh the Pokedex. Spoiler Adding a Form We've successfully added a new Pokemon with a new type. But how can you add a new type without having Arceus's approval? Let's help them out. We're going to do a little cheating and instead of having the plate/Z-Crystal change Arceus's type, we'll change the form based on if you have a Light-type Pokemon or move in your party when encountering it. Unfortunately, though, Arceus (and Silvally) signature moves are tied to their items at the moment. So, we're going to need to override that. Back in Battle_MoveEffects.rb, we need to find PokeBattle_Move_09F. We'll need to throw in a check for our new Arceus form in the pbType method like such: if attacker.species == :ARCEUS && $cache.pkmn[:ARCEUS].forms[attacker.form] == "Light" return super(attacker,:LIGHT) end This should go before any check to avoid needless chance to write over things we aren't accounting for. After that we can go back to montext.rb and define the new form: Spoiler "Light" => { :Abilities => [:FLUORESCENT], :Type1 => :LIGHT, }, :OnCreation => proc{ next 0 if !$Trainer foundLight = false for mon in $Trainer.party if mon.type1 == :LIGHT || mon.type2 == :LIGHT foundLight = true break end for move in mon.moves if move.type == :LIGHT foundLight = true break end end break if foundLight end next $cache.pkmn[:ARCEUS].forms.invert["Light"] if foundLight next 0 }, You should be removing the current :OnCreation attribute already there with this one. This proc will check your party for the above defined conditions and return the form that Light is assigned to. You'll want to compile one more time and then your beautiful Light-type Arceus works! Spoiler You can do a lot of fun stuff with procs. If you are so inclined, you can even add a new proc to them (make sure you follow my handling of :OnCreation procs!!!!!) and pass in the Pokemon object itself and make direct edits to it if you so desired. Very similar to how EncounterModifier works, actually. I wouldn't recommend this though as handling the dat files of the games is very finnicky if you don't know what you're doing. Were you not loaded into a save when you compiled? No worries! You can just run the command: $Trainer.pokedex.refreshDex to refresh every dex entry, or: $Trainer.pokedex.updateGenderFormEntries to update specifically forms/genders of existing dex entries. With that, we've covered the basics of modding for the Rebornverse games. Next we will get into bosses, trainers, and extra coding tips! 1 Link to comment Share on other sites More sharing options...
Developers Haru,, Posted November 9, 2023 Author Developers Share Posted November 9, 2023 Defining a Trainer Trainers are the most complicated of the DOH, excluding bosses, while also being the easiest to create. Trainer definitions can be found in trainertext.rb. Trainers are defined as an array of hashes, where each hash has 6 components: :teamid - An array of the trainer name, class, and identifier for matching names/classes. :items - An array of items the trainer has access to in battle :ace - A string displayed in battle when the trainer sends out their last Pokemon :defeat - A string displayed at the end of battle when the trainer is defeated :mons - An array of hashes containing data for each Pokemon. Attributes such as species, level, ability, item, moves, nature, EVs, happiness, and IVs may be changed. IVs will be spread across the board, and an IV value of 32 is used for Trick Room teams to give a 0 speed IV while the rest are 31. :trainereffect - A hash which contains effects similar to bosses. These are explained at the top of Rejuvenation's trainertext.rb so I will not be diving into them here. If you'd like to expand on these I will be adding a new break effect on bosses, which should provide insight as to what you're expected to look for to add a new effect here. Trainers are compiled via the line compileTrainers, and called via pbTrainerBattle or pbDoubleTrainerBattle. Each of their parameters can be found by looking at their definitions. Trainer Classes Classes are what makes a trainer a trainer. They contain all sorts of useful information, and can be found in ttypetext.rb. These do in fact have a symbol identifier, and can be named anything. The following is the list of attributes that can be used: :ID - The number assigned to the trainer's graphic in the Graphics/Characters folder, following the word "trainer" :title - The display name of the trainer class :skill - the level of AI used out of 100, default 30 :moneymult - the multiplier of money earned based on the trainer's highest level Pokemon, default 30 :trainerID - An override of the PokeBattle_Trainer object trainerID. Used exclusively for flavor. :battleBGM - A string containing the name of the battle music for the trainer, found in Audio/BGM :winBGM - A string containing the name of the victory music for the trainer, found in Audio/BGM :player - A boolean based on if the trainer is a playable character. Once again, given the skill you can add anything you want here. Trainer classes are compiled by compileTrainerTypes. 1 Link to comment Share on other sites More sharing options...
Developers Haru,, Posted November 9, 2023 Author Developers Share Posted November 9, 2023 Defining a Boss Bosses are the capstone of our work on Rejuvenation. They're defined in BossInfo.rb and compiled by compileBosses. Once again, like :trainereffects from Trainers above, they are already heavily detailed inside the first boss template, so I will not be explaining them here. I've made a custom boss that will make a little more sense once we get through our custom shield break mechanic. Spoiler :BOSSZAPDOS => { :name => "Zapdos", :entryText => "Zapdos descends from the clouds!", :shieldCount => 1, :immunities => {}, :moninfo => { :species => :ZAPDOS, :level => 100, :moves => [:THUNDER,:ETHEREALTEMPEST,:ROOST,:DISCHARGE], :gender => "F", :nature => :MODEST, :ability => :VOLTABSORB, :iv => 31, :happiness => 255, :ev => [252,252,252,252,252,252] }, :onEntryEffects => { :fieldChange => :ELECTERRAIN, :fieldChangeMessage => "Zapdos's energy courses through the field!" }, :onBreakEffects => { 1 => { :threshold => 0, :animation => :DISCHARGE, :paralyzePlayer => true, :statDropCure => true, :fieldChange => :MOUNTAIN, :fieldChangeMessage => "Zapdos's energy erupts!" } } }, After that you can compile. You'll see a new key there in the break effects, :paralyzePlayer. Let's go ahead an code that in. For our purposes, we'll be looking for the file PokemonBossBattle.rb, and the method pbShieldEffects. To avoid clashing with anything else, we'll be adding our new effect after any stat changes but before any :CustomMethod handling. Quickly, since custom methods aren't explained, you can write some code and throw it in a string, then our break effect handling will run that code. It's a very powerful tool and used for one of our....mechanics. You'll see if you look a little into it but we recommend not doing that! Anyway, Time for our code: Spoiler if onBreakdata[:paralyzePlayer] for i in @battlers next if !i.pbCanParalyze?(false) next if !battler.pbIsOpposing?(i.index) i.pbParalyze(battler) end for i in @party1 i.status = :PARALYSIS if i.status.nil? && !(i.ability == :LIMBER || i.hasType?(:ELECTRIC)) end @scene.pbRefresh pbDisplay("The electricity paralyzed your team!") end This will look through every active battler, check if it can paralyze, check if its your Pokemon, then paralyze them. Then it will iterate through your party and attempt to paralyze those as well. You'll also need to be able to fight these bosses. There's actually two different ways of doing so: through a wild battle and through a trainer. For a wild battle you simply need to call the boss name through the regular call like so: pbWildBattle(:BOSSZAPDOS,1) The level (the number there) does not matter as everything about the Pokemon will be pulled from the boss data. This applies to trainers too. In place of a Pokemon you can simply add in the :boss key where the value is the name of the boss. I strongly recommend putting in basic information there for clarity's sake, though. After that you'd make a call to battle a trainer and it would send out the boss all the same. Spoiler 1 Link to comment Share on other sites More sharing options...
Developers Haru,, Posted November 10, 2023 Author Developers Share Posted November 10, 2023 Miscellaneous Info Here I'll explain a little more on how hashes work and some other features that I'll add to later down the line if I can think of anything. Hashes Hashes are just a super fancy array in essence. They're definitely not a replacement for arrays, as each have their own use, but they are much better for storing large quantities of data as they have keys that point to data rather than simple integers. An example of this would be your hash containing every bit of Pokemon data. Sure, some are fine with remembering all 900+ dex numbers and know that number 492 is Shaymin and 156 is Quilava. But many aren't! I wish there was way to tell the data that I want Quilava data and it gives us Quilava data instead of having to tell it "Give me data entry 156!" That's where hashes come from. They're broken down into two parts: a key and a value. The key can be any value of integer, string, symbol, or even user defined class. However that class must have implemented the hash and eql? functions. The value can be any syntactically correct value: variables, hashes, objects, integers, strings, booleans, etc. If you're trying to create storage for a few values it might be better to use an array as they use less space than a hash. However hashes are much more readable than arrays for bigger cases. Aliasing Aliasing is another very powerful tool in Ruby. It's best to think of it along the lines of overloading a method. Its main use though is hijacking a method to add code before or after the method would usually end. We can set up an alias in two different ways: alias new_name old_method This one is used outside of a class or module for generic methods. An example of such is as follows: def dothis print "I did this" end dothis # Output: I did this alias didthat dothis def dothis didthat print " with aliasing" end dothis # Output: I did this with aliasing The most immediately relevant connection to the games I can make is our achievement system. They all alias various methods to have them process normally, then make changes to achievements in the same method. An example of it running code before hand is a quick little randomizer I had written up on Rejuv V13.5's release: class PokeBattle_Pokemon alias __core_init initialize def initialize(*args) args[0] = $cache.pkmn.keys[rand($cache.pkmn.length)-1] __core_init(*args) end end This will override the initialize method, change the species being passed, and then continue as normal. The other method of aliasing is for modules: module The def self.output puts "Output 1" end self.singleton_class.send(:alias_method, :output1, :output) def self.output puts "Output 2" end end The.output1 The.output # Output: Output 1 # Output 2 Modules work a little differently than classes, so any public methods like these must use the send method to alias them. As shown above, it is not required to used the old method in the new definition and you can even use the old method any time you like, not contained to the class/module you're modifying through alias. Where this gets super handy is for making even more modular mods. Mod authors don't need to combine methods to make a "Compatibility Patch" and instead just need to work around each others methods. The general naming syntax used from Essentials and thus adopted by us is __core_methodName. You'll have seen in some of my later Reborn/Rejuv mods for the last version that I had used __hmd_methodName. In reality it doesn't really matter what you use. Parameters You'll notice above I used a weird parameter for the method signature. *args. To explain this, we'll need to break down method parameters a little bit more. Method parameters will take any value on a 1 to 1 basis, with defined names for each value. But some times, you don't know how many arguments a user will input. That's where we use *args, also known as a splat operator. A splat operator essentially transforms an array into arguments matching the parameters of the method, provided it does not go past the amount of parameters defined. args is just a regular array without the splat operator as well, meaning you can modify it as you would a regular array. To reuse in an aliased method you must reinclude the splat operator. Parameters throughout our code also make use of default values, for when the user does not pass an argument. An easy example of this is our method: def Kernel.pbItemBall(item,quantity=1,pural=nil); ...; end This means our method is expecting anywhere from 1 to 3 arguments. If we give 0, then it will break, if we give 4, then it will break, If we only give the first parameter, then the method will default quantity to 1 and plural to nil. There's also another way of adding in optional parameters, which was added in Ruby 2.0 (original Essentials runs on Ruby 1.8.1): def myMethod(optional: false) puts optional end myMethod # Ouput: false myMethod(true) # Output: true This is more in line with JSON object notation, which you may be more familiar with. 1 Link to comment Share on other sites More sharing options...
Recommended Posts