Skill & Element Tracker - Keep track of what the heroes do

Ironskink

Villager
Member
Joined
May 14, 2020
Messages
7
Reaction score
4
First Language
English
Primarily Uses
RMMV
Ahoy! Just picked up RMMV on the recent HumbleBundle sale, and tried making a little plugin to get my feet wet with this newfangled system. I'd like to make a game where the skills used by the main hero changes their class over time - specifically based off of the elemental properties of the skills that they're using and possibly also what they're getting hit with. Step 1 of getting that kind of system set up is to set up some tracking, which brings us to today's plugin!

Not sure if anyone else has already made something like this before - I'm totally new here and didn't look around for an existing solution before whipping something up myself. If it's something that you might find useful, I'd love to know what you would use it for and what kinds of commands or additional information you would like to flesh this out a bit more!

Currently, this plugin tracks the following data for each actor:
  • Count of how many times each skill has been used
  • For each element defined in the game:
    • Count of how many times the actor has used skills of this element
    • Count of how many times the actor has been hit by skills of this element
    • Total HP damage inflicted by this element
    • Total HP damage received from this element
    • Total HP healing given through this element
    • Total HP healing received through this element
    • Total MP spent on skills of this element by the actor
    • Total MP spent by others when targeting the actor with this element
    • Total TP spent by the actor on this element
    • Total TP regained by using skills of this element
    • Total TP spent by others targeting the actor

Some ideas that I had that you might be able to use this kind of information for (*may require extra scripts or plugins to execute):
  • Use to collect player data to find which skills are overused/underused
  • Unlock new skills when the prerequisite skills have been used a certain number of times
  • Set status conditions on heroes if they have been subjected to certain types of elements or attacks too many times between rests
  • Let characters get better at using skills with practice
  • Penalize characters for 'stale moves' if they spam a single action type
  • Create branching class paths based on character tactics
  • Create a nemesis/rival recurring NPC that tries to counter the player's dominant strategy

Not sure what the preferred way to share code here is yet - Here it is behind a spoiler tag, and I've also tried attaching it as a file to this post:

Code:
/* ************************************************************************************************
      SKILL TRACKER - SkillTracker.js
      Collect information about the skills and elements used by and against each hero
************************************************************************************************ */

/*:
 * @plugindesc Automatically collect and save skill usage data.
 * Use for analytics, usage-based skill progression, etc.  This plugin just gives the data, how to use that knowledge is up to you!
 * Usage ideas (*may require extra scripts or plugins to execute):
 *   - Use to collect player data to find which skills are overused/underused
 *   - Unlock new skills when the prerequisite skills have been used a certain number of times
 *   - Set status conditions on heroes if they have been subjected to certain types of elements or attacks too many times between rests
 *   - Let characters get better at using skills with practice
 *   - Penalize characters for 'stale moves' if they spam a single action type
 *   - Create branching class paths based on character tactics
 *   - Create a nemesis/rival recurring NPC that tries to counter the player's dominant strategy
 *
 * @author David Bilsky ("Ironskink")
 *
 * @help This plugin does not provide any commands yet (TODO!)
 *
 * @param skillSummaryId
 * @desc Variable ID where the Skill Summary object is to be saved (or 0 to not save a skill summary).
 * The skill summary object provides the following tracking info for each hero:
 *   skillUseCount: Count of how many times each skill has been used by a hero (or 'undefined' if a skill has never been used), tracked by skill ID
 *   [element name/element id]: Breakdown of each hero's interactions with all of your game's elemental types, tracked by element ID and element name, including:
 *     useCount           # of times the hero used elemental skills of this type
 *     useTotalTP         Total TP spent on skills of this elemental type
 *     useTotalMP         Total MP spent on skills of this elemental type
 *     useTotalDmg        Total damage inflicted on targets using skills of this elemental type
 *     useTotalHeal       Total damage healed by targets when using skills of this elemental type
 *     gainedTotalTP      Total TP gained by using skills of this elemental type
 *     endureCount        # of times the hero has been hit with attacks of this type
 *     endureTotalTP      Total TP spent on skills of this element that target this hero
 *     endureTotalMP      Total MP spent on skills of this element that target this hero
 *     endureTotalDmg     Total amount of elemental damage this hero has endured
 *     endureTotalHeal    Total amount of elemental healing this hero has received
 *
 *
 * @param actionVarId
 * @desc Variable ID where the most recently used action object should be stored (or 0 to not save to game variables)
 * @default 0
 *
 * @param actorVarId
 * @desc Variable ID where the most recent character object (hero or enemy) should be stored (or 0 to not save to game variables)
 * @default 0
 *
 * @param targetsVarId
 * @desc Variable ID where the array of targets for the most recent action should be stored (or 0 to not save to game variables). This will be an array containing Game_Actor and/or Game_Enemy objects
 * @default 0
 *
 *
 */
    //$dataSystem.elemenets
    //$dataSkills[11].damage.elementId
    //action._item._dataClass, action._item._itemId

