Iavra

Iavra Achievement - Core

Dec 1st, 2015 (edited)
826
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*:
  2.  * @plugindesc Adds global achievements to the game, which are tied to switches/variables.
  3.  * <Iavra Achievement Core>
  4.  * @author Iavra
  5.  *
  6.  * @param Configuration
  7.  * @desc Name of an external configuration file to load at game start. Default: data/achievements.json
  8.  * @default data/achievements.json
  9.  *
  10.  * @param Update Interval
  11.  * @desc How often the plugin should check for completed achievements. 0 or lower disables automatic checking. Default: 1
  12.  * @default 1
  13.  *
  14.  * @param Plugin Command
  15.  * @desc Name of the plugin command, that can be used to manually reset or complete achievements. Default: Achievement
  16.  * @default Achievement
  17.  *
  18.  * @help
  19.  * To add achievements to your game, create a file name "achievements.json" in the "data" folder of your game. You can use
  20.  * a different path, as long as you modify the plugin parameter "Configuration" accordingly. Each achievement consists of
  21.  * multiple properties:
  22.  *
  23.  * id           A unique id, that may not contain whitespaces. Used to identify the achievement during saving an loading
  24.  *              and for script calls / plugin commands.
  25.  * title        Title of the plugin, that may be displayed in a menu or notification popup.
  26.  * description  Description of the plugin, that may be displayed in a menu or notification popup.
  27.  * icon         Index of an icon to be shown for this achievement.
  28.  * secret       This may cause an achievement to be hidden from a menu, as long as it hasn't been completed.
  29.  * trigger      This is an object containing multiple properties. It always contains a "type" value, that indicates, how
  30.  *              the achievement can be completed. Depending on the type, it may or may not contain additional parameters.
  31.  *
  32.  * This is a sample file, containing one of each achievement types:
  33.  *
  34.  * [
  35.  *     {
  36.  *         "id":"simpleAchievement",
  37.  *         "title":"Simple Achievement",
  38.  *         "description":"This achievement can only be completed via a direct script call or plugin command.",
  39.  *         "icon":1,
  40.  *         "secret":true,
  41.  *         "trigger":{
  42.  *             "type":"none"
  43.  *         }
  44.  *     },
  45.  *     {
  46.  *         "id":"switchAchievement",
  47.  *         "title":"Switch Achievement",
  48.  *         "description":"This achievement automatically gets completed, once the given switch is set to ON.",
  49.  *         "icon":2,
  50.  *         "secret":true,
  51.  *         "trigger":{
  52.  *             "type":"switch",
  53.  *             "switchId":1
  54.  *         }
  55.  *     },
  56.  *     {
  57.  *         "id":"variableAchievement",
  58.  *         "title":"Variable Achievement",
  59.  *         "description":"This achievement automatically gets completed, once the given variable reaches the given value.",
  60.  *         "icon":3,
  61.  *         "secret":false,
  62.  *         "trigger":{
  63.  *             "type":"variable",
  64.  *             "variableId":1,
  65.  *             "variableValue":100
  66.  *         }
  67.  *     }
  68.  * ]
  69.  *
  70.  * Additional achievement types can be added by other plugins. For instructions on how to do this, take a look at the
  71.  * variable "IAVRA.ACHIEVEMENT.triggerMappings", which has been made public.
  72.  *
  73.  * By default, achievement progress is checked every frame. If this causes lag in your game (for example, if you have a lot
  74.  * of achievements or generally a lot of stuff going on), you can increase the "Update Interval" plugin parameter to have
  75.  * the update function only being called every X frames. If you specify a value of 0 or lower, automatic checking will be
  76.  * disabled and you'll need to manually do this via script call or plugin command.
  77.  *
  78.  * Once an achievement has been completed, its state will be stored in a global savefile, that's shared among all games.
  79.  * It doesn't matter, if the condition is dropped afterwards (for example, if the given switch is set to OFF, again), as it
  80.  * will stay completed from now on, until manually reset.
  81.  *
  82.  * If you want to manually reset or complete achievements, you can use the following script calls (note, that achievements
  83.  * may immediately complete again after being reset, if their trigger condition is being met):
  84.  *
  85.  * IAVRA.ACHIEVEMENT.achievements();    Returns an array containing all achievements.
  86.  * IAVRA.ACHIEVEMENT.check();           Checks all achievements for completion.
  87.  * IAVRA.ACHIEVEMENT.reset(id);         Resets the achievement with the given id.
  88.  * IAVRA.ACHIEMEMENT.resetAll();        Resets all achievements.
  89.  * IAVRA.ACHIEVEMENT.complete(id);      Completes the achievement with the given id.
  90.  * IAVRA.ACHIEVEMENT.completeAll();     Completes all achievements.
  91.  * IAVRA.ACHIEVEMENT.isCompleted(id);   Returns true, if the given achievement has been completed.
  92.  *
  93.  * If you want to use plugin commands, instead, you can do so. This assumes, that the plugin parameter "Plugin Command" is
  94.  * set to its default value:
  95.  *
  96.  * Achievement reset <id>               Resets the achievement with the given id.
  97.  * Achievement reset                    Resets all achievements.
  98.  * Achievement complete <id>            Completes the achievement with the given id.
  99.  * Achievement complete                 Completes all achievements.
  100.  * Achievement check                    Checks all achievements for completion.
  101.  */
  102.  
  103. var Imported = Imported || {};
  104. Imported.iavra_achievement_core = true;
  105.  
  106. //=============================================================================
  107. // namespace IAVRA
  108. //=============================================================================
  109.  
  110. var IAVRA = IAVRA || {};
  111.  
  112. (function() {
  113.     "use strict";
  114.    
  115.     /**
  116.      * Load plugin parameters independently from the plugin's file name.
  117.      */
  118.     var _params = $plugins.filter(function(p) { return p.description.contains('<Iavra Achievement Core>'); })[0].parameters;
  119.     var _param_configuration = _params['Configuration'];
  120.     var _param_interval = Math.max(0, parseInt(_params['Update Interval']) || 0);
  121.     var _param_pluginCommand = _params['Plugin Command'];
  122.    
  123.     /**
  124.      * String used to mark the achievement savefile.
  125.      */
  126.     var _savefileId = '_iavra_achievement';
  127.    
  128.     /**
  129.      * Stores all achievements.
  130.      */
  131.     var _achievements;
  132.    
  133.     /**
  134.      * Indicates, if achievements are currently being saved.
  135.      */
  136.     var _isSaving = false;
  137.    
  138.     /**
  139.      * Loads the given file and executes a callback after a successful load. If the file can't be loaded, throws an error.
  140.      */
  141.     var _loadFile = function(url, callback) {
  142.         var request = new XMLHttpRequest();
  143.         request.open('GET', _param_configuration);
  144.         request.overrideMimeType('application/json');
  145.         request.onload = function() { callback(JSON.parse(request.responseText)); }
  146.         request.onerror = function() { throw new Error('There was an error loading the file ' + url); }
  147.         request.send();
  148.     };
  149.    
  150.     /**
  151.      * Creates achievements from the given data and loads their current state, afterwards.
  152.      */
  153.     var _create = function(data) {
  154.         var temp = [], entry, cls;
  155.         for(var i = 0, max = data.length; i < max; ++i) {
  156.             entry = data[i], cls = IAVRA.ACHIEVEMENT.triggerMappings[entry.trigger.type];
  157.             if(cls) { temp.push(new cls(entry)); }
  158.         }
  159.         _achievements = temp;
  160.         _load();
  161.     };
  162.    
  163.     /**
  164.      * Calls the test function, if the test interval has been met.
  165.      */
  166.     var _update = function() {
  167.         if(Graphics.frameCount % _param_interval === 0) { _test(); };
  168.     };
  169.    
  170.     /**
  171.      * Updates all achievement, which causes them to mark as completed, if the trigger condition has been met.
  172.      */
  173.     var _test = function() {
  174.         for(var i = 0, max = _achievements.length; i < max; ++i) { _achievements[i].update(); }
  175.     };
  176.    
  177.     /**
  178.      * Loads the current state of all achievements from the save file. We are using the private variable "_completed"
  179.      * directly, because otherwise we would trigger a save every time we update an achievement.
  180.      */
  181.     var _load = function() {
  182.         var loaded = StorageManager.exists(_savefileId) ? JSON.parse(StorageManager.load(_savefileId)) : [];
  183.         var achievements = _achievements, achievement;
  184.         for(var i = 0, max = achievements.length; i < max; ++i) {
  185.             if(loaded.contains((achievement = achievements[i])._id)) { achievement._completed = true; }
  186.         }
  187.     };
  188.    
  189.     /**
  190.      * Stores the current state of all achievements in the save file. This is done asynchronously, so the game flow won't
  191.      * suddently be interrupted by the save. If another save is triggered during the meantime, it gets queued, so there
  192.      * is only one save process at any given time.
  193.      */
  194.     var _save = function() {
  195.         if(_isSaving) { setTimeout(_save, 5); return; }
  196.         _isSaving = true;
  197.         setTimeout(function() {
  198.             var data = [], achievements = _achievements, achievement;
  199.             for(var i = 0, max = achievements.length; i < max; ++i) {
  200.                 if((achievement = achievements[i])._completed) { data.push(achievement._id); }
  201.             }
  202.             StorageManager.save(_savefileId, JSON.stringify(data));
  203.             _isSaving = false;
  204.         }, 0);
  205.     };
  206.    
  207.     /**
  208.      * Sets a single achievement's completed variable to a given value, if it's different from its current state.
  209.      */
  210.     var _setSingle = function(id, value) {
  211.         var achievements = _achievements, achievement;
  212.         for(var i = 0, max = achievements.length; i < max; ++i) {
  213.             if((achievement = achievements[i]).id === id && achievement.completed != value) { achievement.completed = value; }
  214.         }
  215.     };
  216.    
  217.     /**
  218.      * Sets all achievements' completed variable to a given value.
  219.      */
  220.     var _setAll = function(value) {
  221.         var achievements = _achievements, achievement;
  222.         for(var i = 0, max = achievements.length; i < max; ++i) { achievements[i]._completed = value; }
  223.         _save();
  224.     };
  225.    
  226.     /**
  227.      * Used to handle our plugin commands.
  228.      */
  229.     var _handlePluginCommand = function(args) {
  230.         var cmd = args[0], id = args[1];
  231.         switch(cmd) {
  232.             case 'reset': if(id) { IAVRA.ACHIEVEMENT.reset(id); } else { IAVRA.ACHIEVEMENT.resetAll(); } break;
  233.             case 'complete': if(id) { IAVRA.ACHIEVEMENT.complete(id); } else { IAVRA.ACHIEVEMENT.completeAll(); } break;
  234.             case 'check': IAVRA.ACHIEVEMENT.check(); break;
  235.         };
  236.     };
  237.    
  238.     //=============================================================================
  239.     // module IAVRA.ACHIEVEMENT
  240.     //=============================================================================
  241.  
  242.     IAVRA.ACHIEVEMENT = {
  243.         achievements: function() { return _achievements; },
  244.         check: function() { _test(); },
  245.         reset: function(id) { _setSingle(id, false); },
  246.         resetAll: function() { _setAll(false); },
  247.         complete: function(id) { _setSingle(id, true); },
  248.         completeAll: function() { _setAll(true); },
  249.         isCompleted: function(id) { return _achievements.some(function(a) { return a.id === id && a.completed; }); },
  250.         /**
  251.          * The basic achievement class, which contains core logic. Also used for the "none" trigger type. It will never
  252.          * automatically mark as being completed, but can still be completed with a script call or plugin command.
  253.          */
  254.         Achievement: function() { this.initialize.apply(this, arguments); },
  255.         /**
  256.          * Checks a given switch and will automatically mark as completed, once the switch is set to true.
  257.          */
  258.         Achievement_Switch: function() { this.initialize.apply(this, arguments); },
  259.         /**
  260.          * Checks a given variable and will automatically mark as completed, once it reaches or surpasses a given value.
  261.          */
  262.         Achievement_Variable: function() { this.initialize.apply(this, arguments); },
  263.     };
  264.    
  265.     /**
  266.      * Can be extended by other plugins to register additional achievement classes. All of these should extend Achievement.
  267.      */
  268.     IAVRA.ACHIEVEMENT.triggerMappings = {
  269.         'none': IAVRA.ACHIEVEMENT.Achievement,
  270.         'switch': IAVRA.ACHIEVEMENT.Achievement_Switch,
  271.         'variable': IAVRA.ACHIEVEMENT.Achievement_Variable
  272.     };
  273.    
  274.     //=============================================================================
  275.     // class IAVRA.ACHIEVEMENT.Achievement
  276.     //=============================================================================
  277.  
  278.     (function($) {
  279.        
  280.         /**
  281.          * Initializes all basic data, that is shared by all achievement classes. Those are:
  282.          * - id: Used to load and store the current state and to identify the achievement in a script/plugin call.
  283.          * - title: Title to be displayed in the achievement menu and notifications.
  284.          * - description: Description to be displayed in the achievement menu.
  285.          * - icon: Icon index to be used for the achievement menu and notifications.
  286.          * - secret: Whether the achievement should be hidden in the menu, as long as it hasn't been completed.
  287.          */
  288.         $.prototype.initialize = function(data) {
  289.             this._id = data.id;
  290.             this._title = data.title;
  291.             this._description = data.description;
  292.             this._icon = data.icon;
  293.             this._secret = data.secret;
  294.             this._completed = false;
  295.         };
  296.        
  297.         /**
  298.          * If the achievement hasn't already been completed and it's "test" function returns true, mark it as completed
  299.          * and invoke its "_onComplete" function.
  300.          */
  301.         $.prototype.update = function() {
  302.             if(!this._completed && this.test()) { this.completed = true; this.onComplete(); }
  303.         };
  304.        
  305.         /**
  306.          * Tests, whether the trigger condition for this achievement has been completed. To be overridden in subclasses.
  307.          */
  308.         $.prototype.test = function() {};
  309.        
  310.         /**
  311.          * What to do, when an achievement has been completed. May be overridden by other plugins, to display popups or
  312.          * similar.
  313.          */
  314.         $.prototype.onComplete = function() {};
  315.        
  316.         /**
  317.          * Define a bunch of properties, so our private variables don't have to be used directly.
  318.          */
  319.         Object.defineProperties($.prototype, {
  320.             id: { get: function() { return this._id; } },
  321.             title: { get: function() { return this._title; } },
  322.             description: { get: function() { return this._description; } },
  323.             icon: { get: function() { return this._icon; } },
  324.             secret: { get: function() { return this._secret; } },
  325.             completed: {
  326.                 get: function() { return this._completed; },
  327.                 set: function(value) { this._completed = value; _save(); }
  328.             }
  329.         });
  330.        
  331.     })(IAVRA.ACHIEVEMENT.Achievement);
  332.    
  333.     //=============================================================================
  334.     // class IAVRA.ACHIEVEMENT.Achievement_Switch
  335.     //=============================================================================
  336.  
  337.     (function($) {
  338.         ($.prototype = Object.create(IAVRA.ACHIEVEMENT.Achievement.prototype)).constructor = $;
  339.        
  340.         /**
  341.          * Takes an additional "trigger.switchId" parameter, which indicates the switch to check during the update.
  342.          */
  343.         $.prototype.initialize = function(data) {
  344.             IAVRA.ACHIEVEMENT.Achievement.prototype.initialize.call(this, data);
  345.             this._switchId = data.trigger.switchId;          
  346.         };
  347.        
  348.         /**
  349.          * Tests, whether the given switch is set to true.
  350.          */
  351.         $.prototype.test = function() { return $gameSwitches.value(this._switchId); };
  352.        
  353.     })(IAVRA.ACHIEVEMENT.Achievement_Switch);
  354.    
  355.     //=============================================================================
  356.     // class IAVRA.ACHIEVEMENT.Achievement_Variable
  357.     //=============================================================================
  358.  
  359.     (function($) {
  360.         ($.prototype = Object.create(IAVRA.ACHIEVEMENT.Achievement.prototype)).constructor = $;
  361.                
  362.         /**
  363.          * Takes additional "trigger.variableId" and "trigger.variableValue" parameters, which indicate the variable to
  364.          * check and which value it should have.
  365.          */
  366.         $.prototype.initialize = function(data) {
  367.             IAVRA.ACHIEVEMENT.Achievement.prototype.initialize.call(this, data);
  368.             this._variableId = data.trigger.variableId;
  369.             this._variableValue = data.trigger.variableValue;
  370.         };
  371.        
  372.         /**
  373.          * Tests, whether the given variable has reached or surpassed the given value.
  374.          */
  375.         $.prototype.test = function() { return $gameVariables.value(this._variableId) >= this._variableValue; };
  376.        
  377.     })(IAVRA.ACHIEVEMENT.Achievement_Variable);
  378.    
  379.     //=============================================================================
  380.     // class Scene_Base
  381.     //=============================================================================
  382.    
  383.     (function($) {
  384.    
  385.         /**
  386.          * Disable automatic checking, if a value of 0 or less was specified for the interval.
  387.          */
  388.         if(!_param_interval) { return; }
  389.    
  390.         /**
  391.          * During scene update also call our own update function to determine, whether achievements have been completed.
  392.          */
  393.         var alias_update = $.prototype.update;
  394.         $.prototype.update = function() {
  395.             alias_update.call(this);
  396.             _update();
  397.         };
  398.    
  399.     })(Scene_Base);
  400.    
  401.     //=============================================================================
  402.     // class Scene_Boot
  403.     //=============================================================================
  404.  
  405.     (function($) {
  406.        
  407.         /**
  408.          * Load our achievement data at startup.
  409.          */
  410.         var alias_create = $.prototype.create;
  411.         $.prototype.create = function() {
  412.             alias_create.call(this);
  413.             _loadFile(_param_configuration, _create);
  414.         };
  415.        
  416.         /**
  417.          * Check, whether all achievements have been initialized.
  418.          */
  419.         var alias_isReady = $.prototype.isReady;
  420.         $.prototype.isReady = function() {
  421.             return !!_achievements && alias_isReady.call(this);
  422.         };
  423.        
  424.     })(Scene_Boot);
  425.    
  426.     //=============================================================================
  427.     // class Game_Interpreter
  428.     //=============================================================================
  429.    
  430.     (function($) {
  431.    
  432.         /**
  433.          * Register our own plugin command with the game interpreter.
  434.          */
  435.         var alias_pluginCommand = $.prototype.pluginCommand;
  436.         $.prototype.pluginCommand = function(cmd, args) {
  437.             if(cmd === _param_pluginCommand) { return _handlePluginCommand(args); }
  438.             alias_pluginCommand.call(this, cmd, args);
  439.         };
  440.    
  441.     })(Game_Interpreter);
  442.    
  443.     //=============================================================================
  444.     // module StorageManager
  445.     //=============================================================================
  446.    
  447.     (function($) {
  448.    
  449.         /**
  450.          * Return the path to our own save file, if our savefile id is supplied.
  451.          */
  452.         var alias_localFilePath = $.localFilePath;
  453.         $.localFilePath = function(savefileId) {
  454.             if(savefileId === _savefileId) {
  455.                 return this.localFileDirectoryPath() + 'achievements.rpgsave';
  456.             }
  457.             return alias_localFilePath.call(this, savefileId);
  458.         };
  459.        
  460.         /**
  461.          * Return the key to our local storage entry, if our savefile id is supplied.
  462.          */
  463.         var alias_webStorageKey = $.webStorageKey;
  464.         $.webStorageKey = function(savefileId) {
  465.             if(savefileId === _savefileId) {
  466.                 return 'RPG Achievements';
  467.             }
  468.             return alias_webStorageKey.call(this, savefileId);
  469.         };
  470.    
  471.     })(StorageManager);
  472.    
  473. })();
Add Comment
Please, Sign In to add comment