// 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;