WCouillard

Star Ocean The Last Hope Bonus Board v1.02

Sep 21st, 2025 (edited)
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*:
  2.  * @plugindesc v1.02 SO4 Bonus Board: Persistent, stacking post-battle effects via certain actions in battle.
  3.  * @author Coolie (William Couillard)
  4.  *
  5.  * @param Empty Icon ID
  6.  * @type number
  7.  * @min 0
  8.  * @desc Icon ID for an empty slot on the Bonus Board
  9.  * @default 16
  10.  *
  11.  * @param Reward 1 Icon ID
  12.  * @type number
  13.  * @min 0
  14.  * @desc Icon ID for Physical KO Bonus
  15.  * @default 32
  16.  *
  17.  * @param Reward 2 Icon ID
  18.  * @type number
  19.  * @min 0
  20.  * @desc Icon ID for Magical KO Bonus
  21.  * @default 33
  22.  *
  23.  * @param Reward 3 Icon ID
  24.  * @type number
  25.  * @min 0
  26.  * @desc Icon ID for One-Hit KO Bonus
  27.  * @default 78
  28.  *
  29.  * @param Reward 4 Icon ID
  30.  * @type number
  31.  * @min 0
  32.  * @desc Icon ID for Critical Hit Bonus
  33.  * @default 77
  34.  *
  35.  * @param Slot Fill Animation ID
  36.  * @type animation
  37.  * @desc Battle animation to play on party when a Bonus Board slot is filled.
  38.  * @default 1
  39.  *
  40.  * @param Reset Animation ID
  41.  * @type animation
  42.  * @desc Battle animation to play on party when the Bonus Board is forcefully reset.
  43.  * @default 1
  44.  *
  45.  * @param Window X
  46.  * @type number
  47.  * @desc X position of the gauge window on the screen. (-1 = centered)
  48.  * @default -1
  49.  *
  50.  * @param Window Y
  51.  * @type number
  52.  * @desc Y position of the gauge window on the screen.
  53.  * @default 0
  54.  *
  55.  * @help
  56.  * -----------------------------------------------------------------------------
  57.  * ABOUT THIS PLUGIN (Star Ocean: The Last Hope Bonus Board)
  58.  * -----------------------------------------------------------------------------
  59.  * The Bonus Board is a feature in Star Ocean: The Last Hope. It is a persistent
  60.  * gauge, always visible during battle. By performing certain actions, the
  61.  * player can fill the gauge for a variety of post-battle effects.
  62.  *
  63.  * -----------------------------------------------------------------------------
  64.  * HOW TO FILL THE BONUS BOARD SLOTS
  65.  * -----------------------------------------------------------------------------
  66.  * Slots will fill for specific KO or hit conditions.
  67.  *  1. Physical KO on an Enemy
  68.  *  2. Magical KO on an Enemy
  69.  *  3. One-Hit KO on an Enemy
  70.  *  4. Critical Hit on an Enemy
  71.  *
  72.  * Bonuses persist across battles, but is reset to empty if:
  73.  *  - Any party member is KO'd (reduced to 0 HP) for any reason.
  74.  *  - Any party member suffers a critical hit from an enemy.
  75.  *
  76.  * -----------------------------------------------------------------------------
  77.  * BONUS BOARD EFFECTS
  78.  * -----------------------------------------------------------------------------
  79.  * After victory, for each filled slot, post-battle effects will activate.
  80.  *
  81.  * - Physical KO:  Heal each living party member for 2% max HP per slot.
  82.  * - Magical KO:   Heal each living party member for 2% max MP per slot.
  83.  * - One-Hit KO:   Increase party's total EXP reward by 10% per slot.
  84.  * - Critical Hit: Increase party's total Gold reward by 10% per slot.
  85.  *
  86.  * -----------------------------------------------------------------------------
  87.  * OTHER FEATURES
  88.  * -----------------------------------------------------------------------------
  89.  * A custom animation will play on the party when a slot is filled, or when the
  90.  * gauge is forcefully reset by KO or critical hit.
  91.  *
  92.  * You can use a blank Animation if you don't want to use this feature of
  93.  * the plugin, but the plugin parameter must be set to an Animation.
  94.  *
  95.  * -----------------------------------------------------------------------------
  96.  * COMMANDS
  97.  * -----------------------------------------------------------------------------
  98.  * The Bonus Board can be manually reset via the following:
  99.  *
  100.  * Plugin Command: ResetBonusBoard
  101.  * Script Call:    $gameSystem.clearBonusBoard();
  102.  *
  103.  * -----------------------------------------------------------------------------
  104.  * TERMS OF USE
  105.  * -----------------------------------------------------------------------------
  106.  * It is OK to use this plugin in both commercial and non-commercial projects.
  107.  *
  108.  * Credit is appreciated, but not required. Happy game making!
  109.  *
  110.  * -----------------------------------------------------------------------------
  111.  * VERSION & UPDATE HISTORY
  112.  * -----------------------------------------------------------------------------
  113.  * > v1.02 : Fixed bug related to HP restoration (incorrectly set to 1%)
  114.  *   > September 22, 2025
  115.  *
  116.  * > v1.01 : Fixed bug related to EXP and Gold gain bonuses
  117.  *   > September 22, 2025
  118.  *
  119.  * > v1.00 : Initial Release
  120.  *   > September 21, 2025
  121.  */
  122.  
  123. (function() {
  124.     var pluginName = document.currentScript.src.match(/([^\/]+)\.js$/)[1];
  125.     var parameters = PluginManager.parameters(pluginName);
  126.     function paramInt(name, def) { return Number(parameters[name] || def); }
  127.     var ICON_EMPTY  = paramInt('Empty Icon ID', 16);
  128.     var ICON_REWARD1 = paramInt('Reward 1 Icon ID', 64);
  129.     var ICON_REWARD2 = paramInt('Reward 2 Icon ID', 65);
  130.     var ICON_REWARD3 = paramInt('Reward 3 Icon ID', 66);
  131.     var ICON_REWARD4 = paramInt('Reward 4 Icon ID', 67);
  132.     var SLOT_FILL_ANIMATION = paramInt('Slot Fill Animation ID', 0);
  133.     var RESET_ANIMATION     = paramInt('Reset Animation ID', 0);
  134.     var WINDOW_X = paramInt('Window X', -1);
  135.     var WINDOW_Y = paramInt('Window Y', 0);
  136.  
  137.     var SLOTS = 15;
  138.     var ICONS_REWARD = [0, ICON_REWARD1, ICON_REWARD2, ICON_REWARD3, ICON_REWARD4];
  139.  
  140.     Game_System.prototype.initBonusBoard = function() {
  141.         if (!this._bonusBoard) {
  142.             this._bonusBoard = [];
  143.             for (var i = 0; i < SLOTS; i++) this._bonusBoard[i] = 0;
  144.         }
  145.     };
  146.     Game_System.prototype.bonusBoard = function() {
  147.         this.initBonusBoard();
  148.         return this._bonusBoard;
  149.     };
  150.     Game_System.prototype.setBonusBoard = function(gauge) {
  151.         this._bonusBoard = gauge.slice();
  152.     };
  153.     Game_System.prototype.addRewardSlot = function(type) {
  154.         if (type < 1 || type > 4) return -1;
  155.         var gauge = this.bonusBoard();
  156.         for (var i = 0; i < SLOTS; i++) {
  157.             if (gauge[i] === 0) {
  158.                 gauge[i] = type;
  159.                 return i;
  160.             }
  161.         }
  162.         return -1;
  163.     };
  164.     Game_System.prototype.countRewardSlots = function(type) {
  165.         return this.bonusBoard().filter(function(x) { return x === type; }).length;
  166.     };
  167.     Game_System.prototype.clearBonusBoard = function() {
  168.         for (var i = 0; i < SLOTS; i++) this._bonusBoard[i] = 0;
  169.     };
  170.  
  171.     BattleManager.initBonusBoard = function() {
  172.         $gameSystem.initBonusBoard();
  173.     };
  174.     BattleManager.bonusBoard = function() {
  175.         return $gameSystem.bonusBoard();
  176.     };
  177.     BattleManager.addRewardSlot = function(type) {
  178.         var slotIndex = $gameSystem.addRewardSlot(type);
  179.         if (slotIndex >= 0) {
  180.             SceneManager._scene && SceneManager._scene._bonusBoardWindow && SceneManager._scene._bonusBoardWindow.refresh();
  181.             if (SLOT_FILL_ANIMATION > 0 && SceneManager._scene && SceneManager._scene.isBattleActive && SceneManager._scene.isBattleActive()) {
  182.                 Scene_Battle.playAnimOnParty(SLOT_FILL_ANIMATION);
  183.             }
  184.         }
  185.     };
  186.     BattleManager.clearBonusBoard = function() {
  187.         $gameSystem.clearBonusBoard();
  188.         SceneManager._scene && SceneManager._scene._bonusBoardWindow && SceneManager._scene._bonusBoardWindow.refresh();
  189.     };
  190.  
  191.     function Window_BonusBoard() { this.initialize.apply(this, arguments); }
  192.     Window_BonusBoard.prototype = Object.create(Window_Base.prototype);
  193.     Window_BonusBoard.prototype.constructor = Window_BonusBoard;
  194.     Window_BonusBoard.prototype.initialize = function() {
  195.         var width = 36 * SLOTS + this.standardPadding() * 2;
  196.         var height = this.fittingHeight(1);
  197.         Window_Base.prototype.initialize.call(this, 0, 0, width, height);
  198.         this.openness = 255;
  199.         this.refresh();
  200.     };
  201.     Window_BonusBoard.prototype.refresh = function() {
  202.         this.contents.clear();
  203.         var gauge = $gameSystem.bonusBoard();
  204.         for (var i = 0; i < SLOTS; i++) {
  205.             var iconIndex = gauge[i] ? ICONS_REWARD[gauge[i]] : ICON_EMPTY;
  206.             this.drawIcon(iconIndex, i * 36, 0);
  207.         }
  208.     };
  209.  
  210.     var _Scene_Battle_createAllWindows = Scene_Battle.prototype.createAllWindows;
  211.     Scene_Battle.prototype.createAllWindows = function() {
  212.         _Scene_Battle_createAllWindows.apply(this, arguments);
  213.         this.createBonusBoardWindow();
  214.     };
  215.     Scene_Battle.prototype.createBonusBoardWindow = function() {
  216.         this._bonusBoardWindow = new Window_BonusBoard();
  217.         if (WINDOW_X >= 0) {
  218.             this._bonusBoardWindow.x = WINDOW_X;
  219.         } else {
  220.             this._bonusBoardWindow.x = (Graphics.boxWidth - this._bonusBoardWindow.width) / 2;
  221.         }
  222.         this._bonusBoardWindow.y = WINDOW_Y;
  223.         this._windowLayer.addChild(this._bonusBoardWindow);
  224.     };
  225.  
  226.     Scene_Battle.playAnimOnParty = function(animId) {
  227.         if (!animId) return;
  228.         var sprites = SceneManager._scene && SceneManager._scene._spriteset ? SceneManager._scene._spriteset.battlerSprites() : [];
  229.         var actors = $gameParty.battleMembers();
  230.         actors.forEach(function(actor) {
  231.             var sprite = sprites.find(function(spr) { return spr._battler === actor; });
  232.             if (sprite) sprite.startAnimation($dataAnimations[animId], false, 0);
  233.         });
  234.     };
  235.     Scene_Battle.prototype.isBattleActive = function() {
  236.         return !!this._spriteset && !!this._bonusBoardWindow;
  237.     };
  238.  
  239.     var _Game_Enemy_initMembers = Game_Enemy.prototype.initMembers;
  240.     Game_Enemy.prototype.initMembers = function() {
  241.         _Game_Enemy_initMembers.apply(this, arguments);
  242.         this._hpBeforeAction = this.hp;
  243.         this._wasAliveBeforeAction = this.isAlive();
  244.     };
  245.     var _BattleManager_startAction = BattleManager.startAction;
  246.     BattleManager.startAction = function() {
  247.         $gameTroop.members().forEach(function(enemy) {
  248.             enemy._hpBeforeAction = enemy.hp;
  249.             enemy._wasAliveBeforeAction = enemy.isAlive();
  250.         });
  251.         _BattleManager_startAction.apply(this, arguments);
  252.     };
  253.  
  254.     var _Game_Action_apply = Game_Action.prototype.apply;
  255.     Game_Action.prototype.apply = function(target) {
  256.         _Game_Action_apply.apply(this, arguments);
  257.         if (target.isEnemy() && $gameParty.inBattle()) {
  258.             var result = target.result();
  259.             if (result && result.critical) {
  260.                 BattleManager.addRewardSlot(4);
  261.             }
  262.         }
  263.         if (target.isActor() && this.subject() && this.subject().isEnemy()) {
  264.             var result = target.result();
  265.             if (result && result.critical && $gameParty.inBattle()) {
  266.                 var anyFilled = $gameSystem.bonusBoard().some(function(x){return x > 0;});
  267.                 if (anyFilled) {
  268.                     BattleManager.clearBonusBoard();
  269.                     if (RESET_ANIMATION > 0 && SceneManager._scene && SceneManager._scene.isBattleActive && SceneManager._scene.isBattleActive()) {
  270.                         Scene_Battle.playAnimOnParty(RESET_ANIMATION);
  271.                     }
  272.                 }
  273.             }
  274.         }
  275.     };
  276.  
  277.     var _Game_Actor_die = Game_Actor.prototype.die;
  278.     Game_Actor.prototype.die = function() {
  279.         var wasAlive = this.isAlive();
  280.         _Game_Actor_die.apply(this, arguments);
  281.         if (wasAlive && !$gameParty.inBattle()) return;
  282.         if (wasAlive && $gameParty.inBattle()) {
  283.             var anyFilled = $gameSystem.bonusBoard().some(function(x){return x > 0;});
  284.             if (anyFilled) {
  285.                 BattleManager.clearBonusBoard();
  286.                 if (RESET_ANIMATION > 0 && SceneManager._scene && SceneManager._scene.isBattleActive && SceneManager._scene.isBattleActive()) {
  287.                     Scene_Battle.playAnimOnParty(RESET_ANIMATION);
  288.                 }
  289.             }
  290.         }
  291.     };
  292.  
  293.     var _Game_Enemy_performCollapse = Game_Enemy.prototype.performCollapse;
  294.     Game_Enemy.prototype.performCollapse = function() {
  295.         if (BattleManager._subject && this._wasAliveBeforeAction) {
  296.             var action = BattleManager._action;
  297.             var isPhysical = action && action.isPhysical();
  298.             var isMagical  = action && action.isMagical();
  299.             var wasFullHP  = this._hpBeforeAction === this.mhp;
  300.             var isOHKO     = wasFullHP;
  301.             if (action && isPhysical) {
  302.                 BattleManager.addRewardSlot(1);
  303.             }
  304.             if (action && isMagical) {
  305.                 BattleManager.addRewardSlot(2);
  306.             }
  307.             if (action && isOHKO) {
  308.                 BattleManager.addRewardSlot(3);
  309.             }
  310.         }
  311.         _Game_Enemy_performCollapse.apply(this, arguments);
  312.     };
  313.  
  314.     var _BattleManager__rewardsExp = BattleManager._rewardsExp;
  315.     BattleManager._rewardsExp = function() {
  316.         var baseExp = $gameTroop.expTotal();
  317.         var bonus = $gameSystem.countRewardSlots(3) * 0.10;
  318.         return Math.floor(baseExp * (1 + bonus));
  319.     };
  320.     var _BattleManager__rewardsGold = BattleManager._rewardsGold;
  321.     BattleManager._rewardsGold = function() {
  322.         var baseGold = $gameTroop.goldTotal();
  323.         var bonus = $gameSystem.countRewardSlots(4) * 0.10;
  324.         return Math.floor(baseGold * (1 + bonus));
  325.     };
  326.  
  327.     BattleManager.gainRewards = function() {
  328.         var count1 = $gameSystem.countRewardSlots(1);
  329.         var count2 = $gameSystem.countRewardSlots(2);
  330.         if (count1 > 0) {
  331.             $gameParty.aliveMembers().forEach(function(actor) {
  332.                 var healAmount = Math.floor(actor.mhp * 0.02 * count1);
  333.                 if (healAmount > 0) actor.gainHp(healAmount);
  334.             });
  335.         }
  336.         if (count2 > 0) {
  337.             $gameParty.aliveMembers().forEach(function(actor) {
  338.                 var healAmount = Math.floor(actor.mmp * 0.02 * count2);
  339.                 if (healAmount > 0) actor.gainMp(healAmount);
  340.             });
  341.         }
  342.         var exp = this._rewardsExp();
  343.         var gold = this._rewardsGold();
  344.         $gameParty.allMembers().forEach(function(actor) {
  345.             actor.gainExp(exp, false);
  346.         });
  347.         $gameParty.gainGold(gold);
  348.         $gameTroop.makeDropItems().forEach(function(item) {
  349.             $gameParty.gainItem(item, 1);
  350.         });
  351.     };
  352.  
  353.     var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  354.     Game_Interpreter.prototype.pluginCommand = function(command, args) {
  355.         _Game_Interpreter_pluginCommand.apply(this, arguments);
  356.         if (command.toLowerCase() === 'resetbonusboard') {
  357.             $gameSystem.clearBonusBoard();
  358.             if (SceneManager._scene && SceneManager._scene._bonusBoardWindow) {
  359.                 SceneManager._scene._bonusBoardWindow.refresh();
  360.             }
  361.         }
  362.     };
  363.  
  364.     var _DataManager_createGameObjects2 = DataManager.createGameObjects;
  365.     DataManager.createGameObjects = function() {
  366.         _DataManager_createGameObjects2.apply(this, arguments);
  367.         $gameSystem.initBonusBoard();
  368.     };
  369. })();
Add Comment
Please, Sign In to add comment