RPG Maker MZ Typescript edition

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV


Introductions
This project document the whole MZ codebase allowing programmer to create plugins for rpg maker MZ using typescript. (it can also be used for get typing for javascript project)

Do take in consideration this project is still in development and so far only the Core files released with the API is defined!

How to use it?
Just download the project HERE and get the files from dist/libs
There's a totals of 7 files (including the external library)
There's two way of using it :

by using the bundled files and calling the file globally or using the import features such as

Code:
import {Sprite} from "./lib/rpg_core";

Please take now that in the foreseen futur the file will be created as a distributable npm project and the core file will be declared as an ambient module.

Plugin Environment
The plugin environment is still a work in progress but will be worked once the definitions files will be released. It will use rollup.js to build your plugins. An integration with gulp is also planned.

Contributing
You can contribute to the project by any means!
use to install all the dependencies.

Code:
npm install
As the project is open to contribution there are specific rules to follow :

All class must be declared then exported

Code:
     declare class Dummy {}

     export {Dummy}
due to the fact the project use rollup to bundle the files.
All the appropriate files must be imported into the main file relative to the folder.
i.e : rpg_core.d.ts , rpg_object.d.ts etc
the structure is at follow :

Code:
import {Class} from "./module";



declare module "rpg_core" {

    export {

        Class,

    }

}
do and don't
Do not references separated module directly from separated folder

Code:
import {Game_Temp} from "../rpg_object/Game_Temp";
It will provoke bundling error. Take in consideration when working on plugins it is fine to use the separated module. Using the bundled file is less typing though.

do references separated module like this when doing the rpg declaration files:

Code:
import {Game_Temp} from "../bundled/rpg_object";
build the files
Simply do rollup configfilename for the time being. In the futur their will be a NPM script command to just build the whole library in one call.

Roadmap
  • Documents all the classes.
  • Create RPG_Object, Manager , etc definitions files
  • Convert the project to a NPM project.
  • Create plugins helper.
  • Establish the plugin environment
  • Establish proper NPM command for ease the building.
Licenses :
MIT
 
Last edited:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,748
Reaction score
911
First Language
Chinese
Primarily Uses
N/A
Would you mind further elaborating the expected plugin development workflow with this? I'm quite interested in this project, and it seems to me that my workflow would be something like the following if I'm to use your project:
1. Installs your project
2. Writes a plugin in TypeScript
3. Uses rollup.js to change it into vanilla JavaScript
4. Checks what the vanilla counterpart looks like and see if there's anything need to be further optimized for code quality(some plugin users will end up touching the implementations in the vanilla counterpart)
5. Tests the vanilla counterpart in a RMMZ project
6. Publishes the vanilla counterpart for plugin users and both versions for fellow plugin developers
I guess some fellow plugin developers will read the original TypeScript versions of others' plugins, and some will read the vanilla counterparts, so I think I'll publish both version for each plugin into my GitHub(the TypeScript versions will be in the development(TypeScript) folder and the vanilla counterparts will be in the release(Vanilla) folder so plugin users won't end up downloading the TypeScript version) :)
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
Hi! the plugin workflow was still in development but pretty much you will work with separated files or one (all ups to you) but the overall bundling will be in a classic IIFE namespace.

The way I work my plugins is by folder and going

namespace Engine - contain all my parameters and is NOT exported in the final process.

then you have all the files as single class plugin and doesn't need namespace you just need to export it at the end.




@DoubleX as you can see it's very simple and heavily typed I always make sure that I know what my data return. I had often trouble to document my parameters so I often do the use of Generic function to force the system to always return a said function.

The only drawback I had was to create parameters variable likes yanfly did
like
var parameters = parameters || {};

I had to do some kind of black magic where I am forcing the reasignation of the variable type using the as command but other than that it's pretty standard coding.

you not forced to do that but it's always fun to do so.

+ with the import / export workflow it make variables and class to not clash.

but yeah after in the index.ts you just import the class and just export them like that :

then you just compile.

The biggest annoyance right now is if you have globals if you setup import then the globals are 'local' (for typescript not the js) but if you setup it to be global (without using import export) for the globals....it will mess up with rollup lol.

as for overwriting existing method, you can just do that and then Import it like this :

import * as override from "./class";
it will properly import and for those 'override' you not forced to export them.
 

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 I'll wait for an actual MZ plugin written this way(both the original TypeScript development version and the vanilla release version) to decide whether I'll be going in :)
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
Sure! The typescript is still in development!
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,748
Reaction score
911
First Language
Chinese
Primarily Uses
N/A
As I've been reading some parts of the default MZ codebase, I've found quite some poorly written codes and I ended up breaking them down into small functions/methods making plugins easier to add/edit/remove functionality without having to rewrite them, thus increasing plugin compatibility and development effectiveness and efficiency.
I wonder if you'll make similar changes to the default MZ codebase in your TypeScript versions as well, as long as none of those changes will break plugins not written with your TypeScript versions in mind :)
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
As I've been reading some parts of the default MZ codebase, I've found quite some poorly written codes and I ended up breaking them down into small functions/methods making plugins easier to add/edit/remove functionality without having to rewrite them, thus increasing plugin compatibility and development effectiveness and efficiency.
I wonder if you'll make similar changes to the default MZ codebase in your TypeScript versions as well, as long as none of those changes will break plugins not written with your TypeScript versions in mind :)
The problem with this is I am doing an definition files for mz which is supposed to reflect mz original code base. So i am bot per say touching the code.

