Jump to content
MakeWebGames

[SPADE] Creating Your First Mod


gamble

Recommended Posts

Summary of Steps:

  1. Create Client Side script (Javascript)
  2. Add Client Side script to assets/js/config.js
  3. Create Client side API links (if needed)
  4. Include Client Side script and API links to index.html
  5. Create Server Side API (PHP)

Create Client Side Script (Javascript):

'use strict';

class NewPage {
	constructor() {
	}

	generateMenuLink(button) {
	    button.div.setAttribute('jailRestricted', 'true')
		button.div.classList.add('menu-button')

		button.icon.classList.add('menu-button-icon', 'fa', 'fa-hospital')

		button.text.classList.add('large')
		button.text.innerHTML = 'Hospital'

		button.div.append(button.icon)
		button.div.append(button.text)
		button.div.append(button.label)
	}
	
	generateContent(content) {
	}

	start(params) {
		params.menuLink.div.classList.add('menu-button-active')
	}

	stop(params) {
		params.menuLink.div.classList.remove('menu-button-active')
	}
}

Above is an example of the basic code that can be found in a Client sided script. Now lets go through each function and some notes of some thing inside

  • constructor **OPTIONAL** - This is just a basic javascript function that is called when the class is created.
    • Throughout the engine this class is used to initiate variables and API link classes
  • generateMenuLink **OPTIONAL** - This will automatically create a link in the main menu...you just need to specify some things
    • button.div - this is the entire button - we associate the  menu-button class with it because it is our basic styling
      • Notice the atttribute "jailRestricted" is set to true...this means that the menu link is hidden when in jail
        • We also have a "hospitalRestricted" attribute that can be set
    • button.text - this is what text appears in the button
      • Notice the "large" class - The "large" class is only visible on desktop, it is hidden on mobile
        • A "small" class exists to hide on desktop, and only show on mobile
    • button.icon - this is what icon will appear on the button
      • The engine uses Font Awesome, so take a look and find a icon you like for your page!
        • Be smart with the icon choice...if you choose to hide the text on mobile, they will only see the icon
  • generateContent - This is the content generate on load of the page 
    • DO NOT USE THE DATABASE IN THIS FUNCTION
      • Trying to access the database in this section can result in an error occurring thus, making the site not work
    • Typically in the engine we create a class variable and store the "content" parameter so we can draw the page later
  • start - this is called whenever the menu link is clicked, or the module is loaded in any other way
    • You can pass parameters to this by creating a link - for example if we wanted to load a profile on a link click:
      • modules.load("profile", {id: userID})
    • I always use this to "activate" a button with styling
  • stop - this is called whenever the "start" function of another module is called AKA whenever the page is stopped being used
    • I always use this to "deactivate" a button with styling

From here you are free to create whatever you want

Add Client Side script to assets/js/config.js:

  1. Open assets/js.config.js
  2. You will see a object created called "config" with a list of modules stored in the "modules" key
  3. Add your new module to this list
    1. "newPage": new Page(new NewPage()),
      1. Update the key of this with an alias you would like to use
        1. This is used with modules.load()
      2. Within the new Page() add the class name you used in your client sided script

Create Client Side API links (Javascript):

  1. Go to assets/js/apis
  2. Create a new file with an appropriate name to what your module is
'use strict';
/*
 *
 */

class ModuleAPI {
    constructor(){
        this._file = 'module.php'
    }
	
	getSomething(){
	    return {
			url: api.buildURL(this._file+`/getSomething`),
			headers: api.authHeaders(),
			method: 'GET',
		}
	}

	postSomething(data){
	    return {
			url: api.buildURL(this._file+`/postSomething`),
			headers: api.authHeaders(),
			method: 'POST',
			body: JSON.stringify({'data': data}),
		}
	}
}

There are a couple things to note here:

  • getSomething - will request {DOMAIN}/module.php/getSomething
    • We will cover what this does on the server side later
    • We need to pass the headers in order for the users data to be validated on the server
    • No data can be passed with this method...
  • postSomething - will request {DOMAIN}/module.php/postSomething
    • Will will cover what this does on the server side later
    • Again we need to pass headers - this is necessary for everything we want to do in game...we need to make sure you are logged in
    • Data is passed in the body
    • We can use this to request info about a certain user, update information, or whatever
  • Basically this file is how the UI communicates with the server

Include Client Side Script and API Links script to index.html:

  • This is simple enough so we will not go into much detail
  • Open index.html
  • Add the links to the new files you just created
  • Make sure API is before the Page script so that you can access the API script from the Page script
    • Where you should put the links is marked in index.html

Create Server Side API (PHP):

  • Open the /api/ folder
  • Create a new file for the module
  • Create the API call responses
<?
require_once('core.php');

$getSomething = function(){
    global $api;
    
    $loggedInUser = new User($api->getHeader('user'));
    $loggedInUser->enforceHospitalRestriction();
    
    global $db;
    
    $db->query("SELECT * FROM users");
	$allUsers = $db->fetchAll();

    $api->output($allUsers);
};

$postSomething = function(){
    global $api;
    
    $loggedInUser = new User($api->getHeader('user'));
    $loggedInUser->enforceJailHospitalRestriction();
    
    $userID = intval($api->getPostData()['data']);

	global $db;

	$db->query("SELECT * FROM users WHERE id = :userID", ['userID' => $userID]);
	$singleUser = $db->fetch();
	
	$api->output($singleUser);
};

$installer = function(){
    $installer = new Installer();
    $installer->checkTable('users');
};

