How to Extends / Alias / Overwrite existing class in MZ/ ES6 [Tutorial]

Dr.Yami

。◕‿◕。
Developer
Joined
Mar 5, 2012
Messages
1,003
Reaction score
757
First Language
Vietnamese
Primarily Uses
Other
This is also my final answer and sorry all for derailing... But it's important to change ideas and get some new perspectives.

It was good article about understanding JS and totally agreed on everything he said (especially "If you don’t understand how statements in JavaScript are terminated, then you just don’t know JavaScript very well").
BUT my point was that semicolons are not obsolete.

And you can't run JS in single line if there are no semicolons to end statement - Not even JS leaders can do this (or any referenced institute/organization/individual)...
Code:
let i = 0 let j = 1 ... // Errors
let i = 0; let j = 1; ... // No errors

let obj = function {} obj.prototype.x = function () {} let y = 1 function b() {} let a = '' // Several errors
let obj = function () {}; obj.prototype.x = function () {}; let y = 1; function b() {} let a = ''; // No errors...
// Notice that for example function b() {} doesn't need semicolons even in single line mode
I have been programmed with different languages all my life (now about 30 years and JS about 12 years) and I've seen these pit falls many times during this time... Just trying to help others here.
Yeah I understand, just wanna share my point is if dev write a good and clean code enough and understand those edge cases, semicolons is not necessary :kaojoy: Otherwise, semicolon or not is most likely about the taste and project conventions.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,562
Reaction score
3,832
First Language
English
Nice semi colon discussion.

For me, you'll notice semi colons in one line and no semi colon in another line, even if they're practically the same line just different assignments.

I've crashed because I forgot to semicolon before IIFE.

I have no semi colon standards when it comes to JS.
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,748
Reaction score
911
First Language
Chinese
Primarily Uses
N/A
Just checked the PluginCommandBase.js, which is supposed to be an official Kadokawa(and thus MZ) plugin, and it's still aliasing this way:
Code:
(() => {
    'use strict';
    const _Klass_aliasedMethod = Klass.prototype.aliasedMethod;
    Klass.prototype.aliasedMethod = function() {};
})();
I wonder if we're supposed to alias this way as well when writing MZ plugins(even though I won't use this way unless absolutely necessary) lol
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,562
Reaction score
3,832
First Language
English
Just checked the PluginCommandBase.js, which is supposed to be an official Kadokawa(and thus MZ) plugin, and it's still aliasing this way:
Code:
(() => {
    'use strict';
    const _Klass_aliasedMethod = Klass.prototype.aliasedMethod;
    Klass.prototype.aliasedMethod = function() {};
})();
I wonder if we're supposed to alias this way as well when writing MZ plugins(even though I won't use this way unless absolutely necessary) lol
You should alias it in a way that works lol
Whether it's a fancier solution using class syntax or otherwise.

So we don't get any weird things like changes to the super-class don't get inherited by subclasses because they're pointing to the old method.

Presumably if you were to change methods in Game_Battler using class syntax, the changes won't be applied to Game_Actor or Game_Enemy?
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
yeah @Dr.Yami is right there it's just a question of convention. I just use semicolon because it's mandatory in C# and I am used to it.

but it's all up how you guys want to work. Personally when I use typescript I use an explicit function declaration such

Code:
class Dummy extends Parent {

  private _variable : number;
  constructor(){
   this._variable = 10;
  }
 
  public foo(value: number): void {
 
  }
}
I always tell the returning type even if it's not necessary but this just old habit from C#.
I even go as using generic when declaring parameters so I get specific typestructures.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,562
Reaction score
3,832
First Language
English
Visibility generally makes it easier to refactor since you know all private methods/variables will be strictly only inside your class.