If they will get an typescript full version? Its kinda impossible to maintain it by myself the definition files already taking a lots of work to do (especially that js doesnt reflect typing properly already)

The data mananger took me a little while to figure and by desigb i switched the global variable ($dataarmors etc) to a individual files named globals since due to how the import export features works you have to import the variable. With the import features

As for improving the code base i will try to apply some snippset when necessary
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,748
Reaction score
911
First Language
Chinese
Primarily Uses
N/A
While your project states that its license is MIT, I wonder if it's really pure MIT, because it'd mean any plugin developed with your project will have to use the MIT license as well, and this can be troublesome for some commercial plugins, therefore I guess the MIT license isn't applied here as-is.
So I'd like to ask:
1. Do I need to give you credit and/or ask users to do so if my plugins are written using your project?
2. If I've made some commercial plugins using your project, do I have to have an agreement with you first(like sharing some profits with you on a case-by-case basis) before publishing it?
 

Dr.Yami

。◕‿◕。
Developer
Joined
Mar 5, 2012
Messages
1,003
Reaction score
757
First Language
Vietnamese
Primarily Uses
Other
While your project states that its license is MIT, I wonder if it's really pure MIT, because it'd mean any plugin developed with your project will have to use the MIT license as well, and this can be troublesome for some commercial plugins, therefore I guess the MIT license isn't applied here as-is.
So I'd like to ask:
1. Do I need to give you credit and/or ask users to do so if my plugins are written using your project?
2. If I've made some commercial plugins using your project, do I have to have an agreement with you first(like sharing some profits with you on a case-by-case basis) before publishing it?
I believe MIT License allows people to use, modify, distribute etc etc for both commercial and non-commercial uses. Users only have to include the credit + original license for the original library
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
I think yeah i might rework the licenses to fits more the usage but yeah ANYONE can create plugins from it etc even commercial. Its stands with the same licenses as Mz as long you have a copy of mz you can use it
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
Update so far :

I am almost done with Manager files Only need to do the BattleManager class which IMO is the longest

  • Storage Manager is somewhat typed but a lots of 'unknown' is thrown in this one since I need to figure out the whole structure. (I am not familiar with async and promise).
  • I still have to define the variable for Game_Temp etc since they are not defined yet.
  • The RPG namespace who hold most of the interface is cluttered and needs refactoring.
  • Classes still have yet to be documented but I prefers focussing on the typing then I will add some comments
I switched to ES_Lint to help me in keeping the code cleaner. Also waiting for Kino to work on the documentation generation config so we can Compile docs into a format similar to the MZ / pixi docs API.


