6 Replies - 1150 Views - Last Post: 30 January 2022 - 08:24 PM

#1 JeremyBenson11   User is offline

  • D.I.C Regular

Reputation: 1
  • View blog
  • Posts: 306
  • Joined: 10-January 14

Text Game Parser Help

Posted 20 January 2022 - 04:43 PM

I'm working on the text parser for my game again. I have this working really well. It's not fully debugged. Every case does work though. The only issue I'm facing, as I can see, is that the regexes for directObject and indirectObj need to capture any number of words or characters.

Also, this is a wysiwyg creative commons parser anyone can use for anything. It's got a couple of bandaids and not fully tested, but it's pretty solid.

Simply, how can I make this Regex capture any word amount for (?<indirectObject>[^ $]*) and (?<directObject>[^ $]*)

I've tried everything w+ w. .w, ect.

I found one other bug. 1 & 2 are getting confused. Is there a way to make them more strict? It should see when 'to' is there. Better yet, can 1 & 2 be made into one regex without confusing any other?

const matches = /(?<verb>[^ $]*)( the)? (?<indirectObject>[^ $]*) (on|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.exec(command);



For the long version, and anyone interested in playing with it, the code is below. Interested parties should definitely test all the commands in your game before considering implementation.


Declaration
$(document).ready(function(){
			
  // Parser needs a list of verbs used in the game
  // and a command to work with
  // set angledCommand with Parser.setAngledCommand(true) if commands are '<get lamp>' as apposed to 'get lamp'
 // set the error message returned on a failed parse. This can be updated any time.
			
  let verbs = ["jump", "run", "map", "yell", "ask", "get", "pump", "jam", "turn"];
  let command = "turn the key";
  Parser = new Parser();
  Parser.setVerbList(verbs);
  Parser.setErrorMessage("Command not recognized.");
			
  // parsing a command will return json data
  // "actionType" -> One of six action types to make action handling easier
  //"verb" -> The verb being used
  // "indirectObject" -> The indirect object in the pattern.
  //"directObject"  -> The direct object in the pattern
  //"preposition" -> Any preposition being used in the command
  //"error" -> true if unknown verb is used.
  //"errorMessage" -> A returned error message, may be handled elsewhere if wanted.
  //"angledCommand" -> true/false
  //"command" -> The entire command sentence.
			
   let parsedCommand = Parser.parse(command);
			
   console.log(parsedCommand);
			
   // This is where you could have an action class
   // switch case test for verb, or actionType and handle action.
			
});



Class
// This parser will return a parsedCommand object filled with usefull sentence parts and information.
// This is a very simple and lightweight parser that would be great for Browser or Electron/Node.js games.
// (opt) means optional word.

// License. 'Creative Commons' User may adapt, sell, provide as is, modify this code, and include in any project (commercial or otherwise.)
// Feel free to leave Jeremy Benson some credit if used.

// One issue with this parser is that prepositions are required, otherwise two named capture groups would be side by side.
// Another is in case 2 below, 'to' must be included in the command so regex does not get confused with case 01.
// May not be totally gramatically correct, direct and indirect objects may need to be swapped.

//BUG: 
//directObjects & indirectObjects need to be multi-word like 'red ball.'

// BUG: 'yell to the woman about the job' is matching in 01 instead of 02
// Need to tell 01 not to match if 'to' is present before ( the)?, or to be an exact match to pattern
function Parser() {

    this.parsedCommand = {
        "actionType": null,
        "verb": null,
        "indirectObject": null,
        "directObject": null,
        "preposition": null,
        "error": false,
        "errorMessage": "none",
        "angledCommand": false,
        "command": ""

    };

    this.prepositionList = ['on',
        'off',
        'under',
        'over',
        'above',
        'around',
        'beside',
        'in',
        'out',
        'down',
        'up',
        'with',
        'across',
        'from',
        'at',
        'to',
        'for',
        'about',
        'open',
        'opened',
        'close',
        'closed',
        'shut'
    ];

    this.commandArray = [];

    this.verbList = [];

}

Parser.prototype.parse = function(command) {


    if (this.parsedCommand.angledCommand == false) {
        // commands are fed in like 'get the lamp'
        this.parsedCommand.command = command;
        this.commandArray = command.split(" ");

    } else {
        // commands are fed in like <get the lamp>
        // so strip the first and last character of the command

        // end angledCommand process
    }

    switch (this.commandArray[0]) {

        default:
            // prepositions: (on|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)
            // Prepositions help make regex queries unique, due to placement in sentence. Due to this preposition will not be a named capture group.
            //01 <verb> the(opt) <indirectObj> <preposition> the(opt) <directObj> 
            //02 <verb> to the(opt) <indirectObj> <preposition> the(opt) <directObj> 
            //03 <verb> <preposition> the(opt) <directObj> 
            //04 <verb> the(opt) <directObj> <preposition>
            //05 <verb> the(opt) <directObj>
            //06 <verb>

            //01 'push the ball under the table', 'ask the woman about the ball'
            //02 'yell to the woman about the job', even 'yell woman about job'
            //03 'push over the statue' 'look under the ledge'
            //04 'twist the cap off' 'jam the door open' 
            //05 'turn the key'
            //06 'dance'

            if (/(?<verb>[^ $]*)( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.test(command)) {
                alert("1");
                const matches = /(?<verb>[^ $]*)( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.exec(command);
                this.parsedCommand.actionType = "<verb><indirectObject><preposition><directObj>";
                this.parsedCommand.verb = matches.groups.verb;
                this.parsedCommand.directObject = matches.groups.directObject;
                this.parsedCommand.indirectObject = matches.groups.indirectObject;
                this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);

            } else if (/(?<verb>[^ $]*) to( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.test(command)) {
                alert("2");
                const matches = /(?<verb>[^ $]*)( to)?( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.exec(command);
                this.parsedCommand.actionType = "<verb>to<indirectObj><preposition><directObj>";

                this.parsedCommand.verb = matches.groups.verb;
                this.parsedCommand.directObject = matches.groups.directObject;
                this.parsedCommand.indirectObject = matches.groups.indirectObject;
                this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);

            } else if (/(?<verb>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.test(command)) {
                alert("3");
                const matches = /(?<verb>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.exec(command);
                this.parsedCommand.actionType = "<verb><preposition><directObj>";
                this.parsedCommand.verb = matches.groups.verb;
                this.parsedCommand.directObject = matches.groups.directObject;
                this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);


            } else if (/(?<verb>[^ $]*)( the)? (?<directObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)/.test(command)) {

                alert("4");
                const matches = /(?<verb>[^ $]*)( the)? (?<directObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)/.exec(command);
                this.parsedCommand.actionType = "<verb><directObj><preposition>";
                this.parsedCommand.verb = matches.groups.verb;
                this.parsedCommand.directObject = matches.groups.directObject;
                this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);

            } else if (/(?<verb>[^ $]*)( the)? (?<directObject>[^ $]*)/.test(command)) {

                alert("5");
                const matches = /(?<verb>[^ $]*)( the)? (?<directObject>[^ $]*)/.exec(command);
                this.parsedCommand.actionType = "<verb><directObj>";
                this.parsedCommand.verb = matches.groups.verb;
                this.parsedCommand.directObject = matches.groups.directObject

            } else if (/(?<verb>[^ $]*)/.test(command)) {

                alert("6");
                // In this case single word commands may not be verbs, but will be stored in verb.
                // Ie: 'map' as apposed to 'run'
                const matches = /(?<verb>[^ $]*)/.exec(command);
                this.parsedCommand.actionType = "<verb>";
                this.parsedCommand.verb = matches.groups.verb;

            }

            // end parse
            break;

    }

    // test if verb in game, if not throw error
    this.parsedCommand = this.testVerb(this.parsedCommand.verb);
    return this.parsedCommand;

    // end parse function
};

// This function will return preposition used in command
// remove function if not needed
Parser.prototype.prepositionFetch = function(command, commandArray) {
    // every command version except 05 and 06 have a preposition.
    let preposition = "";

    for (let i = 0; i <= this.prepositionList.length - 1; i++) {

        if (commandArray.indexOf(this.prepositionList[i]) >= 0) {

            preposition = this.prepositionList[i];

        }

    }

    return preposition;
    // end preposition fetch
}

Parser.prototype.setErrorMessage = function(message) {

    this.parsedCommand.errorMessage = message;

}

Parser.prototype.setAngledCommand = function(state) {

    this.parsedCommand.angledCommand = state;

}

Parser.prototype.setVerbList = function(verbs) {

    this.verbList = verbs;

}

Parser.prototype.testVerb = function(verb) {

    // test if verb is used in game

    if (this.verbList.indexOf(verb) <= -1) {

        this.parsedCommand.actionType = null;
        this.parsedCommand.verb = "unkown";
        this.parsedCommand.directObject = null;
        this.parsedCommand.indirectObject = null;
        this.parsedCommand.error = true;

    }

    return this.parsedCommand;

}



Edit: Fixed lots of bugs.

This post has been edited by JeremyBenson11: 20 January 2022 - 07:28 PM


Is This A Good Question/Topic? 0
  • +

Replies To: Text Game Parser Help

#2 ArtificialSoldier   User is offline

  • D.I.C Lover
  • member icon

Reputation: 3147
  • View blog
  • Posts: 8,965
  • Joined: 15-January 14

Re: Text Game Parser Help

Posted 21 January 2022 - 06:50 PM

You can use a + or * for that list of words to do more than one. If you don't need a specific whitelist, and you have a closing tag, just match everything between the tags.
Was This Post Helpful? 0
  • +
  • -

#3 JeremyBenson11   User is offline

  • D.I.C Regular

Reputation: 1
  • View blog
  • Posts: 306
  • Joined: 10-January 14

Re: Text Game Parser Help

Posted 22 January 2022 - 11:36 AM

Hey, thanks ArtificalSoldier :)
Was This Post Helpful? 0
  • +
  • -

#4 ndc85430   User is offline

  • I think you'll find it's "Dr"
  • member icon

Reputation: 1065
  • Posts: 4,107
  • Joined: 13-June 14

Re: Text Game Parser Help

Posted 23 January 2022 - 12:03 AM

By "not fully tested", what do you mean? You at least have some level of automated tests in place, hopefully. If not, why not?
Was This Post Helpful? 0
  • +
  • -

#5 JeremyBenson11   User is offline

  • D.I.C Regular

Reputation: 1
  • View blog
  • Posts: 306
  • Joined: 10-January 14

Re: Text Game Parser Help

Posted 26 January 2022 - 01:24 AM

It won't let me edit the post, but here's the new code. This seems to be working 100%. The two cases couldn't be combined, but added a little bandaid. It works different though. No longer passing in a verb list.

"Yell to woman about the job" and "turn the knob on the machine" no longer get confused.

There is one last bug. indirectObj and directObj need to be multiword, any characters.

declarations
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title><!-- Insert your title here --></title>
</head>
<body>
    <!-- Insert your content here -->
	<script src="Parser.js"></script>
	<script src="jquery.js"></script>
	<script>
		$(document).ready(function(){
			
			
			// Parser needs a command to work with
			// set angledCommand with Parser.setAngledCommand(true) if commands are '<get lamp>' as apposed to 'get lamp'
			
			let command = "turn";
			Parser = new Parser();

			// parsing a command will return json data
			// "actionType" -> One of six action types to make action handling easier
			//"verb" -> The verb being used
			// "indirectObject" -> The indirect object in the pattern.
			//"directObject"  -> The direct object in the pattern
			//"preposition" -> Any preposition being used in the command
			//"error" -> true if parsing errors found.
			//"errorMessages" -> Json of errors.
			//"angledCommand" -> true/false
			//"command" -> The entire command sentence.
			
			//01 'push the ball under the table', 'ask the woman about the ball'
			//02 'yell to the woman about the job', even 'yell woman about job'
			//03 'push over the statue' 'look under the ledge'
			//04 'twist the cap off' 'jam the door open' 
			//05 'turn the key'
			//06 'dance'
			
			let parsedCommand = Parser.parse(command);
			console.log(parsedCommand);
			
			// This is where you could have an action class
			// switch case test for verb, or actionType and handle action.
			
		});
	</script>
</body>
</html>


class
// This parser will return a parsedCommand object filled with usefull sentence parts and information.
// This is a very simple and lightweight parser that would be great for Browser or Electron/Node.js games.
// (opt) means optional word.

// License. 'Creative Commons' User may adapt, sell, provide as is, modify this code, and include in any project (commercial or otherwise.)
// Feel free to leave Jeremy Benson some credit if used.

// One issue with this parser is that prepositions are required, otherwise two named capture groups would be side by side.
// Another is in case 2 below, 'to' must be included in the command so regex does not get confused with case 01.
// May not be totally gramatically correct, direct and indirect objects may need to be swapped.

//BUG: 
//directObjects & indirectObjects need to be multi-word like 'red ball.'
function Parser() {

    this.parsedCommand = {
        "actionType": null,
        "verb": null,
        "indirectObject": null,
        "directObject": null,
        "preposition": null,
        "error": false,
        "errorMessages": {},
        "angledCommand": false,
        "command": ""

    };

    this.prepositionList = ['on',
        'off',
        'under',
        'over',
        'above',
        'around',
        'beside',
        'in',
        'out',
        'down',
        'up',
        'with',
        'across',
        'from',
        'at',
        'to',
        'for',
        'about',
        'open',
        'opened',
        'close',
        'closed',
        'shut'
    ];

    this.commandArray = [];

}

Parser.prototype.parse = function(command) {


    if (this.parsedCommand.angledCommand == false) {
        // commands are fed in like 'get the lamp'
        this.parsedCommand.command = command;
        this.commandArray = command.split(" ");

    } else {
        // commands are fed in like <get the lamp>
        // so strip the first and last character of the command

        // end angledCommand process
    }

    switch (this.commandArray[0]) {

        default:
            // prepositions: (on|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)
            // Prepositions help make regex queries unique, due to placement in sentence. Due to this preposition will not be a named capture group.
            //01 <verb> the(opt) <indirectObj> <preposition> the(opt) <directObj> 
            //02 <verb> to the(opt) <indirectObj> <preposition> the(opt) <directObj> 
            //03 <verb> <preposition> the(opt) <directObj> 
            //04 <verb> the(opt) <directObj> <preposition>
            //05 <verb> the(opt) <directObj>
            //06 <verb>

            //01 'push the ball under the table', 'ask the woman about the ball'
            //02 'yell to the woman about the job'
            //03 'push over the statue' 'look under the ledge'
            //04 'twist the cap off' 'jam the door open' 
            //05 'turn the key'
            //06 'dance'

            // a little bandaid here. If 'to' not the second word and match here, run this one.
            if (/(?<verb>[^ $]*)( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.test(command)) {
                alert("1");
                if (this.commandArray[1] !== 'to') {

                    const matches = /(?<verb>[^ $]*)( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.exec(command);

                    this.parsedCommand.actionType = "<verb><indirectObject><preposition><directObj>";
                    this.parsedCommand.verb = matches.groups.verb;
                    this.parsedCommand.directObject = matches.groups.directObject;
                    this.parsedCommand.indirectObject = matches.groups.indirectObject;
                    this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);
                    //this.parsedCommand.errorMessages.add({"fun error": "This is a test error."});

                } else if (/(?<verb>[^ $]*) to( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.test(command)) {

                    alert("2");
                    const matches = /(?<verb>[^ $]*)( to)?( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.exec(command);
                    this.parsedCommand.actionType = "<verb>to<indirectObj><preposition><directObj>";

                    this.parsedCommand.verb = matches.groups.verb;
                    this.parsedCommand.directObject = matches.groups.directObject;
                    this.parsedCommand.indirectObject = matches.groups.indirectObject;
                    this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);

                }

            } else if (/(?<verb>[^ $]*) to( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.test(command)) {
                // a little bandaid here. If 'to' second word and match here, run this one.
                alert("2");
                if (this.commandArray[1] == 'to') {

                    const matches = /(?<verb>[^ $]*)( to)?( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.exec(command);
                    this.parsedCommand.actionType = "<verb>to<indirectObj><preposition><directObj>";

                    this.parsedCommand.verb = matches.groups.verb;
                    this.parsedCommand.directObject = matches.groups.directObject;
                    this.parsedCommand.indirectObject = matches.groups.indirectObject;
                    this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);

                } else if (/(?<verb>[^ $]*)( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.test(command)) {
                    alert("1");
                    const matches = /(?<verb>[^ $]*)( the)? (?<indirectObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.exec(command);

                    this.parsedCommand.actionType = "<verb><indirectObject><preposition><directObj>";
                    this.parsedCommand.verb = matches.groups.verb;
                    this.parsedCommand.directObject = matches.groups.directObject;
                    this.parsedCommand.indirectObject = matches.groups.indirectObject;
                    this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);
                    //this.parsedCommand.errorMessages.add({"fun error": "This is a test error."});

                }

            } else if (/(?<verb>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.test(command)) {
                alert("3");
                const matches = /(?<verb>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)( the)? (?<directObject>[^ $]*)/.exec(command);
                this.parsedCommand.actionType = "<verb><preposition><directObj>";
                this.parsedCommand.verb = matches.groups.verb;
                this.parsedCommand.directObject = matches.groups.directObject;
                this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);


            } else if (/(?<verb>[^ $]*)( the)? (?<directObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)/.test(command)) {

                alert("4");
                const matches = /(?<verb>[^ $]*)( the)? (?<directObject>[^ $]*) (on|off|under|over|above|around|beside|down|up|with|across|from|at|to|for|about|open|opened|close|closed|shut)/.exec(command);
                this.parsedCommand.actionType = "<verb><directObj><preposition>";
                this.parsedCommand.verb = matches.groups.verb;
                this.parsedCommand.directObject = matches.groups.directObject;
                this.parsedCommand.preposition = this.prepositionFetch(command, this.commandArray);

            } else if (/(?<verb>[^ $]*)( the)? (?<directObject>[^ $]*)/.test(command)) {

                alert("5");
                const matches = /(?<verb>[^ $]*)( the)? (?<directObject>[^ $]*)/.exec(command);
                this.parsedCommand.actionType = "<verb><directObj>";
                this.parsedCommand.verb = matches.groups.verb;
                this.parsedCommand.directObject = matches.groups.directObject

            } else if (/(?<verb>[^ $]*)/.test(command)) {

                alert("6");
                // In this case single word commands may not be verbs, but will be stored in verb.
                // Ie: 'map' as apposed to 'run'
                const matches = /(?<verb>[^ $]*)/.exec(command);
                this.parsedCommand.actionType = "<verb>";
                this.parsedCommand.verb = matches.groups.verb;

                // error handle, verb must be one word, not multiword

                if (this.commandArray.length >= 1) {

                    // there are too many words for this case to be error free, so throw error.
                    //this.parsedCommand.errorMessages.unshift({"verb error":"verb not understood"});
                    this.parsedCommand.actionType = null;
                    this.parsedCommand.verb = null;
                    this.parsedCommand.indirectObj = null;
                    this.parsedCommand.directObj = null;
                    this.parsedCommand.error = true;

                }

            }

            // end parse
            break;

    }


    return this.parsedCommand;

    // end parse function
};

// This function will return preposition used in command
// remove function if not needed
Parser.prototype.prepositionFetch = function(command, commandArray) {
    // every command version except 05 and 06 have a preposition.
    let preposition = "";

    for (let i = 0; i <= this.prepositionList.length - 1; i++) {

        if (commandArray.indexOf(this.prepositionList[i]) >= 0) {

            preposition = this.prepositionList[i];

        }

    }

    return preposition;
    // end preposition fetch
}

Parser.prototype.setAngledCommand = function(state) {

    this.parsedCommand.angledCommand = state;

}

This post has been edited by JeremyBenson11: 26 January 2022 - 01:26 AM

Was This Post Helpful? 0
  • +
  • -

#6 JeremyBenson11   User is offline

  • D.I.C Regular

Reputation: 1
  • View blog
  • Posts: 306
  • Joined: 10-January 14

Re: Text Game Parser Help

Posted 28 January 2022 - 09:07 AM

In a couple of weeks I'm going to get that if statement taken out, repair the regex, and finish a couple other small details. I think I'd like to supply a perfected version on Github. Probably in Javascript, C#, and PHP.
Was This Post Helpful? 0
  • +
  • -

#7 JeremyBenson11   User is offline

  • D.I.C Regular

Reputation: 1
  • View blog
  • Posts: 306
  • Joined: 10-January 14

Re: Text Game Parser Help

Posted 30 January 2022 - 08:24 PM

Done and done. Enjoy, for those that paid attention:

https://github.com/J...ext-game-parser
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1