Web-based implementation of a moddable Advance Wars engine
- Install node.js. I used a prebuilt installer because it's a bit more convenient on Windows, but using
nvm
is easier on Linux. I'm using the latest version as of the time of writing, which isv23.3.0
. - Clone the repository, or download it, either by clicking on the link or downloading the repository from the
< > Code
menu to the top right. - Open a terminal, navigate to the root of your cloned or downloaded repository, then run
npm install
. This will download the dependencies required for the game's server (the game's client acquires its dependencies via CDNs). - Run the game using
npm start
, then open the printed link (by default, it ishttp://localhost:80
) in a web browser.
To choose which port to run the game on, you can provide the --port PORT_NUMBER
command-line argument, e.g. npm start -- --port 8080
. The --
in between start
and --port
ensures the --port
command-line argument is passed to the game and not the npm start
command. NOTE: on PowerShell, you'll need to run npm start --- --port 8080
instead due to this issue.
In place of npm start
, you can also run the game directly using node server.mjs
, node server.mjs --port 8080
, etc.
The game's core codebase (/src
, /shared
, /public
) is pretty bare. It is supposed to act as a framework with which you can create Advance Wars-like games. The default-map-pack
will contain the code and assets that let you play any of the original four games on the GBA and the DS (primarily focusing on Dual Strike for now), but using the framework, you can develop any 2D turn-based strategy game that's played on a tiled map.
Map packs are made up of the following folders and files. Technically only the root-level exports.mjs
script is mandatory, but on its own the exports script is not very useful.
map-pack
maps
public
assets
components
menuName
subComponents
example.mjs
root.mjs
etc.
locales
en
translation.json
de
translation.json
etc.
main.mjs
src
exports.mjs
This folder stores all of the binary .map
files that are loaded by the game when the pack is loaded. This folder can have as many sub-folders as the developer desires, however, all .map
files must be located within this root-level maps
folder. .map
files have a consistent format across all map packs, but the values stored within them will hold different means based on the map pack they are within, so you should not expect .map
files from one pack to be loadable with another.
This folder contains all of the front-end code and assets. When a map pack is loaded, the game's web server will map all GET
requests that begin with /pack
in their URLs to this public
folder (as if they were a static folder). It's important to note that front-end assets largely dictate how the game looks and will have no bearing on how the game runs on the server. Additionally, most of the front-end code will consist of React components that dictate how the UI of the game will be structured and how it will function.
It is not mandatory to store all of your assets in this folder; the game lets you organise your front-end files in however which way you please (with a few exceptions) but it is customary to place them all in a single assets
folder. Specifically, assets will include sprite sheets, audio files, fonts, etc.
It is also customary to organize all of your React components in a components
folder, with each menu of the game occupying a root.mjs
component, containing many subComponents
as necessary. The only restriction is that only one menu can be displayed at a time, otherwise you're free to organise your UI code in any way. For example, if you need to show two menus at once, you'll need to combine them as two components under one root menu component.
The front-end Controller
mandates that translation files be stored in this root-level locales
folder, where each language has its own folder containing at least a single translation.json
script. Your map pack can have any I18Next namespaces you like (this means additional *.json
scripts), but the language keys (e.g. en
, de
, etc.) are shared across all map packs.
If you need to run some code whenever a client connects to the server (such as opening your map pack's "main" menu where appropriate), you'll need to write a root-level main.mjs
script. This gets imported by the WebWars.html
page on initial load. This is before the client has connected with the server, so you'll usually want to run your code once the Connected
event arrives:
// /public and /shared are accessible directly from the root of the game's URL (/).
import controller from "/controller.mjs";
// This event is emitted whenever the client successfully connects with the server,
// whether that be on initial load, or when the client reconnects after connection
// drops.
controller.updateComponentWhen("Connected", () =>
// For example, this tells the client to open the "main menu" root component,
// but only if they don't already have a menu open (which is what the false
// argument achieves). That last part is important, as it prevents the map
// pack from moving the player away from another menu when they refresh the
// browser.
controller.command("OpenMenu", "/pack/components/mainMenu/root.mjs", false)
);
Your back-end code can be stored anywhere outside of the public
folder, but it's usually a good idea to bundle it together under a root-level src
folder. The back-end code is where all of your game logic will sit. Your client-facing commands must validate all of the data they receive: your client-side code could be perfect, but you must assume that a client could tamper with any data it sends. The only data that you can rely on to be safe are session keys, as they are internally managed by the server.
This root-level script's purpose is to tell the server what back-end components it can load. Its existence is mandatory, but all of its exports are technically optional:
If an array is exported using this identifier, the server will iterate through it and instantiate each given type as a model within the system. This is the mechanism via which you can receive events from the system, emit your own events, and register client-facing commands that let clients interact with your game. As such, each element of the array must be a class that inherits from Model
, and not constructed objects (let the server construct the models).
These exported objects tell the server what sorts of units, terrains, etc. your map pack supports, and what their string identifiers are. This will directly influence how to load the binary map files given by the map pack (as such files contain aforementioned identifiers).
If the default
export of the module is a function, it will be invoked once all of the module's exported models have been added to the server. The function will accept a reference to the server's controller instance, which will let you perform commands, emit events, etc. when your map pack is initially loaded (but before the accompanying MapPackLoaded
event is emitted).