Also an mockup idea for MZ typescript edition TOS :
Code:
By using MZ Typescript tool set you recognize :

Owning a legal copy of RPG Maker.
You recognize to not REPOST the Typescript definitions files anywhere else and claim you did it.
Use NPM install / gitclone. Forking the project on github is fine.
To not sell the Typescript definitions files or hide them behind a paywall.
To not remove the copyright header on top of the definitions files.

By agreeing on those terms you are allowed :
To develop games both commercial and non commercial using the typescript toolset free of charge
Free to distribute plugins built using the MZ typescript tool set both commercially and non commercial.
To like cat.
I will also ping a moderator @MushroomCake28 to pine this article since it's IMO considerate an very useful tool.
 
Last edited:

MushroomCake28

KAMO Studio
Global Mod
Joined
Nov 18, 2015
Messages
3,601
Reaction score
4,611
First Language
English
Primarily Uses
RMMV
@nio kasgami I've pin the thread for now, but we might have to move it to the MZ section once MZ releases and the section gets created.
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,748
Reaction score
911
First Language
Chinese
Primarily Uses
N/A
  • Storage Manager is somewhat typed but a lots of 'unknown' is thrown in this one since I need to figure out the whole structure. (I am not familiar with async and promise).
According to pako, zip in Storage Manager is Uint8Array|Array.
As for Promises, you can simply type it as Promise, even though things might be even better if you go for something like Promise(string) or Promise[string](I don't know the exact TypeScript format in this case), meaning that the promise should return a string when resolved(i.e., the resolve function in the Promise takes a string as the argument) :)
In the case of fs, it's actually used synchronously(existsSync, mkdirSync, renameSync, unlinkSync, readFileSync, writeFileSync) in Storage Manager, so Promises are used there to prevent blocking ;)
 
Last edited:

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
Yeah @DoubleX I think at runtime and console.log i will be able to get an understanding of the overall structure of the promise sincr the console.log print an exhautive log of the object returned compared to VS code who just return any
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,748
Reaction score
911
First Language
Chinese
Primarily Uses
N/A
You may want to try something like this:
JavaScript:
function StorageManager() {
    throw new Error("This is a static class");
}

String[] StorageManager._forageKeys = [];
Boolean StorageManager._forageKeysUpdated = false;

Boolean StorageManager.isLocalMode = function() {
    return Utils.isNwjs();
};

Promise() StorageManager.saveObject = function(String saveName, Object object) {
    return this.objectToJson(object)
        .then(String json => this.jsonToZip(json))
        .then(Uint8Array|Array zip => this.saveZip(saveName, zip));
};

Promise(Object) StorageManager.loadObject = function(String saveName) {
    return this.loadZip(saveName)
        .then(Uint8Array|Array zip => this.zipToJson(zip))
        .then(String json => this.jsonToObject(json));
};

Promise(string) StorageManager.objectToJson = function(Object object) {
    return new Promise((any -> void resolve, any -> void reject) => {
        try {
            const String json = JsonEx.stringify(object);
            resolve(json);
        } catch (Error e) {
            reject(e);
        }
    });
};

Promise(Object) StorageManager.jsonToObject = function(String json) {
    return new Promise((any -> void resolve, any -> void reject) => {
        try {
            const Object object = JsonEx.parse(json);
            resolve(object);
        } catch (Error e) {
            reject(e);
        }
    });
};

Promise(Uint8Array|Array) StorageManager.jsonToZip = function(String json) {
    return new Promise((any -> void resolve, any -> void reject) => {
        try {
            const Uint8Array|Array zip = pako.deflate(json, { to: "string", level: 1 });
            if (zip.length >= 50000) {
                console.warn("Save data is too big.");
            }
            resolve(zip);
        } catch (Error e) {
            reject(e);
        }
    });
};

Promise(String) StorageManager.zipToJson = function(Uint8Array|Array zip) {
    return new Promise((any -> void resolve, any -> void reject) => {
        try {
            if (zip) {
                const String json = pako.inflate(zip, { to: "string" });
                resolve(json);
            } else {
                resolve("null");
            }
        } catch (Error e) {
            reject(e);
        }
    });
};

Promise() StorageManager.saveZip = function(String saveName, Uint8Array|Array zip) {
    if (this.isLocalMode()) {
        return this.saveToLocalFile(saveName, zip);
    } else {
        return this.saveToForage(saveName, zip);
    }
};

Promise(Uint8Array|Array) StorageManager.loadZip = function(String saveName) {
    if (this.isLocalMode()) {
        return this.loadFromLocalFile(saveName);
    } else {
        return this.loadFromForage(saveName);
    }
};

Boolean StorageManager.exists = function(String saveName) {
    if (this.isLocalMode()) {
        return this.localFileExists(saveName);
    } else {
        return this.forageExists(saveName);
    }
};

Promise() StorageManager.remove = function(String saveName) {
    if (this.isLocalMode()) {
        return this.removeLocalFile(saveName);
    } else {
        return this.removeForage(saveName);
    }
};

Promise() StorageManager.saveToLocalFile = function(String saveName, Uint8Array|Array zip) {
    const String dirPath = this.fileDirectoryPath();
    const String filePath = this.filePath(saveName);
    const String backupFilePath = filePath + "_";
    return new Promise((any -> void resolve, any -> void reject) => {
        this.fsMkdir(dirPath);
        this.fsUnlink(backupFilePath);
        this.fsRename(filePath, backupFilePath);
        try {
            this.fsWriteFile(filePath, zip);
            this.fsUnlink(backupFilePath);
            resolve();
        } catch (Error e) {
            try {
                this.fsUnlink(filePath);
                this.fsRename(backupFilePath, filePath);
            } catch (Error e2) {
                //
            }
            reject(e);
        }
    });
};

Promise(Uint8Array|Array) StorageManager.loadFromLocalFile = function(String saveName) {
    const String filePath = this.filePath(saveName);
    return new Promise((any -> void resolve, any -> void reject) => {
        const Uint8Array|Array data = this.fsReadFile(filePath);
        if (data) {
            resolve(data);
        } else {
            reject(new Error("Savefile not found"));
        }
    });
};

Boolean StorageManager.localFileExists = function(String saveName) {
    const FS fs = require("fs");
    return fs.existsSync(this.filePath(saveName));
};

void StorageManager.removeLocalFile = function(String saveName) {
    this.fsUnlink(this.filePath(saveName));
};

Promise() StorageManager.saveToForage = function(String saveName, Uint8Array|Array zip) {
    const String key = this.forageKey(saveName);
    const String testKey = this.forageTestKey();
    setTimeout(() => localforage.removeItem(testKey));
    return localforage
        .setItem(testKey, zip)
        .then(localforage.setItem(key, zip))
        .then(this.updateForageKeys());
};

Promise(Uint8Array|Array) StorageManager.loadFromForage = function(String saveName) {
    const String key = this.forageKey(saveName);
    return localforage.getItem(key);
};

Boolean StorageManager.forageExists = function(String saveName) {
    const String key = this.forageKey(saveName);
    return this._forageKeys.includes(key);
};

Promise() StorageManager.removeForage = function(String saveName) {
    const String key = this.forageKey(saveName);
    return localforage.removeItem(key).then(this.updateForageKeys());
};

Promise() StorageManager.updateForageKeys = function() {
    this._forageKeysUpdated = false;
    return localforage.keys().then(String[] keys => {
        this._forageKeys = keys;
        this._forageKeysUpdated = true;
        return 0;
    });
};

Boolean StorageManager.forageKeysUpdated = function() {
    return this._forageKeysUpdated;
};

void StorageManager.fsMkdir = function(path) {
    const FS fs = require("fs");
    if (!fs.existsSync(path)) {
        fs.mkdirSync(path);
    }
};

void StorageManager.fsRename = function(String oldPath, String newPath) {
    const FS fs = require("fs");
    if (fs.existsSync(oldPath)) {
        fs.renameSync(oldPath, newPath);
    }
};

void StorageManager.fsUnlink = function(String path) {
    const FS fs = require("fs");
    if (fs.existsSync(path)) {
        fs.unlinkSync(path);
    }
};

Uint8Array?|Array? StorageManager.fsReadFile = function(String path) {
    const FS fs = require("fs");
    if (fs.existsSync(path)) {
        return fs.readFileSync(path, { encoding: "utf8" });
    } else {
        return null;
    }
};

void StorageManager.fsWriteFile = function(String path, Uint8Array|Array data) {
    const FS fs = require("fs");
    fs.writeFileSync(path, data);
};

String StorageManager.fileDirectoryPath = function() {
    const Path path = require("path");
    const String base = path.dirname(process.mainModule.filename);
    return path.join(base, "save/");
};

String StorageManager.filePath = function(String saveName) {
    const String dir = this.fileDirectoryPath();
    return dir + saveName + ".rmmzsave";
};

String StorageManager.forageKey = function(String saveName) {
    const Number gameId = $dataSystem.advanced.gameId;
    return "rmmzsave." + gameId + "." + saveName;
};

String StorageManager.forageTestKey = function() {
    return "rmmzsave.test";
};
While it's not exactly TypeScript, I hope you can still get the important parts :)

P.S.: You may want to check localForage and path as well ;)
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
Ah yeah this what i tried (not same syntax tho)
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
I got some interesting interaction while talking with hudell about his upcoming patching system) I really love his Patching tool for classes which allows to alias classes in a very simple way. Very similar to how we discussed for how to alias ES6 class thread