But I find that most of the time I prefer not having the visibility because half the time I'm hacking my way into variables that are not exposed (eg: accessing the scene's spritesets or windows, etc)
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,748
Reaction score
911
First Language
Chinese
Primarily Uses
N/A
You should alias it in a way that works lol
Whether it's a fancier solution using class syntax or otherwise.
I'm actually concerning about the fact that the original Klass.prototype.aliasedMethod, which is now _Klass_aliasedMethod, has become private, and this can be problematic in rare cases where the original Klass.prototype.aliasedMethod has to be accessed(I've encountered this in MV several times already).
Is it impossible to fix with this way of aliasing? Of course not , and there are at least 2 ways to do so:
1. Copy the original Klass.prototype.aliasedMethod into the compatibility fix, but this might violate the terms of use for few plugins, and the compatibility fix can break whenever the original Klass.prototype.aliasedMethod changes(actually, this way only works if you know which is the original Klass.prototype.aliasedMethod, and sometimes you can't even tell).
2. Ask users to add yet another snippet right above the plugin aliasing that Klass.prototype.aliasedMethod, and all that snippet does is to capture the original Klass.prototype.aliasedMethod, but needless to say, it's less user-friendly than things can be.
So, at the very least, I think the official RM plugins can consider storing the original Klass.prototype.aliasedMethod into some kind of a container that can be accessed by other plugins :)
 

Solar_Flare

