Simple In-Battle gained Exp Popup

SaucissonSec

ᕙ໒( ˵ ಠ ╭͜ʖ╮ ಠೃ ˵ )७ᕗ
Veteran
Joined
Feb 17, 2018
Messages
38
Reaction score
6
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:
Set up a battle event page for each enemy in the troop. Set the condition to Enemy HP (X) <= 0%, where X is the EnemyID you set this page up for. Then enter the following Event commands:
◆Script:for (var i = 1; i <= $gameParty.aliveMembers().length; i++) { //Sets a loop to award Exp to each alive party member.
: : var Difference = ($gameActors.actor(i).level + 5).clamp(0, 11); //See below for full explanation.
: : var Multiplier = [5, 4.3, 3.7, 3.2, 2.8, 2.4, 2, 1.8, 1.6, 1.4, 1.2, 1][Difference]; //Multiplier amounts can be changed and/or added to.
: : var ExPts = 80 * Multiplier / $gameParty.aliveMembers().length; //80 is the amount of Base Exp enemy is worth. Change to suit.
: : $gameActors.actor(i).gainExp(ExPts); //Awards proper Exp per formula each time an enemy dies up until the last one.
: :};
◆Control Variables:#0001 EnemiesDefeated += 1 //Keeps count of enemies defeated.
◆If:EnemiesDefeated = 2 //The value here should be the number of enemies minus 1. Condition runs when only 1 enemy is left.
◆Change Enemy State:Entire Troop, + Immortal //"Entire Troop" at this point is the last enemy.

:End
◆If:#1 Bat is affected by Immortal //This was page #1 in my example. The enemy denoted here should match enemy the page is made for.
◆Change Enemy State:Entire Troop, - Immortal //Allows battle to end now that last enemy is defeated and Exp is awarded for it.

:End

Difference was set up based on an example provided by a user who originally asked for the script. They had set up tiers for level differences as follows: actor - enemy = -6, actor - enemy = -5, actor - enemy = -4, ..., actor - enemy = 4, and actor - enemy > 4, so that there were 11 differences in level that earned unique multipliers. Since their differences started at -6, a modifier of +6 was applied to compute difference. Since the var Difference returned a NaN error if the enemy level was referenced globally, I just plugged in the actual enemy level (something you should be able to decide upon if you're making each page for the target enemy) as a minus, and then combined with the modifier of +6 (because the clamp starts at 0...so shift -6 up to 0, -5 up to 1, etc.) In this example, the enemy is level 1, so actor - enemy + modifier = actor - 1 + 6, which reduces to actor + 5.

Multiplier was also set up according to the specific original request. If they had wanted 12 level differences with unique multipliers, that's easily added in by giving one more value in the [Multiplier list] and setting upper bound of clamp to 12.
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:

Magnus0808

Software Developer
Veteran
Joined
Feb 2, 2019
Messages
147
Reaction score
166
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.
 

Magnus0808

Software Developer
Veteran
Joined
Feb 2, 2019
Messages
147
Reaction score
166
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;
    };

})();
 

Attachments

SaucissonSec

ᕙ໒( ˵ ಠ ╭͜ʖ╮ ಠೃ ˵ )७ᕗ
Veteran
Joined
Feb 17, 2018
Messages
38
Reaction score
6
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.
 

mathmaster74

just...John
Veteran
Joined
Jun 12, 2016
Messages
285
Reaction score
193
First Language
English
Primarily Uses
RMMV
I do not know why @mathmaster74 gave you a version of the script part which is plain out flawed.
@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.

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.
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:

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.
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:
 

Magnus0808

Software Developer
Veteran
Joined
Feb 2, 2019
Messages
147
Reaction score
166
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 :)

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.
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". :)
 

SaucissonSec

ᕙ໒( ˵ ಠ ╭͜ʖ╮ ಠೃ ˵ )७ᕗ
Veteran
Joined
Feb 17, 2018
Messages
38
Reaction score
6
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.
 

Users Who Are Viewing This Thread (Users: 0, Guests: 1)

Latest Threads

Latest Profile Posts

Don't forget, aspiring writers: Personality isn't what your characters do, it is WHY they do it.
Hello! I would like to know if there are any pluggings or any way to customize how battles look?
I was thinking that when you start the battle for it to appear the eyes of your characters and opponents sorta like Ace Attorney.
Sadly I don't know how that would be possible so I would be needing help! If you can help me in any way I would really apreciate it!
The biggest debate we need to complete on which is better, Waffles or Pancakes?
rux
How is it going? :D
Day 9 of giveaways! 8 prizes today :D

Forum statistics

Threads
106,049
Messages
1,018,546
Members
137,835
Latest member
yetisteven
Top