Simple In-Battle gained Exp Popup

Discussion in 'JS Plugin Requests' started by SaucissonSec, Jun 15, 2019.

  1. SaucissonSec

    SaucissonSec ᕙ໒( ˵ ಠ ╭͜ʖ╮ ಠೃ ˵ )७ᕗ Veteran

    Messages:
    38
    Likes Received:
    6
    Location:
    Frenchie France
    First Language:
    Frenglish
    Primarily Uses:
    RMMV
    Hello everyone!
    I've been tinkering around with my Exp system, wanting to create a level difference based exp gaining system like the one we can see in CrossCode for example.
    Needing help to set this up, I asked for help on the forum:
    Here's a link to the related thread: https://forums.rpgmakerweb.com/index.php?threads/exp-gain-tweaking-help-needed.109998/
    (Credits to @mathmaster74 for his help and advice on this case)

    Here's what mathmaster74 recommended me to do:
    After testing out all the script and formulas mathmaster54 recommended, everything technically works. My only problem is how to display gained Exp to the player, as there is no way to inform how much exp he gained from killing an enemy (Aftermath plugins like Yanfly's won't display gained Exp as it isn't gained conventionally.)

    Ideally, gained exp could be displayed right when it is obtained, like a popup on top of the defeated enemy EDIT: On top of each actor! This will allow the player to check instantly how efficient his exp farming is, while granting him satisfaction from seeing his work on killing that enemy paying out.

    That is my request: I need a plugin that can do that, which is displaying a popup saying " + [ExPts] Exp" once an enemy is defeated. Simple, yet impossible for me with close to 0 knowledge in creating JS plugins :/

    Also, as a bonus, it would be perfect if we could have similar popups if an actor levels up in combat, like a fancy popup showing up on their portrait (I'm using front view battle system)

    Also, I would feel bad if @mathmaster74 's work was all for nothing, i wanted to show him we can make a working system from his work and ideas :)

    Thanks in advance for your help!
     
    Last edited: Jun 15, 2019
    #1
    mathmaster74 likes this.
  2. Magnus0808

    Magnus0808 Software Developer Veteran

    Messages:
    111
    Likes Received:
    102
    First Language:
    Danish
    Primarily Uses:
    RMMV
    Hi, I do not know why @mathmaster74 gave you a version of the script part which is plain out flawed. @caethyril rewrote it beautifully like this:
    Code:
    $gameParty.aliveMembers().forEach(function(actor) {
      var enemy = $gameTroop.members()[0];  // troop leader (change the 0 the place in troop the enemy has)
      var diffL = (actor.level + 6 - enemy.level).clamp(0, 11); // (If you do not use e.g. YEP Enemy Level. Then exhange enemy.level with whatever level you think the enemy should have)
      var multL = [5, 4.3, 3.7, 3.2, 2.8, 2.4, 2, 1.8, 1.6, 1.4, 1.2, 1][diffL];
      var exPts = Math.round(80 * multL / $gameParty.aliveMembers().length);
      actor.gainExp(exPts);
    });
    The current version of the script you uses $gameActors.actor(i) which I pointed out in the original thread does NOT equal the party members. Currently you always give "Harold" exp even if he is not in the party and the ones who are in the party might not even get any exp at all. Furthermore, the for-loop itself is also wrong as it should have been for (var i = 0; i < $gameParty.aliveMembers().length; i++) which was also shown in the original thread this was developed in.

    In that thread I also developed a plugin that handles individual exp at the end of battle that works with YEP Aftermath as an extension to YEP Enemy Levels. (however, it might still be flawed as the original requester have some flawes with it I haven't been able to recreate)

    However, if you want a system that gives exp immediately after enemy deaths I would recommend writting it all into a plugin to save yourself some time in the future.
     
    #2
    SaucissonSec and caethyril like this.
  3. Magnus0808

    Magnus0808 Software Developer Veteran

    Messages:
    111
    Likes Received:
    102
    First Language:
    Danish
    Primarily Uses:
    RMMV
    Just for the fun of it I made you a new plugin. This should take care of your needs. It handles the exp gain and it even displays the gin above each actors head as requested. With this you do not need to setup a battle page for each enemy in a troop. Instead you can set the exp formular once in the plugin parameters or you can set it as a meta-tag for specific enemies.

    Overall it should behave like @mathmaster74 's approach just without as much hassle :)
    Oh yeah, I even made it compatible with Yanfly's EnemyLevels, VictoryAftermath, and AftermathLevelUp. However, I haven't checked for anything else.

    If you encounter any problems with it feel free to message me :D

    Code:
    //=============================================================================
    // Enemy Death Exp
    // MRP_EnemyDeathExp.js
    // By Magnus0808 || Magnus Rubin Peterson
    //=============================================================================
    
    /*:
     * @plugindesc Individually Experiance on Enemy Deaths
     * @author Magnus0808
     *
     * @help This plugin changes the way exp is calculated.
     * It also displays the exp gain above the actors head. The code for this is
     * heavily inspired by HimeWorks' Battle Action Exp -> Battle Exp Popup
     *
     * This plugin is intended to be used with a enemy level plugin such as Yanfly's
     * Enemy Levels. However, this is not necesarry. In case you use Yanfly's Enemy
     * Levels, then this plugin will overwrite the exp formular!
     *
     * You can specify the exp formular using the meta tag: <expFormular:FORMULAR>
     *      E.g. <expFormular:
     *               value = 10 * actor.level - this.level;
     *           >
     * value is the exp given.
     *
     * If you do not have an enemy level plugin then you can specify the enemy level
     * using the meta tag: <level:ENEMY_LEVEL>
     *      E.g <level:10>
     *
     *
     * @param Default Exp Formular
     * @type note
     * @default "var diffL = (actor.level + 6 - this.level).clamp(0, 11);\nvar multL = [5, 4.3, 3.7, 3.2, 2.8, 2.4, 2, 1.8, 1.6, 1.4, 1.2, 1][diffL];\nvalue = Math.round(80 * multL);"
     *
     * @param Distribute Exp
     * @type boolean
     * @desc If true then the exp will be distributed between the party instead of everyone getting it.
     * @default true
     */
    
     var Imported = Imported || {};
     Imported.MRP_EnemyDeathExp = true;
     
     var MRP = MRP || {};
     MRP.EnemyDeathExp = MRP.EnemyDeathExp || {};
     
    (function() {
        
        MRP.EnemyDeathExp.parameters = PluginManager.parameters('MRP_EnemyDeathExp');;
        MRP.EnemyDeathExp.defaultExpFormular = JSON.parse(String(MRP.EnemyDeathExp.parameters['Default Exp Formular']));
        MRP.EnemyDeathExp.distributeExp = (String(MRP.EnemyDeathExp.parameters['Distribute Exp']) == "true");
        
        //-----------------------------------------------------------------------------
        // BattleManager
        //
        // Changes to the BattleManager
        
        BattleManager.gainExp = function(enemy) {
            console.log("BM GAIN EXP");
            if(!enemy) return 0;
            $gameParty.allMembers().forEach(function(actor) {
                var exp = MRP.EnemyDeathExp.distributeExp ? Math.ceil(enemy.exp(actor) / $gameParty.aliveMembers().length) : enemy.exp(actor);
                BattleManager._gainedTotalExp += exp;
                actor.gainExp(exp);
            });
        };
        
        BattleManager.displayExp = function() {
            var exp = this._gainedTotalExp;
            if (exp > 0) {
                var text = TextManager.obtainExp.format(exp, TextManager.exp);
                $gameMessage.add('\\.' + text);
            }
        };
        
        MRP.EnemyDeathExp.BattleManager_initMembers = BattleManager.initMembers;
        BattleManager.initMembers = function() {
            MRP.EnemyDeathExp.BattleManager_initMembers.call(this);
            this._gainedTotalExp = 0;
        };
        
        // Compatibility stuff
        if(Imported.YEP_VictoryAftermath) {
            MRP.EnemyDeathExp.BattleManager_startBattle = BattleManager.startBattle;
            BattleManager.startBattle = function() {
                MRP.EnemyDeathExp.BattleManager_startBattle.call(this);
                if(Imported.YEP_X_AftermathLevelUp) this.prepareVictoryPreLevel();
                $gameParty.allMembers().forEach(function(actor) {
                    actor._preVictoryExp = actor.currentExp();
                    actor._preVictoryLv = actor._level;
                }, this);
            };
            
            BattleManager.prepareVictoryInfo = function() {
                $gameParty.allMembers().forEach(function(actor) {
                    ImageManager.loadFace(actor.faceName());
                    actor._victoryPhase = true;
                    actor._victorySkills = [];
                }, this);
                this.gainRewards();
                $gameParty.allMembers().forEach(function(actor) {
                    actor._expGained = actor.currentExp() - actor._preVictoryExp;
                    actor._postVictoryLv = actor._level;
                }, this);
                if(Imported.YEP_X_AftermathLevelUp) this.prepareVictoryPostLevel();
            };
        }
        
        //-----------------------------------------------------------------------------
        // Game_Enemy
        //
        // Changes to Game_Enemy   
        
        Object.defineProperty(Game_Enemy.prototype, 'level', {
            get: function() {
                return this._level;
            },
            configurable: true
        });
        
        MRP.EnemyDeathExp.Game_Enemy_die = Game_Enemy.prototype.die;
        Game_Enemy.prototype.die = function() {
            MRP.EnemyDeathExp.Game_Enemy_die.call(this);
            BattleManager.gainExp(this);
        };
    
        MRP.EnemyDeathExp.Game_Enemy_setup = Game_Enemy.prototype.setup;
        Game_Enemy.prototype.setup = function(enemyId, x, y) {
            MRP.EnemyDeathExp.Game_Enemy_setup.call(this, enemyId, x, y);
            this._level = this._level || eval(this.enemy().meta.level || 1);
        };
        
        Game_Enemy.prototype.exp = function(actor) {
            if(!actor) return 0;       
            var value = 0;
            var formular = this.enemy().meta.expFormular || MRP.EnemyDeathExp.defaultExpFormular;
            value = eval(formular)
            return value;
        };
        
        //-----------------------------------------------------------------------------
        // Game_Actor
        //
        // Changes to Game_Actor
        
        MRP.EnemyDeathExp.Game_Actor_changeExp = Game_Actor.prototype.changeExp;
        Game_Actor.prototype.changeExp = function(exp, show) {
            var gainedExp = exp - this.currentExp();
            this._expGain = gainedExp;
            MRP.EnemyDeathExp.Game_Actor_changeExp.call(this, exp, show);
        };   
        
        Game_Actor.prototype.gainExp = function(exp) {
            console.log("GAINED EXP")
            var newExp = this.currentExp() + Math.round(exp * this.finalExpRate());
            this.changeExp(newExp, this.shouldDisplayLevelUp());
        };
        
        //-----------------------------------------------------------------------------
        // Sprite_Actor
        //
        // Changes to Sprite_Actor
        
        MRP.EnemyDeathExp.Sprite_Actor_initMembers = Sprite_Actor.prototype.initMembers;
        Sprite_Actor.prototype.initMembers = function() {
            MRP.EnemyDeathExp.Sprite_Actor_initMembers.call(this);
            this.createExpSprite();
        };
    
        Sprite_Actor.prototype.createExpSprite = function() {
            this._expSprite = new Sprite_ExpGain();
            this._expSprite.x = this.x + this.damageOffsetX() - 20;
            this._expSprite.y = this.y + this.damageOffsetY() - 140;
            this.addChild(this._expSprite);
        };
        
        MRP.EnemyDeathExp.Sprite_Actor_setBattler = Sprite_Actor.prototype.setBattler;
        Sprite_Actor.prototype.setBattler = function(battler) {
            var changed = (battler !== this._actor);
            if (changed) {
                this._expSprite.setup(battler, this._expSprite.x, this._expSprite.y);
            }
            MRP.EnemyDeathExp.Sprite_Actor_setBattler.call(this, battler);
        };
        
        MRP.EnemyDeathExp.Sprite_Actor_update = Sprite_Actor.prototype.update;
        Sprite_Actor.prototype.update = function() {
            MRP.EnemyDeathExp.Sprite_Actor_update.call(this);
            if (this._actor && this._actor.isSpriteVisible()) {
                this._expSprite.update();
            }
        }
        
        
        //-----------------------------------------------------------------------------
        // Sprite_ExpGain
        //
        // The sprite that shows the exp gained.
        
        function Sprite_ExpGain() {
            this.initialize.apply(this, arguments);
        }
        Sprite_ExpGain.prototype = Object.create(Sprite_Base.prototype);
        Sprite_ExpGain.prototype.constructor = Sprite_ExpGain;
        
        Sprite_ExpGain.prototype.initialize = function() {
            Sprite_Base.prototype.initialize.call(this);
            this._duration = 0;
            
        };
        
        Sprite_ExpGain.prototype.setup = function(battler, x, y) {
            this.originalX = x;
            this.originalY = y;
            this._battler = battler;
            this.bitmap = new Bitmap(100, 200);
            this.bitmap.textColor = "rgb(0, 255, 0)";
            this.bitmap.fontSize = 20;
        };
        
        Sprite_ExpGain.prototype.play = function(exp) {
            this._battler._expGain = 0;
            this.y = this.originalY;
            this.bitmap.clear();
            this.bitmap.drawText(exp + TextManager.expA, 0, 0, 100, 200, 'center');
            this._duration = 90;
        }
        
        Sprite_ExpGain.prototype.update = function() {   
            if (this._duration > 30) {
              this.y -= 0.5;
            }
            if (this._duration > 0) {
                this.show();
                this._duration -= 1
            }else {
                this.hide();
                if(this._battler && this._battler._expGain > 0) this.play(this._battler._expGain);
            }
        };
        
        Sprite_ExpGain.prototype.isPlaying = function() {
            return this._duration > 0;
        };
    
    })();
     

    Attached Files:

    #3
    zerobeat032, SoSick. and SaucissonSec like this.
  4. SaucissonSec

    SaucissonSec ᕙ໒( ˵ ಠ ╭͜ʖ╮ ಠೃ ˵ )७ᕗ Veteran

    Messages:
    38
    Likes Received:
    6
    Location:
    Frenchie France
    First Language:
    Frenglish
    Primarily Uses:
    RMMV
    Hey @Magnus0808 !
    Thank you for your help! You really made my day :)
    I'll check that out and keep you updated.
     
    #4
  5. mathmaster74

    mathmaster74 just...John Veteran

    Messages:
    285
    Likes Received:
    188
    Location:
    Sheboygan, WI USA
    First Language:
    English
    Primarily Uses:
    RMMV
    @Magnus0808 I gave them what I had working at the time, having failed to comprehend the issue about the discrepancy. I also gave them the link to the thread where you had supplied a plugin. Looking back at that thread, it seemed like your plugin was having some issues, so I supplied what I had as an alternative that seemed to be working for me at the time.

    I have since come to comprehend the issue about this discrepancy, and amended the process to use a variable that references by actor ID and only progresses through the lineup of length = alive members if the actor referenced was indeed alive, issuing exp to the actor only if the actor referenced is alive and until all alive actors have been issued exp...so...fixed. :smile:

    o_O

    You're saying "while 0 < length" is different from "while 1 <= length"? Actually...they both say the same thing...iterate "length" amount of times. There's nothing wrong with the values I'm using. My tests show valid exp awards for every enemy killed going to the proper actor for the proper level differential...and yes...the routine now awards exp for every enemy...even the last one defeated in battle.

    In short, if your plugin has been fixed to do what the OP of both threads have requested...that's great. Please don't criticize me for trying to help people with what solutions I am able to offer. Your profile says you are a software developer. By that dint alone, but given that you know how to make plugins as well, I will concede you know more than I concerning compatibility with existing plugins and the JavaScript on which MV runs. That being said, my solution requires no dependency on plugins...not even the need to be one itself. I think that ingenuity deserves some credit.

    I'm not surprised if @caethyril has a more eloquent solution than I came up with for the discrepancy. You are both well ahead of my learning curve with JavaScript and/or its applications in MV. That being said, my latest version of my solution can be found in my last post here:

    https://forums.rpgmakerweb.com/index.php?threads/exp-gain-tweaking-help-needed.109998/#post-977386

    Please check it out. I think if we all offer our thoughts on solutions and look at each others' thoughts to work together, we can come up with something superior to what any of us does alone. If you look back through my posts in these threads, you can see how I have come from not being able to solve any of this, to being where I am now. That kind of growth should be encouraged and commended, not condemned for having produced flawed results that were shared as hopeful solutions along the way. No one is perfect. I understand and can appreciate if you don't care for the avenue I took to solve the problem. I had to do what I could with what I have. I have no experience writing plugins. If this problem had been my own, then my solution is how I would have solved it myself. You have different tools that are more suited to the task in your toolbox. That's great! Thank you for your significant contribution in giving these OPs the solution they deserve...which is beyond my means (at present). Now...let's finish cracking this thing so we have a ready solution for the next similar request, because my guess is...they're coming. :eek:
     
    #5
  6. Magnus0808

    Magnus0808 Software Developer Veteran

    Messages:
    111
    Likes Received:
    102
    First Language:
    Danish
    Primarily Uses:
    RMMV
    @mathmaster74 I am sorry if you feel like I condemned your effort. That was not my intention at all :)

    Yes and no. It is true that it iterates the same amount of times, however the 'i' itself represents two different things. $gameParty.aliveMembers() returns an array which is zero-based. So if we want to itterate over it we should start at 0 not 1.
    This might actually have made you realise your mistake with $gameActors.actor(i) as the actor database is one-based. This is also part of the reason why you didn't see any errors in your tests as it would unfortunely return the correct actor if you use the first actors in the database.
    However, true if your only purpose is to itterate a number of times then it doesn't really matter and is more a question of "best practices". :)
     
    #6
    mathmaster74 likes this.
  7. SaucissonSec

    SaucissonSec ᕙ໒( ˵ ಠ ╭͜ʖ╮ ಠೃ ˵ )७ᕗ Veteran

    Messages:
    38
    Likes Received:
    6
    Location:
    Frenchie France
    First Language:
    Frenglish
    Primarily Uses:
    RMMV
    Thank you so much to both of you @mathmaster74 and @Magnus0808 , from all the testing I've done, the plugin works perfectly so far.
    From the bottom of my heart, thanks for spending time to find a solution to my problem.
    I wish you the best for the future,
    Cheers!
    A happy game designer.
     
    #7

Share This Page