// Copyright 2019 Campbell Crowley. All rights reserved.
// Author: Campbell Crowley (dev@campbellcrowley.com)
/**
* Option base.
*
* @memberof HungryGames~DefaultOptions
* @inner
*/
class Option {
/**
* @description Default option constructor.
* @param {*} value Value of this option.
* @param {?string} [comment=null] Comment/description for the user about this
* option.
* @param {?string} [category=null] Category this option falls under for
* showing user.
*/
constructor(value, comment = null, category = null) {
this._value = value;
if (comment != null && typeof comment !== 'string') {
throw new Error('Comment is not a string');
}
this._comment = comment;
if (category != null && typeof category !== 'string') {
throw new Error('Category is not a string');
}
this._category = category;
}
/**
* @description Get the value of this option.
* @public
* @type {*}
*/
get value() {
return this._value;
}
/**
* @description Get the description of this option.
* @public
* @type {?string}
*/
get comment() {
return this._comment;
}
/**
* @description Get the category of this option.
* @public
* @type {?string}
*/
get category() {
return this._category;
}
/**
* @returns {string[]} Array of all keys for this option.
*/
get keys() {
const all = Object.entries(Object.getOwnPropertyDescriptors(this));
const output = [];
for (const one of all) {
output.push(one[0].slice(1));
}
return output;
}
/**
* @returns {HungryGames~DefaultOptions~Option} All variables of this option
* fetched through their getters.
*/
get entries() {
const keys = this.keys;
const output = {};
for (const k of keys) {
output[k] = this[k];
}
return output;
}
}
/**
* Number option.
*
* @memberof HungryGames~DefaultOptions
* @inner
* @augments HungryGames~DefaultOptions~Option
*/
class NumberOption extends Option {
/**
* @description Stores a number value with optional range.
* @param {number} value Value of this option.
* @param {?string} [comment=null] Comment/description for the user about this
* option.
* @param {?string} [category=null] Category this option falls under for
* showing user.
* @param {{min: number, max: number}} [range] Allowed range of this value.
*/
constructor(value, comment, category, range) {
if (typeof value !== 'number' || isNaN(value)) {
throw new Error('Value is not a number');
}
super(value, comment, category);
if (range) {
this._range = {min: range.min, max: range.max};
} else {
this._range = null;
}
}
/**
* @description Get the range of allowable values for this option.
* @returns {?{min: number, max: number}} Allowable range of values.
*/
get range() {
return this._range;
}
}
/**
* Boolean option.
*
* @memberof HungryGames~DefaultOptions
* @inner
* @augments HungryGames~DefaultOptions~Option
*/
class BooleanOption extends Option {
/**
* @description Stores a boolean.
* @param {boolean} value Value of this option.
* @param {?string} [comment=null] Comment/description for the user about this
* option.
* @param {?string} [category=null] Category this option falls under for
* showing user.
*/
constructor(value, comment, category) {
if (typeof value !== 'boolean') throw new Error('Value is not a boolean');
super(value, comment, category);
}
}
/**
* Object option. Shallow copies passed value and range.
*
* @memberof HungryGames~DefaultOptions
* @inner
* @augments HungryGames~DefaultOptions~Option
*/
class ObjectOption extends Option {
/**
* @description Stores any object. Shallow copies the object using
* object.assign.
* @param {object} value Value of this option.
* @param {?string} [comment=null] Comment/description for the user about this
* option.
* @param {?string} [category=null] Category this option falls under for
* showing user.
* @param {{min: number, max: number}} [range] Range of allowable values for
* this option.
*/
constructor(value, comment, category, range) {
if (typeof value !== 'object') throw new Error('Value is not an object');
value = Object.assign({}, value);
super(value, comment, category);
if (range) {
this._range = {min: range.min, max: range.max};
} else {
this._range = null;
}
}
/**
* @description Get the range of allowable values for this option.
* @returns {?{min: number, max: number}} Allowable range of values.
*/
get range() {
return this._range;
}
}
/**
* One of multiple choices option.
*
* @memberof HungryGames~DefaultOptions
* @inner
* @augments HungryGames~DefaultOptions~Option
*/
class SelectOption extends Option {
/**
* @description Allows an option from a set of possible values.
* @param {string} value Value of this option.
* @param {?string} [comment=null] Comment/description for the user about this
* option.
* @param {?string} [category=null] Category this option falls under for
* showing user.
* @param {string[]} values Possible values.
*/
constructor(value, comment, category, values) {
if (!values || !Array.isArray(values)) {
throw new Error('Values is not array of strings');
}
let included = false;
for (let i = 0; i < values.length; i++) {
if (typeof values[i] !== 'string') {
throw new Error('Values is not array of strings');
} else if (values[i] === value) {
included = true;
}
}
if (!included) throw new Error('Value is not in given values');
super(value, comment, category);
this._values = values.slice(0);
}
/**
* @description Get possible values for this option.
* @returns {string[]} Array of possible values.
*/
get values() {
return this._values;
}
}
/**
* Default options for a HungryGames.
*
* @memberof HungryGames
* @inner
*/
class DefaultOptions {
/**
* @description Creates a set of default options for a HungryGames.
*/
constructor() {
this._bloodbathOutcomeProbs = new ObjectOption(
{kill: 30, wound: 6, thrive: 8, revive: 0, nothing: 56},
'Relative probabilities of choosing an event with each outcome. This ' +
'is for the bloodbath events.',
'probabilities');
this._playerOutcomeProbs = new ObjectOption(
{kill: 22, wound: 4, thrive: 8, revive: 6, nothing: 60},
'Relative probabilities of choosing an event with each outcome. This ' +
'is for normal daily events.',
'probabilities');
this._arenaOutcomeProbs = new ObjectOption(
{kill: 64, wound: 10, thrive: 5, revive: 6, nothing: 15},
'Relative Probabilities of choosing an event with each outcome. This ' +
'is for the special arena events.',
'probabilities');
this._arenaEvents = new BooleanOption(
true,
'Are arena events possible. (Events like wolf mutts, or a volcano ' +
'erupting.)',
'probabilities');
this._includeBots = new BooleanOption(
false, 'Should bots be included in the games. If this is false, bots ' +
'cannot be added manually.',
'players');
this._excludeNewUsers = new BooleanOption(
false, 'Should new users who join your server be excluded from the ' +
'games by default. True will add all new users to the blacklist, ' +
'false will put all new users into the next game automatically.',
'players');
this._allowNoVictors = new BooleanOption(
false,
'Should it be possible to end a game without any winners. If true, ' +
'it is possible for every player to die, causing the game to end ' +
'with everyone dead. False forces at least one winner.',
'other');
this._bleedDays = new NumberOption(
2, 'Number of days a user can bleed before they can die.', 'other');
this._battleHealth = new NumberOption(
5, 'The amount of health each user gets for a battle.', 'other',
{min: 1, max: 10});
this._teamSize = new NumberOption(
0, 'Maximum size of teams when automatically forming teams. 0 to ' +
'disable teams',
'players');
this._teammatesCollaborate = new SelectOption(
'always',
'Will teammates work together. If disabled, teammates can kill ' +
'eachother, and there will only be 1 victor. If enabled, ' +
'teammates cannot kill eachother, and the game ends when one TEAM' +
' is remaining, not one player. Untilend means teammates work ' +
'together until the end of the game, this means only there will ' +
'be only 1 victor.',
'players', ['disabled', 'always', 'untilend']);
this._useEnemyWeapon = new BooleanOption(
false,
'This will allow the attacker in an event to use the victim\'s ' +
'weapon against them.',
'players');
this._mentionVictor = new BooleanOption(
false,
'Should the victor of the game (can be team), be tagged/mentioned ' +
'so they get notified?',
'messages');
this._mentionAll = new SelectOption(
'disabled',
'Should a user be mentioned every time something happens to them ' +
'in the game? (can be disabled, for all events, or for when the ' +
'user dies)',
'messages', ['disabled', 'all', 'death']);
this._mentionEveryoneAtStart = new BooleanOption(
false, 'Should @everyone be mentioned when the game is started?',
'messages');
this._useNicknames = new BooleanOption(
true, 'Should we use user\'s custom server nicknames instead of ' +
'their account username? Names only change when a new game is ' +
'created.',
'messages');
this._delayEvents = new NumberOption(
3500, 'Delay in milliseconds between each event being printed.',
'other', {min: 1500, max: 30000}, 'time');
this._delayDays = new NumberOption(
7000, 'Delay in milliseconds between each day being printed.', 'other',
{min: 2500, max: 129600000}, // 1.5 days
'time');
this._probabilityOfArenaEvent = new NumberOption(
0.25, 'Probability each day that an arena event will happen.',
'probabilities', {min: 0, max: 1}, 'percent');
this._probabilityOfBleedToDeath = new NumberOption(
0.5, 'Probability that after bleedDays a player will die. If they ' +
'don\'t die, they will heal back to normal.',
'probabilities', {min: 0, max: 1}, 'percent');
this._probabilityOfBattle = new NumberOption(
0.05,
'Probability of an event being replaced by a battle between two ' +
'players.',
'probabilities', {min: 0, max: 1}, 'percent');
this._probabilityOfUseWeapon = new NumberOption(
0.75,
'Probability of each player using their weapon each day if they ' +
'have one.',
'probabilities', {min: 0, max: 1}, 'percent');
this._eventAvatarSizes = new ObjectOption(
{avatar: 64, underline: 4, gap: 4},
'The number of pixels each player\'s avatar will be tall and wide, ' +
'the underline status height, and the gap between each avatar. ' +
'This is for all normal events and arena event messages.',
'messages', {min: 0, max: 512});
this._battleAvatarSizes = new ObjectOption(
{avatar: 32, underline: 4, gap: 4},
'The number of pixels each player\'s avatar will be tall and wide, ' +
'the underline status height, and the gap between each avatar. ' +
'This is for each battle turn.',
'messages', {min: 0, max: 512});
this._victorAvatarSizes = new ObjectOption(
{avatar: 80, underline: 4, gap: 4},
'The number of pixels each player\'s avatar will be tall and wide, ' +
'the underline status height, and the gap between each avatar. ' +
'This is when announcing the winners of the game.',
'messages', {min: 0, max: 512});
this._numDaysShowDeath = new NumberOption(
-1,
'The number of days after a player has died to show them as dead in' +
' the status list after each day. -1 will always show dead ' +
'players. 0 will never show dead players. 1 will only show them ' +
'for the day they died. 2 will show them for 2 days.',
'messages', {min: -1, max: 100});
this._showLivingPlayers = new BooleanOption(
true,
'Include the living players in the status updates. Instead of only ' +
'wounded or dead players.',
'messages');
this._customEventWeight = new NumberOption(
2, 'The relative weight of custom events. 2 means custom events are ' +
'twice as likely to be chosen.',
'probabilities', {min: 0, max: 1000});
this._anonForceOutcome = new BooleanOption(
false, 'Forced outcomes will use existing events instead of saying ' +
'"The game makers" did it.',
'other');
this._disableOutput = new BooleanOption(
false, 'Debugging purposes only. I mean, you can enable it, but it ma' +
'kes the games really boring. Up to you ¯\\_(ツ)_/¯',
'other');
}
/**
* @description Get bloodbathOutcomeProbs.
* @returns {ObjectOption} Prob opts.
*/
get bloodbathOutcomeProbs() {
return this._bloodbathOutcomeProbs;
}
/**
* @description Get playerOutcomeProbs.
* @returns {ObjectOption} Prob opts.
*/
get playerOutcomeProbs() {
return this._playerOutcomeProbs;
}
/**
* @description Get arenaOutcomeProbs.
* @returns {ObjectOption} Prob opts.
*/
get arenaOutcomeProbs() {
return this._arenaOutcomeProbs;
}
/**
* @description Get arenaEvents.
* @returns {BooleanOption} Option value.
*/
get arenaEvents() {
return this._arenaEvents;
}
/**
* @description Get includeBots.
* @returns {BooleanOption} Option value.
*/
get includeBots() {
return this._includeBots;
}
/**
* @description Get excludeNewUsers.
* @returns {BooleanOption} Option value.
*/
get excludeNewUsers() {
return this._excludeNewUsers;
}
/**
* @description Get allowNoVictors.
* @returns {BooleanOption} Option value.
*/
get allowNoVictors() {
return this._allowNoVictors;
}
/**
* @description Get bleedDays.
* @returns {NumberOption} Option value.
*/
get bleedDays() {
return this._bleedDays;
}
/**
* @description Get battleHealth.
* @returns {NumberOption} Option value.
*/
get battleHealth() {
return this._battleHealth;
}
/**
* @description Get teamSize.
* @returns {NumberOption} Option value.
*/
get teamSize() {
return this._teamSize;
}
/**
* @description Get teammatesCollaborate.
* @returns {BooleanOption} Option value.
*/
get teammatesCollaborate() {
return this._teammatesCollaborate;
}
/**
* @description Get useEnemyWeapon.
* @returns {BooleanOption} Option value.
*/
get useEnemyWeapon() {
return this._useEnemyWeapon;
}
/**
* @description Get mentionVictor.
* @returns {BooleanOption} Option value.
*/
get mentionVictor() {
return this._mentionVictor;
}
/**
* @description Get mentionAll.
* @returns {SelectOption} Option value.
*/
get mentionAll() {
return this._mentionAll;
}
/**
* @description Get mentionEveryoneAtStart.
* @returns {BooleanOption} Option value.
*/
get mentionEveryoneAtStart() {
return this._mentionEveryoneAtStart;
}
/**
* @description Get useNicknames.
* @returns {BooleanOption} Option value.
*/
get useNicknames() {
return this._useNicknames;
}
/**
* @description Get delayEvents.
* @returns {NumberOption} Option value.
*/
get delayEvents() {
return this._delayEvents;
}
/**
* @description Get delayDays.
* @returns {NumberOption} Option value.
*/
get delayDays() {
return this._delayDays;
}
/**
* @description Get probabilityOfArenaEvent.
* @returns {NumberOption} Option value.
*/
get probabilityOfArenaEvent() {
return this._probabilityOfArenaEvent;
}
/**
* @description Get probabilityOfBleedToDeath.
* @returns {NumberOption} Option value.
*/
get probabilityOfBleedToDeath() {
return this._probabilityOfBleedToDeath;
}
/**
* @description Get probabilityOfBattle.
* @returns {NumberOption} Option value.
*/
get probabilityOfBattle() {
return this._probabilityOfBattle;
}
/**
* @description Get probabilityOfUseWeapon.
* @returns {NumberOption} Option value.
*/
get probabilityOfUseWeapon() {
return this._probabilityOfUseWeapon;
}
/**
* @description Get eventAvatarSizes.
* @returns {ObjectOption} Option value.
*/
get eventAvatarSizes() {
return this._eventAvatarSizes;
}
/**
* @description Get battleAvatarSizes.
* @returns {ObjectOption} Option value.
*/
get battleAvatarSizes() {
return this._battleAvatarSizes;
}
/**
* @description Get victorAvatarSizes.
* @returns {ObjectOption} Option value.
*/
get victorAvatarSizes() {
return this._victorAvatarSizes;
}
/**
* @description Get numDaysShowDeath.
* @returns {NumberOption} Option value.
*/
get numDaysShowDeath() {
return this._numDaysShowDeath;
}
/**
* @description Get showLivingPlayers.
* @returns {BooleanOption} Option value.
*/
get showLivingPlayers() {
return this._showLivingPlayers;
}
/**
* @description Get customEventWeight.
* @returns {NumberOption} Option value.
*/
get customEventWeight() {
return this._customEventWeight;
}
/**
* @description Get anonForceOutcome.
* @returns {BooleanOption} Option value.
*/
get anonForceOutcome() {
return this._anonForceOutcome;
}
/**
* @description Get disableOutput.
* @returns {BooleanOption} Option value.
*/
get disableOutput() {
return this._disableOutput;
}
/**
* @returns {string[]} Array of all default option keys.
*/
get keys() {
const all = Object.entries(Object.getOwnPropertyDescriptors(this));
const output = [];
for (const one of all) {
if (one[1].value instanceof Option) {
output.push(one[0].slice(1));
}
}
return output;
}
/**
* @returns {object<HungryGames~DefaultOptions~Option>} All options in this
* object.
*/
get entries() {
const keys = this.keys;
const output = {};
for (const k of keys) {
output[k] = this[k].entries;
}
return output;
}
}
module.exports = DefaultOptions;