(function(){
  const params = PluginManager.parameters('SkillTracker');
  const original_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  const original_startAction = Window_BattleLog.prototype.startAction;
  const original_makeDamageValue = Game_Action.prototype.makeDamageValue; //function(target, critical)
  let elementSummary = null;

  Game_Interpreter.prototype.pluginCommand = function(command, args){
    original_pluginCommand.call(this, command, args);
    if(command !== 'SkillTracker'){
      return;
    }

    const cmdName = args[0];
    switch(cmdName){
      case 'get':
        // TODO
        break;
      case 'set':
        // TODO
        break;
      case 'reset':
        // TODO
        break;
      case 'snapshot':
        //TODO
        break;
      default:
        console.error('SkillTracker: Unknown command "' + cmdName +'"');       
    }
  }

  Window_BattleLog.prototype.startAction = function(subject, action, targets){
    const returnVal = original_startAction.call(this, subject, action,targets);
    saveInfoToGameVars(subject, action, targets)
    saveElementSummary(subject, action, targets)

    params.skillSummaryId && $gameVariables.setValue(params.skillSummaryId, elementSummary)
    return returnVal;
  }

  Game_Action.prototype.makeDamageValue = function(target, critical){
    const damage = original_makeDamageValue.call(this, target, critical);
    const subject = this.subject();
    const action = this;
    saveElementDamage(subject, action, target, damage)
    
    return damage;
  }

  function saveInfoToGameVars(subject, action, targets){
    params.actionVarId && $gameVariables.setValue(params.actionVarId, action)
    params.actorVarId && $gameVariables.setValue(params.actorVarId, subject)
    params.targetsVarId && $gameVariables.setValue(params.targetsVarId, targets.map( (t) => t ))
  }
 
  function saveElementSummary(subject, action, targets){
    if(params.skillSummaryId && action._item._dataClass == 'skill'){
      elementSummary = elementSummary || $gameVariables.value(params.skillSummaryId) || {};
      let skill = $dataSkills[action._item._itemId];
      const elementId = skill.damage.elementId;
      const elementName = $dataSystem.elements[elementId] || getGenericName(elementId);
      const actionId = action._item._dataClass + '_' + action._item._itemId;

      if(subject instanceof Game_Actor){
        let summary = elementSummary[subject._name] = elementSummary[subject._actorId] = elementSummary[subject._actorId] || initializeSummary(subject)
        summary[elementName] = summary[elementId] = summary[elementId] || initializeElement(elementId);
        summary[elementId].useCount ++;
        summary[elementId].useTotalTP += skill.tpCost;
        summary[elementId].useTotalMP += skill.mpCost;
        summary[elementId].gainedTotalTP += skill.tpGain;
        summary.skillUseCounts[actionId] = (summary.skillUseCounts[actionId] || 0) + 1;
      }

      for(let t=0; t<targets.length; t++){
        let target = targets[t];
        if(target instanceof Game_Actor){
          let summary = elementSummary[target._actorId] = elementSummary[target._actorId] || initializeSummary(target)
          summary[elementName] = summary[elementId] = summary[elementId] || initializeElement(elementId);
          summary[elementId].endureCount ++;
          summary[elementId].endureTotalTP += skill.tpCost;
          summary[elementId].endureTotalMP += skill.mpCost;
        }
      }
    }
  }

  function saveElementDamage(subject, action, target, damage){
    if(params.skillSummaryId && action._item._dataClass == 'skill'){
      elementSummary = elementSummary || $gameVariables.value(params.skillSummaryId) || {};
      let skill = $dataSkills[action._item._itemId];
      const elementId = skill.damage.elementId;
      const elementName = $dataSystem.elements[elementId] || getGenericName(elementId);

      if(subject instanceof Game_Actor){
        let summary = elementSummary[subject._name] = elementSummary[subject._actorId] = elementSummary[subject._actorId] || initializeSummary(subject)
        summary[elementName] = summary[elementId] = summary[elementId] || initializeElement(elementId);
        if(damage > 0){
          summary[elementId].useTotalDmg += damage;
        }
        else{
          summary[elementId].useTotalHeal -= damage;
        }
        
      }

      if(target instanceof Game_Actor){
        let summary = elementSummary[target._actorId] = elementSummary[target._actorId] || initializeSummary(target)
        summary[elementId] = summary[elementId] || initializeElement(elementId);
        if(damage > 0){
          summary[elementId].endureTotalDmg += damage;
        }
        else{
          summary[elementId].endureTotalHeal -= damage;
        }
      }
    }
  }

  function initializeSummary(actor){
    return {
      actor: actor,
      skillUseCounts: {}
    }
  }
  function initializeElement(elementId){
    return{
      name: $dataSystem.elements[elementId] || getGenericName(elementId),
      useCount: 0,
      useTotalTP: 0,
      useTotalMP: 0,
      useTotalDmg: 0,
      useTotalHeal: 0,
      gainedTotalTP: 0,
      endureCount: 0,
      endureTotalTP: 0,
      endureTotalMP: 0,
      endureTotalDmg: 0,
      endureTotalHeal: 0
    }
  }
  function getGenericName(elementId){
    switch(elementId){
      case -1:
        return 'Normal Attack';
      case 0:
        return 'None';
      default:
        return 'Unnamed-' + elementId
    }
  }
})()