Code:
let superClass = null;
class MyScene_Title {
  initialize(){
   superClass.initialize.call(this);
  }
}

// CycloneEngine Implementations soon disclosed
Although as I like Hudells way of working. I had one major problem : Typescript will complain a lot lol.
So by doing some test I discovered some function that will work perfectly fine.
Code:
const superClass = null;

class Scene_Test implements Scene_Base {


    public initialize(){
        superClass.initialize.call(this);
    }
    public myMethod(){
        return "";
    }
}
and the code will run fine with all typing intellissenses. It's simply call a form of polymorphism or duck typing
So as mentioned into the ES6 thread if we need to alias like how Hudells does with cyclone we can use this way without asking to much inference from typescript and avoid it to be bugging out.
 

Schlangan

A madman with a computer
Veteran
Joined
May 20, 2015
Messages
1,419
Reaction score
1,695
First Language
French
Primarily Uses
RMMV
Might be a bit late asking that but well... What is the advantage of this Typescript versus a plain notepad++ use to write JS ?
 

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,949
Reaction score
3,039
First Language
French
Primarily Uses
RMMV
Might be a bit late asking that but well... What is the advantage of this Typescript versus a plain notepad++ use to write JS ?
Typescript is a programming language lol so it wouldn't work well in notepad++
To be exact it's Javascript but with static typing (still compile to pure js in the end)

and all also typescript provide intelissenses or 'typing' for your javascript so if you was using a jsconfig file your JS files would get intelisenses from the typescript definitions files.

it's has an advantage that if you say this function return something you know what it return instead of just saying 'Any' (which is anything javascript can return)
 

Panzer_Ravana

Villager
Member
Joined
Aug 21, 2020
Messages
6
Reaction score
3
First Language
español
Primarily Uses
RMMV

What does this mean for the project?
 

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

Latest Threads

Latest Posts

Latest Profile Posts

Stream will be live shortly with some game development! Feel free to drop by!
it took soooo long to get character assist to work right in my game. like certain fighting games, a team mate can hop in, do an attack, then leave. it works since these are all one on one fights (usually)
Honestly. Didn't sleep for a day. ONCE
Hi! I've been working on some character sprites for my pirate themed game.
Still somewhat new to pixel art, so feedback or inputs would be appreciated ^^
TRIVIA: I love Japanese food and culture! I've never been to Japan, but someday I'm going to go to Tokyo and do some touring, including Disneyland Tokyo and DisneySea! And I even prepared myself for earthquake safety.

Forum statistics

Threads
104,259
Messages
1,005,024
Members
135,770
Latest member
Yeah_That_1
Top