Veteran
Veteran
Joined
Jun 6, 2020
Messages
530
Reaction score
232
First Language
English
Primarily Uses
RMMV
I think that anything which would need to access the original aliased method is probably better solved by controlling load order to guarantee that your plugin loads before the one being modified. While I can't deny that there might be some cases where it's necessary, it seems very suspicious that you'd need to access some other plugin's aliased method reference.
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,748
Reaction score
911
First Language
Chinese
Primarily Uses
N/A
I think that anything which would need to access the original aliased method is probably better solved by controlling load order to guarantee that your plugin loads before the one being modified. While I can't deny that there might be some cases where it's necessary, it seems very suspicious that you'd need to access some other plugin's aliased method reference.
It really depends, and sometimes changing the load order won't solve it, because in some rare cases things can be this crazy:
1. Plugin A placed above plugin B -> Compatibility issue x
2. Plugin B placed above plugin A -> Compatibility issue y
Also, if the foreign plugin(the one you've no control over) to have compatibility issues addressed are written very, very poorly(but still works without nontrivial bugs), you can end up in a hellish nightmare like that:
1. Place plugin A above plugin B -> Solve compatibility issue x with the need to access an aliased method which is now private
2. Place plugin B above plugin A -> Solve compatibility issue y with the need to access an aliased method which is now private
I've never faced something this extreme in MV(my cases in MV can indeed be solved by forcing a certain ordering, even though it'd still mean I'll have to solve more compatibility issues that are fortunately all easy to solve), but I've faced just once in VXA(it's so insane that the compatibility issues only manifest when 3 advanced complex scripts are in play, and different ordering leads to different compatibility issues which are all very difficult to solve, causing the fix to be ridiculously painful).

Also, I'd like to know the reason of sealing off the access of the aliased method.
Maybe it's to prevent any other plugin to bypass the new version and directly access the original version?
Maybe it's to reduce plugin development workload?
Maybe it's to deliberately reduce compatibility towards some other plugins(there are indeed plugin developers intentionally not caring about external compatibility issues)?
I don't know, but I still believe that the official Kadokawa plugin developers, who are supposed to be seasoned professional software engineers, are knowingly making this aliasing decision, and there must be some wisdom that I still failed to grasp yet :)
 
Last edited:

Ossra

Formerly Exhydra
Veteran
Joined
Aug 21, 2013
Messages
1,076
Reaction score
847
First Language
English
Primarily Uses
RMMV
I am kind of bouncing between the old fashioned way and a way similar to what Hudell is going to use :

Code:
(function(_) {

  plugin.mixin(_.prototype, {

    initialize () {
      plugin.code.Scene_Map.initialize.call(this);
    },

    newMethod () {
      // ...
    },

  });

})(Scene_Map);
Which basically does the exact same thing as Hudell's code, just in a dynamic fashion. I created a small plugin class to handle plugin related whatnots ...

Code:
mixin (proto, object) {
  this.code[proto.constructor.name] = { };

  for (const name of Object.keys(object)) {
    if (typeof proto[name] === 'function') {
      this.code[proto.constructor.name][name] = proto[name];
    }
  }

  Object.assign(proto, object);
};
Kind of leaning toward the new method, but I dunno ...



For anyone interested, here is the full code for the plugin class I have been tinkering around on :

Code:
  // +=================================================|                         Plugin |
  // | [Class] Plugin
  // +==================================================================================+

    class Plugin {

  // +----------------------------------------------------------------------------------+
  // | [Method] constructor
  // +----------------------------------------------------------------------------------+
      constructor (namestring, version) {

        this.name     = namestring.split('.')[1].match(/[A-Z]*[^A-Z]+/g).join(' ');
        this.base     = this.namespace(_, 'Ossra.Plugin.' + namestring);
        this.code     = this.namespace(_, 'Ossra.Plugin.' + namestring + '.Code');
        this.imported = this.namespace(_, 'Imported.Ossra.' + namestring, version);

        this.data     = { };
        this.config   = { };
        this.helpers  = { };
        this.defaults = { };

      }; // Plugin << constructor

  // +----------------------------------------------------------------------------------+
  // | [Method] namespace
  // +----------------------------------------------------------------------------------+
      namespace (object, namestring, value = { }) {

        return namestring.split('.').reduce((parent, child, index, array) => {
          if (typeof parent[child] === 'undefined') {
            parent[child] = index + 1 === array.length ? value : { };
          }

          return parent[child];
        }, object);

      }; // Plugin << namespace

  // +----------------------------------------------------------------------------------+
  // | [Method] parameters
  // +----------------------------------------------------------------------------------+
      parameters (gid) {

        return JSON.stringify($plugins.find(function(plugin) {
          return plugin.parameters['gid'] === gid;
        })['parameters']);

      }; // Plugin << parameters

  // +----------------------------------------------------------------------------------+
  // | [Method] setup
  // +----------------------------------------------------------------------------------+
      setup (gid, defaults) {

        this.config = this.parse(this.parameters(gid), defaults);

      }; // Plugin << setup

  // +----------------------------------------------------------------------------------+
  // | [Method] parse
  // +----------------------------------------------------------------------------------+
      parse (json, defaults) {

        try {
          return JSON.parse(json, (key, value) => {
            let _def = isNaN(key) ? defaults[key] : defaults[0];
            let _val = isNaN(key) && value === '' ? _def : value;

            if (key) {
              if (typeof _def === 'function' && typeof _val !== 'function') {
                return this.helpers[_def.name.substring(6)].bind(this, _val);
              } else if (typeof _val !== typeof _def) {
                return this.parse(_val, _def);
              }
            }

            return _val;
          });
        } catch (error) {
          return defaults;
        }

      }; // Plugin << parse

  // +----------------------------------------------------------------------------------+
  // | [Method] command
  // +----------------------------------------------------------------------------------+
      command (name, func, defaults) {

        const parse = this.parse.bind(this);

        PluginManager.registerCommand(this.name, name, function (args) {
          func.call(this, parse(JSON.stringify(args), defaults));
        });

      }; // Plugin << command

     
  // +----------------------------------------------------------------------------------+
  // | [Method] mixin
  // +----------------------------------------------------------------------------------+
      mixin (proto, object) {

        this.code[proto.constructor.name] = { };

        for (const name of Object.keys(object)) {
          if (typeof proto[name] === 'function') {
            this.code[proto.constructor.name][name] = proto[name];
          }
        }

        Object.assign(proto, object);

      }; // Plugin << mixin

    };
 
Last edited:

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,562
Reaction score
3,832
First Language
English
I am kind of bouncing between the old fashioned way and a way similar to what Hudell is going to use :

Code:
(function(_) {

  plugin.mixin(_.prototype, {

    initialize () {
      plugin.code.Scene_Map.initialize.call(this);
    },

    newMethod () {
      // ...
    },

  });

})(Scene_Map);
Which basically does the exact same thing as Hudell's code, just in a dynamic fashion. I created a small plugin class to handle plugin related whatnots ...

Code:
mixin (proto, object) {
  this.code[proto.constructor.name] = { };

  for (const name of Object.keys(object)) {
    if (typeof proto[name] === 'function') {
      this.code[proto.constructor.name][name] = proto[name];
    }
  }

  Object.assign(proto, object);
};
Kind of leaning toward the new method, but I dunno ...



For anyone interested, here is the full code for the plugin class I have been tinkering around on :

Code:
  // +=================================================|                         Plugin |
  // | [Class] Plugin
  // +==================================================================================+

    class Plugin {

  // +----------------------------------------------------------------------------------+
  // | [Method] constructor
  // +----------------------------------------------------------------------------------+
      constructor (namestring, version) {

        this.name     = namestring.split('.')[1].match(/[A-Z]*[^A-Z]+/g).join(' ');
        this.base     = this.namespace(_, 'Ossra.Plugin.' + namestring);
        this.code     = this.namespace(_, 'Ossra.Plugin.' + namestring + '.Code');
        this.imported = this.namespace(_, 'Imported.Ossra.' + namestring, version);

        this.data     = { };
        this.config   = { };
        this.helpers  = { };
        this.defaults = { };

      }; // Plugin << constructor

  // +----------------------------------------------------------------------------------+
  // | [Method] namespace
  // +----------------------------------------------------------------------------------+
      namespace (object, namestring, value = { }) {

        return namestring.split('.').reduce((parent, child, index, array) => {
          if (typeof parent[child] === 'undefined') {
            parent[child] = index + 1 === array.length ? value : { };
          }

          return parent[child];
        }, object);

      }; // Plugin << namespace

  // +----------------------------------------------------------------------------------+
  // | [Method] parameters
  // +----------------------------------------------------------------------------------+
      parameters (gid) {

        return JSON.stringify($plugins.find(function(plugin) {
          return plugin.parameters['gid'] === gid;
        })['parameters']);

      }; // Plugin << parameters

  // +----------------------------------------------------------------------------------+
  // | [Method] setup
  // +----------------------------------------------------------------------------------+
      setup (gid, defaults) {

        this.config = this.parse(this.parameters(gid), defaults);

      }; // Plugin << setup

  // +----------------------------------------------------------------------------------+
  // | [Method] parse
  // +----------------------------------------------------------------------------------+
      parse (json, defaults) {

        try {
          return JSON.parse(json, (key, value) => {
            let _def = isNaN(key) ? defaults[key] : defaults[0];
            let _val = isNaN(key) && value === '' ? _def : value;

            if (key) {
              if (typeof _def === 'function' && typeof _val !== 'function') {
                return this.helpers[_def.name.substring(6)].bind(this, _val);
              } else if (typeof _val !== typeof _def) {
                return this.parse(_val, _def);
              }
            }

            return _val;
          });
        } catch (error) {
          return defaults;
        }

      }; // Plugin << parse

  // +----------------------------------------------------------------------------------+
  // | [Method] command
  // +----------------------------------------------------------------------------------+
      command (name, func, defaults) {

        const parse = this.parse.bind(this);

        PluginManager.registerCommand(this.name, name, function (args) {
          func.call(this, parse(JSON.stringify(args), defaults));
        });

      }; // Plugin << command

   
  // +----------------------------------------------------------------------------------+
  // | [Method] mixin
  // +----------------------------------------------------------------------------------+
      mixin (proto, object) {

        this.code[proto.constructor.name] = { };

        for (const name of Object.keys(object)) {
          if (typeof proto[name] === 'function') {
            this.code[proto.constructor.name][name] = proto[name];
          }
        }

        Object.assign(proto, object);

      }; // Plugin << mixin

    };
If that code can be included with every copy of RPG Maker MZ, then I think everyone would start using it.
I see there's a "community" plugin included in my copy of MV, for example.

I just don't want to have to

1. require users to install some sort of "HIME CORE" plugin, or
2. copy that mixin logic just to be able to avoid typing prototype a bunch of times

Imagine if we all started pushing our own CORE plugin just to get that mixin patch.
 

Hudell

Dog Lord
Veteran
Joined
Oct 2, 2014
Messages
3,529
Reaction score
3,679
First Language
Java's Crypt
Primarily Uses
RMMZ
My problem is having to use some kind of core too. So I prefer writing a couple extra lines but without any dependency.
Ideally the maker would come out with some way of handling this by default, but that would be asking too much at this point.
 

Ossra

Formerly Exhydra
Veteran
Joined
Aug 21, 2013
Messages
1,076
Reaction score
847
First Language
English
Primarily Uses
RMMV
For MV I crammed several functions into the initial section of the plugin so that I would not need to add a 'core' plugin. I may simply do that for MZ, and squish the plugin class into the plugin. The downside being that whenever I change code in that plugin class, I probably need to go back to the other plugins and change the code there, as well. So as much as I am not fond of a core plugin, I might go that direction to simply code updates. Not sure.

For me, if the code does not look a certain way, I am displeased with plugin and attempt to find ways to refactor. So anything that allows the code to look sleek-ish I am probably going to gravitate toward due to personal preference. Unless it is a teeny patch plugin, then heck all of that.
 

Solar_Flare

Veteran
Veteran
Joined
Jun 6, 2020
Messages
530
Reaction score
232
First Language
English
Primarily Uses
RMMV
If that code can be included with every copy of RPG Maker MZ, then I think everyone would start using it.
I see there's a "community" plugin included in my copy of MV, for example.

I just don't want to have to

1. require users to install some sort of "HIME CORE" plugin, or
2. copy that mixin logic just to be able to avoid typing prototype a bunch of times

Imagine if we all started pushing our own CORE plugin just to get that mixin patch.
Yeah, I don't really like having a core plugin either, but I also don't like duplicating basic common code (such as parameter parsing) in all my plugins...
 

Hudell

Dog Lord
Veteran
Joined
Oct 2, 2014
Messages
3,529
Reaction score
3,679
First Language
Java's Crypt
Primarily Uses
RMMZ
Played around with the idea a little more and came up with a patchClass function that works like this:

1597454073488.png

And now I like it too much so I think I'll give in and make a core class too.
What the above code does is equivalent to this:

1597454309772.png
 

TheoAllen

Self-proclaimed jack of all trades
Veteran
Joined
Mar 16, 2012
Messages
5,573
Reaction score
6,497
First Language
Indonesian
Primarily Uses
RMVXA
Yeah, I don't really like having a core plugin either, but I also don't like duplicating basic common code (such as parameter parsing) in all my plugins...
The problem with the core engine is the public impression of it. People blame the "core engine" for incompatibility. In the past, the "core" engine usually modify the base code. Which lead to more complication.

Now, if the core engine only provides the basic function you will repeatedly use, it SHOULD be fine. However, that raises another problem. When the user asking for support in this forum and provide the link to the said plugin. They will see some missing functions. The function existed in the core engine. Ideally, one specific plugin should contain all the info needed for the support question, now we need to look somewhere else (worse is if we don't know where to look the said core engine), in the collection of function in the core engine while the said plugin only uses a small part of it.

That on itself is not exactly a big deal. But it just extra work for some people to provide support answers.
 

Ossra

Formerly Exhydra
Veteran
Joined
Aug 21, 2013
Messages
1,076
Reaction score
847
First Language
English
Primarily Uses
RMMV
For MZ you can specify dependencies in the plugin header to provide a bit more information :

Code:
/*:
* @plugindesc [1.00] Plugin description.
* @author
* // Specify the RPGM the plugin is aimed toward :
* @target MZ
* @target MV
* @target MZ MV
* // Specify the base, required dependency :
* @base MyCommonBasePlugin
* // Specify placement in the plugin list :
* @orderAfter MyCommonBasePlugin
* @orderBefore MyOtherPlugin
* // Specify URL for plugin :
* @url
*/
So, having a base plugin will not be too awful ... hopefully. The documentation says that if the base plugin specified in the plugin is not present, a warning will be displayed.
 
Last edited:

Hudell

Dog Lord
Veteran
Joined
Oct 2, 2014
Messages
3,529
Reaction score
3,679
First Language
Java's Crypt
Primarily Uses
RMMZ
Just in time for MZ's release, I found a way to use actual classes for my `patchClass` method and made it a bit better:

1597895071987.png
 

Ossra

Formerly Exhydra
Veteran
Joined
Aug 21, 2013
Messages
1,076
Reaction score
847
First Language
English
Primarily Uses
RMMV
@Hudell Are you doing any framework plugin, or just shoving a few functions into a plugin class within the plugin?
 

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

Latest Threads

Latest Posts

Latest Profile Posts

Stream will be live shortly with some Darkest Dungeon! Feel free to drop by!
Made a HUGE (YYOOOOJJ) Update to Monstructs and moving towards a Steam Early Access release!
A skill type called: "Rumagic". The intention is Magic with Rum(that pirates drink)
Does it sound strange in English?
So I'm having issues with my steam account... so I can't use MZ right now, so I can't work on my game until its solved, so it be harder to be ready for 31... guess I'll get ice cream :kaopride:
Motivation! Inspiration! There you are. I was worried you guys weren't going to show up today. Let's get this thing going!

Forum statistics

Threads
104,217
Messages
1,004,757
Members
135,725
Latest member
Sark1017
Top