Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add enums for event command parameters #245

Open
fdelapena opened this issue Oct 2, 2018 · 23 comments
Open

Add enums for event command parameters #245

fdelapena opened this issue Oct 2, 2018 · 23 comments
Labels

Comments

@fdelapena
Copy link
Contributor

fdelapena commented Oct 2, 2018

Currently Player uses dozens of magic numbers for event command parameters. For more readable code, mostly in game interpreter and shared and better support for game editors, liblcf might provide some generated code from CSV with these enumerations.

Number of hits of com.parameters in code:

  • game_interpreter.cpp: 529
  • game_interpreter_map.cpp: 52
  • game_interpreter_battle.cpp: 42
@jetrotal
Copy link

jetrotal commented Sep 4, 2023

I did some research on this:

Ghabry Suggested checking R48's commands descriptions, from @20kdc repos:
https://github.com/20kdc/gabien-app-r48/blob/master/app/src/main/resources/assets/R2K/CommandsGI_0.txt
image

There are also some interesting references from Destiny Patch sourcecode (thanks to Kotatsu Akira and @drxgb)
Object_Command.zip

image
image

The Destiny Patch one also has lots of name suggestions for some parameters outputs,
maybe making things friendlier for the creation of a scripting language.

It may complicate stuff in cases where maniacs patch is used, due to its alien bitsmask parameters...
The code from it is a WIP assembly file, that Kotatsu made for her Destiny Script update. It has some of those maniacs patch specifications, but it may be oudated due to how mp chances every week.

@20kdc
Copy link

20kdc commented Sep 4, 2023

just so you know, some commands are... complicated, and can't be directly dumped from r48's CMDB due to this
that said if you need a JSONification of what's available I can rig up something if asked

@jetrotal
Copy link

jetrotal commented Sep 4, 2023

just so you know, some commands are... complicated, and can't be directly dumped from r48's CMDB due to this that said if you need a JSONification of what's available I can rig up something if asked

Hey @20kdc! It would help us a lot, since we are stuck with this issue since 2018


Meanwhile I extracted all the commented const from the Destiny Script asm file:
commonParameters.csv

image

@20kdc
Copy link

20kdc commented Sep 4, 2023

This isn't absolutely all data in the system (it gets increasingly complicated to do that going further into the details) but it should have an entry for every command.
cmdb.json.zip

@20kdc
Copy link

20kdc commented Sep 4, 2023