Making nice plugin commands is still on the TODO list - will hopefully get some time to flesh that bit out this weekend or next - so if you're interested in using this tracker and would like certain commands please let me know and I'll try to work 'em in!

Feel free to use this for your projects if it's helpful. I would love to hear what you end up using it for, and if you get to the point where you need to thank the little people I can be credited with as this username.
 

Attachments

Johnboy

Veteran
Veteran
Joined
Jan 13, 2016
Messages
344
Reaction score
124
First Language
English
Primarily Uses
This is an interesting plugin. Also, welcome to the forums. :smile:
 

ShadowDragon

Veteran
Veteran
Joined
Oct 8, 2018
Messages
2,840
Reaction score
999
First Language
Dutch
Primarily Uses
RMMV
while it uses the skills used, inflicted, given dmg, what about the normal attacks?
normal HP dmg dealth, taken?

because I probably only need those (if you can turn ON, OFF the skills for what
you need and dont need.
 

Ironskink

Villager
Member
Joined
May 14, 2020
Messages
7
Reaction score
4
First Language
English
Primarily Uses
RMMV
There seem to be 2 special elements: index -1 is used for 'Normal Attack' and index 0 is used for 'None' (non-elemental), and by default your physical attacks and healing abilities will fall into those two categories, respectively. So yes - it does currently track the amount of physical damage given and taken by each character, and it wouldn't be too hard to add a few extra params to allow you to specify exactly what information you want to collect. This month is pretty busy and isn't affording a lot of time for hobby-projects, but I can try to make a few updates this weekend or next :)
 

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

Latest Threads

Latest Posts

Latest Profile Posts

what if... I recreate Kvatch's oblivion gate in rpg maker. Haha jk....unless?:oops:

My only regret is the portal mirror effect is too subtle to show up in these GIFs. It probably just needs more sparkles. :LZSwink:
Microsoft: Hey, let's waste money advertising the Xbox Series X when nobody has any in stock, we don't seem to be making more, we aren't taking preorders and you can't get on a waiting list. BEST. IDEA. EVER.
So I'm practicing ITC with a spirit box, and decide to try to contact my deceased soulmate. It actually gave me multiple identifiers. Me, still a bit skeptical, asked aloud "Fine but does he still love me?" and the box spoke and printed the word "Forever" at the same time. Been a mess of tears since. :kaocry:
Been scratching away at my game and making progress, but just had a revelation. I'm working in full screen and adjusting all my pictures accordingly, but will they resize if someone's screen is smaller?? I hope this doesn't turn out to be a problem later.

Forum statistics

Threads
105,635
Messages
1,015,162
Members
137,304
Latest member
KenSoulslayer
Top