// Copyright 2019 Campbell Crowley. All rights reserved. // Author: Campbell Crowley (dev@campbellcrowley.com) const HungryGames = require('./HungryGames.js'); /** * @description A single battle in an Event. * @memberof HungryGames * @inner * @augments HungryGames~Event */ class Battle extends HungryGames.Event { /** * @description Create a single battle. * @param {string} message The message of this battle event. * @param {number} attacker The damage done to the attacker. * @param {number} victim The damage done to the victim. */ constructor(message, attacker, victim) { super(message); /** * Information about attacker. * * @public * @type {{damage: number}} */ this.attacker = {damage: attacker}; /** * Information about victim. * * @public * @type {{damage: number}} */ this.victim = {damage: victim}; } /** * The file path to read attacking left image. * * @private * @static * @readonly * @returns {string} Path relative to project root. */ static get _fistLeft() { return './img/fist_left.png'; } /** * The file path to read attacking right image. * * @private * @static * @readonly * @returns {string} Path relative to project root. */ static get _fistRight() { return './img/fist_right.png'; } /** * The file path to read attacking both directions image. * * @private * @static * @readonly * @returns {string} Path relative to project root. */ static get _fistBoth() { return './img/fist_both.png'; } /** * Make an event that contains a battle between players before the main event * message. * * @public * @static * @param {HungryGames~Player[]} affectedUsers All of the players involved in * the event. * @param {number} numVictim The number of victims in this event. * @param {number} numAttacker The number of attackers in this event. * @param {boolean} mention Should every player be mentioned when their name * comes up? * @param {HungryGames~GuildGame} game The GuildGame this battle is for. This * is for settings checking and fetching non-affected users. * @param {HungryGames~Battle[]} battles Array of all possible battle events * to choose from. * @returns {HungryGames~NormalEvent} The event that was created. */ static finalize( affectedUsers, numVictim, numAttacker, mention, game, battles) { const useNicknames = game.options.useNicknames; const outcomeMessage = battles.outcomes[Math.floor(Math.random() * battles.outcomes.length)]; const finalEvent = HungryGames.NormalEvent.finalize( outcomeMessage, affectedUsers, numVictim, numAttacker, 'dies', 'nothing', game); finalEvent.attacker.killer = true; finalEvent.battle = true; finalEvent.state = 0; finalEvent.attacks = []; const userHealth = new Array(affectedUsers.length).fill(0); const maxHealth = game.options.battleHealth * 1; let numAlive = numVictim; let duplicateCount = 0; let lastAttack = {index: 0, attacker: 0, victim: 0, flipRoles: false}; const startMessage = battles.starts[Math.floor(Math.random() * battles.starts.length)]; const battleString = '**A battle has broken out!**'; let healthText = affectedUsers .map( (obj, index) => '`' + (useNicknames ? (obj.nickname || obj.name) : obj.name) + '`: ' + Math.max((maxHealth - userHealth[index]), 0) + 'HP') .sort() .join(', '); finalEvent.attacks.push( HungryGames.NormalEvent.finalize( `${battleString}\n${startMessage}\n${healthText}`, affectedUsers, numVictim, numAttacker, 'nothing', 'nothing', game)); let loop = 0; do { loop++; if (loop > 1000) { throw new Error('INFINITE LOOP'); } const eventIndex = Math.floor(Math.random() * battles.attacks.length); const eventTry = battles.attacks[eventIndex]; const attackerEventDamage = eventTry.attacker.damage * 1; const victimEventDamage = eventTry.victim.damage * 1; const flipRoles = Math.random() > 0.7; const attackerIndex = Math.floor(Math.random() * numAttacker) + numVictim; if (loop == 999) { console.log( 'Failed to find valid event for battle!\n', eventTry, flipRoles, userHealth, '\nAttacker:', attackerIndex, '\nUsers:', affectedUsers.length, '\nAlive:', numAlive, '\nFINAL:', finalEvent); } if ((!flipRoles && userHealth[attackerIndex] + attackerEventDamage >= maxHealth) || (flipRoles && userHealth[attackerIndex] + victimEventDamage >= maxHealth)) { continue; } let victimIndex = Math.floor(Math.random() * numAlive); let count = 0; for (let i = 0; i < numVictim; i++) { if (userHealth[i] < maxHealth) count++; if (count == victimIndex + 1) { victimIndex = i; break; } } const victimDamage = (flipRoles ? attackerEventDamage : victimEventDamage); const attackerDamage = (!flipRoles ? attackerEventDamage : victimEventDamage); userHealth[victimIndex] += victimDamage; userHealth[attackerIndex] += attackerDamage; if (userHealth[victimIndex] >= maxHealth) { numAlive--; } if (lastAttack.index == eventIndex && lastAttack.attacker == attackerIndex && lastAttack.victim == victimIndex && lastAttack.flipRoles == flipRoles) { duplicateCount++; } else { duplicateCount = 0; } lastAttack = { index: eventIndex, attacker: attackerIndex, victim: victimIndex, flipRoles: flipRoles, }; healthText = affectedUsers .map((obj, index) => { const health = Math.max((maxHealth - userHealth[index]), 0); const prePost = health === 0 ? '~~' : ''; return prePost + '`' + (useNicknames ? (obj.nickname || obj.name) : obj.name) + '`: ' + health + 'HP' + prePost; }) .sort() .join(', '); let messageText = eventTry.message; if (duplicateCount > 0) { messageText += ' x' + (duplicateCount + 1); } const newEvent = HungryGames.NormalEvent.finalize( battleString + '\n' + messageText + '\n' + healthText, [ affectedUsers[flipRoles ? attackerIndex : victimIndex], affectedUsers[flipRoles ? victimIndex : attackerIndex], ], 1, 1, !flipRoles && userHealth[victimIndex] >= maxHealth ? 'dies' : 'nothing', flipRoles && userHealth[victimIndex] >= maxHealth ? 'dies' : 'nothing', game); if (victimDamage && attackerDamage) { newEvent.icons.splice(1, 0, {url: Battle._fistBoth}); } else if (attackerDamage) { newEvent.icons.splice( 1, 0, {url: flipRoles ? Battle._fistLeft : Battle._fistRight}); } else if (victimDamage) { newEvent.icons.splice( 1, 0, {url: flipRoles ? Battle._fistRight : Battle._fistLeft}); } finalEvent.attacks.push(newEvent); } while (numAlive > 0); return finalEvent; } } module.exports = Battle;