Oh, right, I should probably note:

  • There are two command databases, event and move. (Move commands are considered just another type of command in R48's system.)
  • All RPG Maker 2000/2003 commands are considered as having a string as parameter 0.
  • specialSchemaEssential is a flag indicating that the parameters given aren't necessarily accurate and basically you can chuck them out a window, because the command does something really specific that can't be expressed with a simple array-of-parameters format.
  • nameRawUnlocalized is written using S-expressions and custom stuff. Details are in the R48 development documentation, but I wouldn't really recommend trying to parse these unless you have to. They're basically inline stringifier logic.
  • name is directly derived from nameRawUnlocalized, i.e. you can derive name from nameRawUnlocalized given the prerequisite support code. (There is no C++ version of said support code, but a good start would be to have a Scheme implementation with Scheme 9 From Empty Space's macro semantics.)
  • Schema element indices are represented as integers. To map these to names, create a reverse lookup table for sdbID. The main purpose of including an SDB dump was to allow access to enum values, but I've included some other stuff just in case (it should just about get you far enough to read special schemas).
  • Dynamic schema elements aren't included. Not that there is a reason to do so; it would just dump the contents of TestGame-2000's database into the file.

@jetrotal
Copy link

jetrotal commented Sep 4, 2023

Thanks!
seems to be using the default vanilla order and looks easy to navigate by cycling through
JSON.cmdbs.event.knownCommands[n].params[m].name // e.g.: "positionLocked" or "null" if unused string

and in order to match with our IDs, it can be get through:
JSON.cmdbs.event.knownCommands[n].commandId // e.g.: 10120


I thought about replacing all se with sdbID key names but I wonder if it is useful for us when building a .csv file...

@jetrotal
Copy link

jetrotal commented Sep 4, 2023

image
This is what I made:
CommandParameters.csv

The js do get it was:

// Determine the maximum number of parameters among all commands
let maxParams = 0;
jsonFile.cmdbs.event.knownCommands.forEach((command) => {
  if (command.params.length > maxParams) {
    maxParams = command.params.length;
  }
});

// Create CSV header
const headers = ['ParentName', 'ParentId'];
for (let i = 0; i < maxParams; i++) {
  headers.push(`Param_${i}`);
}

// Create CSV rows
const csvData = [headers];

jsonFile.cmdbs.event.knownCommands.forEach((command) => {
  const row = [command.name, command.commandId];
  for (let i = 0; i < maxParams; i++) {
    if (i < command.params.length) {
      row.push(command.params[i].name);
    } else {
      row.push(''); // Empty cell if no parameter at this index
    }
  }
  csvData.push(row);
});

// Convert the data to CSV format
const csvContent = csvData.map((row) => row.join(',')).join('\n');

console.log('CSV data as a string:');
console.log(csvContent);

@20kdc
Copy link

20kdc commented Sep 4, 2023

That script isn't handling parameters with dynamic types (i.e. 10310 has goldIsVar and after that, either the literal value of gold or the variable ID)

@jetrotal
Copy link

jetrotal commented Sep 4, 2023

hm... right. Though that kind of detection is already inside the Player's source code. It should indeed have inside it something like goldValue or whatever...

@20kdc
Copy link

20kdc commented Sep 4, 2023

if you look at the JSON, you can get at least a first pass just by taking the default branch of "dynamic" parameters

EDIT: Example:

                  {
                     "arrayDI" : 2,
                     "contents" : {
                        "0" : {
                           "name" : "gold",
                           "se" : 285,
                           "type" : "static"
                        }
                     },
                     "def" : {
                        "name" : "goldVar",
                        "se" : 50,
                        "type" : "static"
                     },
                     "type" : "dynamic"
                  }

@20kdc
Copy link

20kdc commented Sep 4, 2023

...oh, right, also, something I forgot to mention: The sdbNodes stuff was included for a reason! you can use it to find and retrieve enums

@jetrotal
Copy link

jetrotal commented Sep 4, 2023

Update:
image

CommandsParameters.csv


// Determine the maximum number of parameters among all commands
let maxParams = 0;
jsonFile.cmdbs.event.knownCommands.forEach((command) => {
  if (command.params.length > maxParams) {
    maxParams = command.params.length;
  }
});

// Create CSV header
const headers = ['ParentName', 'ParentId'];
for (let i = 0; i < maxParams; i++) {
  headers.push(`Param_${i}`);
}

// Create CSV rows
const csvData = [headers];

jsonFile.cmdbs.event.knownCommands.forEach((command) => {
  const row = [command.name, command.commandId];
  for (let i = 0; i < maxParams; i++) {
    if (i < command.params.length) {
      const param = command.params[i];
      if (param.type === 'dynamic' && param.contents && param.contents[0]) {
        row.push(param.contents[0].name);
      } else {
        row.push(param.name);
      }
    } else {
      row.push(''); // Empty cell if no parameter at this index
    }
  }
  csvData.push(row);
});

// Convert the data to CSV format
const csvContent = csvData.map((row) => row.join(',')).join('\n');

console.log('CSV data as a string:');
console.log(csvContent);

@20kdc
Copy link

20kdc commented Sep 4, 2023

I'd still advise checking the "def" field, as while for booleans things are written with it being "true", in other cases it's either a default or a fallback for unrecognized values

@jetrotal
Copy link

jetrotal commented Sep 4, 2023

You mean, checking if def exists instead of param.type === 'dynamic' ?


I gotta leave right now, later I'll take a better look at still needs to be done and what sdbNodes does.

@20kdc
Copy link

20kdc commented Sep 4, 2023

You mean, checking if def exists instead of param.type === 'dynamic' ?

I gotta leave right now, later I'll take a better look at still needs to be done and what sdbNodes does.

Well, for when you get back, I'll just explain the fine details:

Param is an abstract class in R48, with two implementations: Static and Dynamic. These are represented as the "static" and "dynamic" types.

If param.type == "dynamic", the following will be true:

  1. "def" exists, and is the default param
  2. "contents" exists, and contains alternate param values
  3. "arrayDI" indicates which parameter is used to control this parameter

For static params, it's "name" (name) and "se" (index into "sdbNodes") as usual.

"sdbNodes" is an array because a lot of SDB nodes are just completely unnamed, and some have two names, etc. That said, a name will only ever refer to one SDB node.
You can find the mapping in the "sdbID" object.

The SDB data in the JSON is... incomplete. But it has things like enumerations, so you can use this to get, say, window position enumerations and such. The "se" value in parameters connects those enumerations to parameters.

The "specialSchema" value is for commands which don't fit the Param system; depending on how much or little effort you want to put into really squeezing every last bit of data out of the JSON, you may be better off just ignoring this.

@jetrotal
Copy link

jetrotal commented Sep 5, 2023

Now it has the same command Names from easyRPG sourcecode.
image
EventCommands.csv

I received some feedback about more entries:
image

@20kdc do you have any info on those?


The source code of the JS extractor (included both jsonFile and BetterName templates, to show where goes r48 json and easyRPG commandNames)

// Replace this with your JSON data
const jsonFile = {
  cmdbs: {
    event: {
      knownCommands: [
        {
          name: 'Command1',
          commandId: 'ID1',
          params: [
            { name: 'Param_0_Value', type: 'static' },
            { name: 'Param_1_Value', type: 'dynamic', contents: [{ name: 'Dynamic_Param_1' }] },
          ],
        },
        {
          name: 'Command2',
          commandId: 'ID2',
          params: [
            { name: 'Param_0_Value', type: 'dynamic', contents: [{ name: 'Dynamic_Param_0' }] },
            { name: 'Param_1_Value', type: 'static' },
            { name: 'Param_2_Value', type: 'dynamic', contents: [{ name: 'Dynamic_Param_2' }] },
          ],
        },
      ],
    },
  },
};

const betterNames = {
  "ID1": "BetterCommand1",
  "ID2": "BetterCommand2",
  // Add more mappings as needed
};

// Determine the maximum number of parameters among all commands
let maxParams = 0;
jsonFile.cmdbs.event.knownCommands.forEach((command) => {
  if (command.params.length > maxParams) {
    maxParams = command.params.length;
  }
});

// Create CSV header
const headers = ['ParentName', 'ParentId'];
for (let i = 0; i < maxParams; i++) {
  headers.push(`Param_${i}`);
}

// Create CSV rows
const csvData = [headers];

jsonFile.cmdbs.event.knownCommands.forEach((command) => {
  const row = [betterNames[command.commandId] || command.name, command.commandId];
  for (let i = 0; i < maxParams; i++) {
    if (i < command.params.length) {
      const param = command.params[i];
      if (param.type === 'dynamic' && param.contents && param.contents[0]) {
        row.push(param.contents[0].name);
      } else {
        row.push(param.name);
      }
    } else {
      row.push(''); // Empty cell if no parameter at this index
    }
  }
  csvData.push(row);
});

// Convert the data to CSV format
const csvContent = csvData.map((row) => row.join(',')).join('\n');

console.log('CSV data as a string:');
console.log(csvContent);

@20kdc
Copy link

20kdc commented Sep 6, 2023

The relation between the "value" and "type of value" parameter is, as I have previously explained, in the "dynamic" param structure. "arrayDI" is the index of the "type of value" parameter.

@jetrotal
Copy link

jetrotal commented Sep 9, 2023

ok, put more information inside it.
Remember that this only covers the R48 supported commands, it lacks the new commands and parameters ported from Maniacs Patch.

Let me know if something else is missing:

image

EventCommands (4).csv

jsonFile = {} //R48 json file
easyrpgNames = {} //easyRPG list of commands names

// Determine the maximum number of parameters among all commands
let maxParams = 0;
jsonFile.cmdbs.event.knownCommands.forEach((command) => {
  if (command.params.length > maxParams) {
    maxParams = command.params.length;
  }
});

// Create CSV header
const headers = ['Command', 'ID']; // this instead of ParentName and ParentId
for (let i = 0; i < maxParams; i++) {
  if (i === 0) {
    headers.push('String'); // Rename Param_0 to String
  } else {
    headers.push(`Param_${i}`);
  }
}

// Create CSV rows
const csvData = [headers];

jsonFile.cmdbs.event.knownCommands.forEach((command) => {
  const row = [easyrpgNames[command.commandId] || command.name.replace(/[^a-zA-Z0-9_]/g, ''), command.commandId];
  for (let i = 0; i < maxParams; i++) {
    if (i < command.params.length) {
      const param = command.params[i];
      if (param.type === 'dynamic') {
        // Construct the Param_n cell based on additional rules
        let paramCell = param.def && param.def.name ? param.def.name : 'undefined'; // Default parameter name

        if (param.contents) {
          const contentsNames = Object.keys(param.contents).map((o) => param.contents[o].name);
          if (contentsNames.length > 0) {
            paramCell += ' || ' + contentsNames.join(' || '); // Contents names
          }
        }

        if (param.arrayDI != null) {
          paramCell += ` (ParentParam: ${param.arrayDI})`; // ParentName is used here now.
        }

    

        row.push(paramCell);
      } else {
        const paramName = param.name
        row.push(paramName);
      }
    } else {
      row.push(''); // Empty cell if no parameter at this index
    }
  }
  csvData.push(row);
});

// Convert the data to CSV format
const csvContent = csvData.map((row) => row.join(',')).join('\n');

console.log('CSV data as a string:');
//console.log(csvContent);
csvContent;

@jetrotal
Copy link

jetrotal commented Sep 9, 2023

cases like set variable and timer operation doesn't have parameters described on it.
those cases always have specialSchemaEssential: true + specialSchema: aNumberValue on R48 Json.

example from timer operation:

specialSchemaEssential: true
specialSchema: 423`

that points towards this key and value inside sdbID
image

@20kdc
Copy link

20kdc commented Sep 9, 2023

Yes; this is because it's hard or impossible to describe those cases using the CMDB syntax. Same goes for Conditional Branch. In fact, there's probably argument to be made against converting them period, at least at first; they're mazes and mistakes will result in awkward Player bugs.

@jetrotal
Copy link

ok, just to keep things fresh:

List of Event Commands:
https://github.com/EasyRPG/liblcf/files/12564301/EventCommands.4.csv

List of named Parameters:
https://github.com/EasyRPG/liblcf/files/12515333/commonParameters.csv

@Ghabry let me know what else is missing in those files.

@jetrotal
Copy link

I extracted all the com.parameters[n] and com.string references from their .cpp files:
commandsnippets.zip

image

I'm trying to discover if there's a painless way to customize the csv list with new commands supported by easyRPG, using these snippets of code.

@Ghabry
Copy link
Member

Ghabry commented Sep 29, 2023

Another motivation for this is shown here:

test is how it is currently done (approximately).

test2 is with a wrapper class that exposes getter function for all the individual stuff. The wrapper can be generated and then just pass the event command to the current wrapper and invoke the functions. This is more readable as the intention is more clear and the magic numbers are hidden.

When you look at the assembler output you can see that both functions compile to the exact same code. Compilers are smart :).

image

@Ghabry Ghabry removed this from the 0.8.1 milestone Sep 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

4 participants