Skip to content
Permalink
Browse files
Support predefined games (/sniper, /minigun)
  • Loading branch information
RussellLVP committed Jul 5, 2020
1 parent 73e920b commit 1ee4753349eefe7082788b5f602319762c364322
Showing 5 changed files with 103 additions and 10 deletions.
@@ -0,0 +1,10 @@
{
"name": "Minigun Madness",
"command": "minigun",
"goal": "Take out your enemies with a minigun before they spot you.",

"settings": {
"fights/location": "Acter Nuclear Power Plant",
"fights/pickups": false
}
}
@@ -0,0 +1,10 @@
{
"name": "Sniper Madness",
"command": "sniper",
"goal": "Take out your enemies while trying to remain hidden yourself.",

"settings": {
"fights/location": "Acter Nuclear Power Plant",
"fights/pickups": false
}
}
@@ -10,12 +10,10 @@ describe('FightGame', (it, beforeEach) => {
let russell = null;
let settings = null;

beforeEach(async() => {
beforeEach(async () => {
const feature = server.featureManager.loadFeature('fights');
const finance = server.featureManager.loadFeature('finance');

feature.registry_.initialize();

games = server.featureManager.loadFeature('games');
gunther = server.playerManager.getById(/* Gunther= */ 0);
russell = server.playerManager.getById(/* Russell= */ 1);
@@ -50,10 +48,10 @@ describe('FightGame', (it, beforeEach) => {
}

it('should be able to pick random spawn positions in individual games', async (assert) => {
assert.isTrue(server.commandManager.hasCommand('newfights'));
assert.isTrue(server.commandManager.hasCommand('sniper'));

assert.isTrue(await gunther.issueCommand('/newfights'));
assert.isTrue(await russell.issueCommand('/newfights'));
assert.isTrue(await gunther.issueCommand('/sniper'));
assert.isTrue(await russell.issueCommand('/sniper'));

await server.clock.advance(settings.getValue('games/registration_expiration_sec') * 1000);
await runGameLoop();
@@ -2,18 +2,30 @@
// Use of this source code is governed by the MIT license, a copy of which can
// be found in the LICENSE file.

import { CommandBuilder } from 'components/command_manager/command_builder.js';
import { FightGame } from 'features/fights/fight_game.js';
import { FightLocationDescription } from 'features/fights/fight_location_description.js';
import { FightLocation } from 'features/fights/fight_location.js';
import { GameCommandParams } from 'features/games/game_command_params.js';

import { clone } from 'base/clone.js';

// Directory in which the fight commands aer located.
const kCommandDirectory = 'data/fights/';

// Directory in which the fight locations are located.
const kLocationDirectory = 'data/fights/locations/';

// Keeps track of the fight locations, commands and configuration available on the server. Is
// responsible for loading it, and doing the initial initialization.
export class FightRegistry {
#commands_ = null;
#games_ = null;
#locations_ = null;

constructor() {
constructor(games) {
this.#commands_ = new Set();
this.#games_ = games;
this.#locations_ = new Map();
}

@@ -29,6 +41,7 @@ export class FightRegistry {
// the Fights class for production usage, but has to be called manually when running tests.
initialize() {
this.initializeLocations();
this.initializeCommands();
}

// Initializes the locations in which fights can take place. Each is based on a structured game
@@ -42,9 +55,59 @@ export class FightRegistry {
}
}

// Initializes the commands which act as shortcuts to the available fighting game configuration,
// to make it easier for players to start a game.
initializeCommands() {
for (const filename of glob(kCommandDirectory, '.*fights[\\\\/][^\\\\/]*\.json$')) {
const configuration = JSON.parse(readFile(kCommandDirectory + filename));
const settings = new Map();

for (const [ identifier, value ] of Object.entries(configuration.settings))
settings.set(identifier, value);

settings.set('internal/goal', configuration.goal);
settings.set('internal/name', configuration.name);

this.registerCommand(configuration.command, settings);
}
}

// ---------------------------------------------------------------------------------------------

// Registers a command with the server, following the given configuration. Activating it will
// immediately start the sign-up flow of the configured game. This mimics the command syntax
// that the games features exposes by default.
registerCommand(command, settings) {
server.commandManager.buildCommand(command)
.sub('custom')
.build(FightRegistry.prototype.onCommand.bind(this, settings, /* custom= */ true))
.sub(CommandBuilder.NUMBER_PARAMETER)
.build(FightRegistry.prototype.onCommand.bind(this, settings, /* custom= */ false))
.build(FightRegistry.prototype.onCommand.bind(this, settings, /* custom= */ false));

this.#commands_.add(command);
}

// Called when the |player| has issued a command. We'll construct the game command parameters
// and use their API in order to formally request starting the game.
onCommand(settings, customise, player, registrationId) {
const params = new GameCommandParams();

params.customise = !!customise;
params.registrationId = registrationId;
params.settings = clone(settings);

return this.#games_().startGame(FightGame, player, params);
}

// ---------------------------------------------------------------------------------------------

dispose() {
for (const command of this.#commands_)
server.commandManager.removeCommand(command);

this.#commands_ = null;

this.#locations_.clear();
this.#locations_ = null;
}
@@ -27,7 +27,7 @@ export default class Fights extends Feature {

// Has knowledge of the locations and commands (+presets) of games that are available on the
// server. Will be immediately initialized, even for tests.
this.registry_ = new FightRegistry();
this.registry_ = new FightRegistry(this.games_);
this.registry_.initialize();

// Register all games known to this feature when the Settings feature has been loaded, as
@@ -43,14 +43,16 @@ export default class Fights extends Feature {
const minimumPlayers = this.settings_().getValue('games/fight_minimum_players');

this.games_().registerGame(FightGame, {
name: 'Deathmatch Fight',
name: Fights.prototype.composeGameName.bind(this),
goal: 'Defeat all other players to win the fight.',
command: 'newfights',

minimumPlayers,
maximumPlayers: 16,

settings: [
// Option: Name (internal, string)
new Setting('internal', 'name', Setting.TYPE_STRING, null, 'Display name'),

// Option: Location (string)
new Setting(
'fights', 'location', [ ...this.registry_.locations.keys() ], defaultLocation,
@@ -63,6 +65,16 @@ export default class Fights extends Feature {
}, this.registry_);
}

// Composes the display name of the fighting game that's about to be started. This should be set
// in the internal values for predefined games, but will otherwise be composed of the settings
// that the game is being started with.
composeGameName(settings) {
const base = settings.get('internal/name') ?? 'Deathmatch Fight';

// TODO: Specialise based on the given |settings|.
return base;
}

dispose() {
this.registry_.dispose();
this.registry_ = null;

0 comments on commit 1ee4753

Please sign in to comment.