$api->addRoute('/getSomething/', $getSomething);
$api->addRoute('/postSomething/', $postSomething);
?>
  • As you can see, the server side of this engine is stupid simple (as it should be!)
  • In this example you can see how we fetch and validate the user in each API call. The User class auto validates the user. If the user appears to not be valid it will automatically terminate the script and return and error forcing a log out of the UI
    • The User class is built into the engine and can be used to fetch ANY user. It will auto detect if the logged in user is being requested and run the appropriate checks, but any user can be fetched using new User($userID). 
      • The users database fields can be accessed using $userClassVeriable->username or whatever you are trying to fetch Example:
        • $user = new User($userID)
        • $user->username - would return the username of the user 
        • $user->money - would return the money of the user
  • Another thing you can see is we enforce a restriction for jail and hospital! This functionality is built in with the following functions
    • enforceJailHospitalRestriction - both Jail and Hospital
    • enforceHospitalRestriction - Hospital only
    • enforceJailRestriction - Jail only
  • In the postSomething function you can see we fetch the "data" we passed in our API links script using:
    • $api->getPostData()['data']
  • $api->output()
    • This is what returns a message to the UI. We can return a string, array, whatever we cant. It will all be encoded into json, and auto-decoded on the UI through the QReq class
    • When this function is called it kills the entire script using the PHP exit() function. Meaning whenever you see this the function will terminate at that point - no need to return anything after or exit afterwards.
    • As an alternative if we run into an issue we can easily send a error message using $api->outputError(errorCodes, otherParameters)
      • otherParameters can be sent as a string or array, this will be passed to the UI automatically and you can use it with the error handlers located in assets/js/core/errors.js - you can add your own codes here and handle them how you please
        • Parameters will be stored in errorResponse.info in erros.js
      • List of built in Error codes:
        • 999 - this is a general error that will be displayed in a pop-up (can be used to say we do not have enough money or whatever)
          • You can specify a string to be displayed in otherParameters
        • 400 - forbidden access for when someone is trying to access something they should like staff panel
          • This will log user out and log the event...
        • 997 - User tried to access something they should have while in jail - redirects to jail and displays error
        • 998 - User tried to access something they should have while in hospital - redirects to hospital and displays error
  • $api->addRoute()
    • This is how we tell the server what to look for. In the example above we are looking for /putSomething/ and /getSomething/ on the end of our file. For example if this is module.php these routes will be used when we try to access module.php/getSomething/ or module.php/postSomething/ 
      • The second parameter is obviously the function we are trying to call when the URL is visited
  • The $installer function in this example is unused...this will be covered in a different post. This is for creating a module for other users to download and use 🙂

 

 

 

Thats pretty much all there is to it. We need a Page script, a client sided API script, and a server sided API script that tells the server what to do with information or what to fetch for us.

 

One last quick note. In order to access an API link from the Page script use the following to make a request:

QReq.request((new ModuleAPI()).postSomething(userID)).then(resp => {
  //what we do with what we return using the $api->output() function in the server code
})

The above will call our postSomething function for the API links script we created earlier...it will pass the parameter we require as you can see.

 

If anything is confusing or you need some assistance feel free to drop a reply below and ill gladly help clear up any confusion. I will update this as clarification is needed!

Reserved for updates or other information

 

Preparing for release so making documentation. As of writing this the engine is not released

  • Like 1
Link to comment
Share on other sites

Instead of using these functions: I think an idea would be function or level. enforceRestrict(1,2,4,ect)

Could add to list inside restrict code for other future restricts without adding new code / replacing in future.

 

On another note, all looks good so far. I can't wait to create some mods myself.

$loggedInUser->enforceHospitalRestriction()
Link to comment
Share on other sites

25 minutes ago, Sim said:

Instead of using these functions: I think an idea would be function or level. enforceRestrict(1,2,4,ect)

Could add to list inside restrict code for other future restricts without adding new code / replacing in future.

 

On another note, all looks good so far. I can't wait to create some mods myself.

$loggedInUser->enforceHospitalRestriction()

This is good input. I chose the way I did due to readability, but a function that runs parallel to these that's more dynamic isn't a bad idea at all! Easy to had too, so I may slap that in before release!

 

Thanks for the feedback!

  • Like 1
Link to comment
Share on other sites

6 hours ago, Sim said:

Can I be one of the 1st people to get spade like aka now?  To start creating mods

Right now is a terrible time for me to release anything. I am reworking crons entirely so some key functionality will be missing...but i will let you in on a secret: I am planning to release the engine tomorrow. Not sure if it will be tomorrow morning or evening. I still have to message @Dave to try and get a SPADE Engine category in the marketplace, but im hoping to wrap up the code tonight, tomorrow morning, or tomorrow evening.

Link to comment
Share on other sites

2 hours ago, rockwood said:

please avoid this to use and try to use DI (Dependency Injection)

I respect that you're trying to improve this, but for simplicity reasons I specifically chose to use globals. DI would be complicated to someone who is not too familiar with OOP. Not to mention that since this is PHP it's not that big of a deal since the server side of things is essentially recreated with every API call - it's not persistent between calls.

 

If you could provide me an actual example why it would be beneficial in something like this I'll gladly reconsider, but it seems pretty pointless for server side code that runs for under 1 second at a time and is already self decoupled by the API calls themselves.

Link to comment
Share on other sites

7 minutes ago, Sim said:

I really dislike the file structure for adding mods. I know its to late to change it now or it would be considered v2. But I will contribute a few mods sooner or later.

To be completely honest you could simplify it down to a PHP file and a javascript file. You could theoretical do the API calls in the same Javascript file as the page. I just do it this way to have complete seperation and uniformity.

 

If you have an file structure idea let me know and ill see what i can do

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...