diff --git a/.eslintrc.js b/.eslintrc.js index cbc5ead3..9daf7b7a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,7 @@ module.exports = { }, plugins: ['@typescript-eslint', 'import'], rules: { + 'arrow-body-style': 'off', 'class-methods-use-this': 'off', 'no-plusplus': 'off', 'no-useless-constructor': 'off', diff --git a/src/arena/BattleLog/index.ts b/src/arena/BattleLog/index.ts deleted file mode 100644 index 9628fc2a..00000000 --- a/src/arena/BattleLog/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BattleLog'; diff --git a/src/arena/BattleLog/utils/format-message.ts b/src/arena/BattleLog/utils/format-message.ts deleted file mode 100644 index 869c8bd8..00000000 --- a/src/arena/BattleLog/utils/format-message.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Message } from '../BattleLog'; -import { formatAction } from './format-action'; -import { formatError } from './format-error'; -import { formatExp } from './format-exp'; - -export function formatMessage(msgObj: Message) { - if ('message' in msgObj) { - return formatError(msgObj); - } - return `${formatAction(msgObj)}${formatExp(msgObj)}`; -} diff --git a/src/arena/Constuructors/AoeDmgMagicConstructor.ts b/src/arena/Constuructors/AoeDmgMagicConstructor.ts index 6f8b67f3..f0e371d5 100644 --- a/src/arena/Constuructors/AoeDmgMagicConstructor.ts +++ b/src/arena/Constuructors/AoeDmgMagicConstructor.ts @@ -71,7 +71,6 @@ export abstract class AoeDmgMagic extends DmgMagic { dmgType: this.dmgType, }; - game.addHistoryDamage(args); - game.battleLog.success(args); + game.recordOrderResult(args); } } diff --git a/src/arena/Constuructors/DmgMagicConstructor.ts b/src/arena/Constuructors/DmgMagicConstructor.ts index 8bafbd45..5b533f94 100644 --- a/src/arena/Constuructors/DmgMagicConstructor.ts +++ b/src/arena/Constuructors/DmgMagicConstructor.ts @@ -114,7 +114,6 @@ export abstract class DmgMagic extends Magic { dmgType: this.dmgType, }; - game.addHistoryDamage(args); - game.battleLog.success(args); + game.recordOrderResult(args); } } diff --git a/src/arena/Constuructors/HealMagicConstructor.ts b/src/arena/Constuructors/HealMagicConstructor.ts index 6015b08d..e6aaa4d0 100644 --- a/src/arena/Constuructors/HealMagicConstructor.ts +++ b/src/arena/Constuructors/HealMagicConstructor.ts @@ -70,8 +70,7 @@ export abstract class Heal { // this.backToLife(); this.next(); } catch (e) { - const { battleLog } = this.params.game; - battleLog.fail(e); + game.recordOrderResult(e); } } @@ -130,8 +129,7 @@ export abstract class Heal { * Функция положительного прохождения */ next(): void { - const { target, initiator } = this.params; - const { battleLog } = this.params.game; + const { target, initiator, game } = this.params; const exp: ExpArr[number] = { id: initiator.id, name: initiator.nick, @@ -147,6 +145,6 @@ export abstract class Heal { effect: this.status.val, hp: target.stats.val('hp'), }; - battleLog.success(args); + game.recordOrderResult(args); } } diff --git a/src/arena/Constuructors/LongDmgMagicConstructor.ts b/src/arena/Constuructors/LongDmgMagicConstructor.ts index 11da3e06..015a35fb 100644 --- a/src/arena/Constuructors/LongDmgMagicConstructor.ts +++ b/src/arena/Constuructors/LongDmgMagicConstructor.ts @@ -45,8 +45,7 @@ export abstract class LongDmgMagic extends DmgMagic { this.next(); this.postRun(initiator, target, game); } catch (failMsg) { - const { battleLog } = this.params.game; - battleLog.fail(failMsg); + game.recordOrderResult(failMsg); } finally { this.resetStatus(); } @@ -91,7 +90,7 @@ export abstract class LongDmgMagic extends DmgMagic { this.checkTargetIsDead(); // проверка трупов в длительных магиях this.longNext(initiator, target); } catch (e) { - game.battleLog.fail(e); + game.recordOrderResult(e); } }); const filteredLongArray = longArray.filter((item) => item.duration !== 0); @@ -139,7 +138,7 @@ export abstract class LongDmgMagic extends DmgMagic { dmgType: this.dmgType, msg: this.longCustomMessage?.bind(this), }; - game.addHistoryDamage(dmgObj); - game.battleLog.success(dmgObj); + + game.recordOrderResult(dmgObj); } } diff --git a/src/arena/Constuructors/LongMagicConstructor.ts b/src/arena/Constuructors/LongMagicConstructor.ts index 7170db0c..731dfcba 100644 --- a/src/arena/Constuructors/LongMagicConstructor.ts +++ b/src/arena/Constuructors/LongMagicConstructor.ts @@ -49,8 +49,7 @@ export abstract class LongMagic extends CommonMagic { this.next(); this.postRun(initiator, target, game); } catch (failMsg) { - const { battleLog } = this.params.game; - battleLog.fail(failMsg); + game.recordOrderResult(failMsg); } finally { this.resetStatus(); } @@ -91,7 +90,7 @@ export abstract class LongMagic extends CommonMagic { this.checkTargetIsDead(); // проверка трупов в длительных магиях this.longNext(initiator, target); } catch (e) { - game.battleLog.fail(e); + game.recordOrderResult(e); } }); const filteredLongArray = longArray.filter((item) => item.duration !== 0); @@ -121,16 +120,16 @@ export abstract class LongMagic extends CommonMagic { } next(): void { - const { battleLog } = this.params.game; + const { initiator, target, game } = this.params; const args: MagicNext = { exp: this.status.exp, action: this.displayName, actionType: 'magic', - target: this.params.target.nick, - initiator: this.params.initiator.nick, + target: target.nick, + initiator: initiator.nick, msg: this.customMessage?.bind(this), }; - battleLog.success(args); + game.recordOrderResult(args); } /** @@ -139,7 +138,7 @@ export abstract class LongMagic extends CommonMagic { * @param target */ longNext(initiator: Player, target: Player): void { - const { battleLog } = this.params.game; + const { game } = this.params; const args: LongMagicNext = { exp: this.status.exp, action: this.displayName, @@ -148,7 +147,7 @@ export abstract class LongMagic extends CommonMagic { initiator: initiator.nick, msg: this.longCustomMessage?.bind(this), }; - battleLog.success(args); + game.recordOrderResult(args); } } diff --git a/src/arena/Constuructors/MagicConstructor.ts b/src/arena/Constuructors/MagicConstructor.ts index 969203d7..2253a393 100644 --- a/src/arena/Constuructors/MagicConstructor.ts +++ b/src/arena/Constuructors/MagicConstructor.ts @@ -80,10 +80,9 @@ export abstract class Magic { this.next(); } catch (failMsg) { - const bl = this.params.game.battleLog; // @fixme прокидываем ошибку выше для длительных кастов if (this.isLong) throw (failMsg); - bl.fail(failMsg); + game.recordOrderResult(failMsg); } finally { this.resetStatus(); } @@ -299,7 +298,7 @@ export abstract class Magic { * @todo тут нужен вывод требуемых параметров */ next(): void { - const { battleLog } = this.params.game; - battleLog.success(this.getNextArgs()); + const { game } = this.params; + game.recordOrderResult(this.getNextArgs()); } } diff --git a/src/arena/Constuructors/PhysConstructor.js b/src/arena/Constuructors/PhysConstructor.js index 76be9d3e..f869559d 100644 --- a/src/arena/Constuructors/PhysConstructor.js +++ b/src/arena/Constuructors/PhysConstructor.js @@ -170,11 +170,10 @@ class PhysConstructor { * Функция агрегации данных после выполннения действия */ next(failMsg) { - const { initiator, target } = this.params; - const { game } = this.params; + const { initiator, target, game } = this.params; const weapon = initiator.weapon ? arena.items[initiator.weapon.code] : {}; if (failMsg) { - game.battleLog.fail({ ...failMsg, weapon }); + game.recordOrderResult({ ...failMsg, weapon }); } else { const msg = { exp: this.status.exp, @@ -187,8 +186,7 @@ class PhysConstructor { weapon, dmgType: 'physical', }; - game.addHistoryDamage(msg); - game.battleLog.success(msg); + game.recordOrderResult(msg); } } diff --git a/src/arena/Constuructors/SkillConstructor.ts b/src/arena/Constuructors/SkillConstructor.ts index 08466f4e..32cc57c3 100644 --- a/src/arena/Constuructors/SkillConstructor.ts +++ b/src/arena/Constuructors/SkillConstructor.ts @@ -68,7 +68,7 @@ export abstract class Skill { this.next(); this.getExp(initiator); } catch (failMsg) { - game.battleLog.fail(failMsg); + game.recordOrderResult(failMsg); } } @@ -111,15 +111,17 @@ export abstract class Skill { * Успешное прохождение скила и отправка записи в BattleLog */ next(): void { + const { initiator, target, game } = this.params; const args: SkillNext = { exp: this.baseExp, action: this.displayName, actionType: 'skill', - target: this.params.target.nick, - initiator: this.params.initiator.nick, + target: target.nick, + initiator: initiator.nick, msg: this.customMessage?.bind(this), }; - this.params.game.battleLog.success(args); + + game.recordOrderResult(args); } /** diff --git a/src/arena/Constuructors/utils/index.ts b/src/arena/Constuructors/utils/index.ts new file mode 100644 index 00000000..5e2b0dbe --- /dev/null +++ b/src/arena/Constuructors/utils/index.ts @@ -0,0 +1,13 @@ +import type { FailArgs, SuccessArgs } from '../types'; + +export const isSuccessResult = (result: SuccessArgs | FailArgs): result is SuccessArgs => { + return !('message' in result); +}; + +export const isSuccessDamageResult = (result: SuccessArgs | FailArgs) => { + if (isSuccessResult(result)) { + return ('dmg' in result); + } + + return false; +}; diff --git a/src/arena/GameService.ts b/src/arena/GameService.ts index c0c1b020..cf12797f 100644 --- a/src/arena/GameService.ts +++ b/src/arena/GameService.ts @@ -3,13 +3,13 @@ import { createGame } from '@/api/game'; import { Profs } from '../data'; import * as channelHelper from '../helpers/channelHelper'; import type { Game } from '../models/game'; -import { BattleLog } from './BattleLog'; import type { LongItem } from './Constuructors/LongMagicConstructor'; import { engine } from './engineService'; -import HistoryService, { historyObj } from './HistoryService'; +import { HistoryService, type HistoryItem } from './HistoryService'; +import { LogService } from './LogService'; import type * as magics from './magics'; import OrderService from './OrderService'; -import PlayersService, { Player } from './PlayersService'; +import PlayersService, { type Player } from './PlayersService'; import { RoundService, RoundStatus } from './RoundService'; import testGame from './testGame'; import arena from './index'; @@ -37,7 +37,7 @@ export default class GameService { players: PlayersService; round = new RoundService(); orders = new OrderService(); - battleLog = new BattleLog(); + logger = new LogService(); history = new HistoryService(); longActions: Partial> = {}; info!: Game; @@ -87,7 +87,7 @@ export default class GameService { } get checkRoundDamage(): boolean { - return !!this.history.getRoundDamage(this.round.count).length; + return !!this.history.hasDamageForRound(this.round.count); } /** @@ -224,11 +224,12 @@ export default class GameService { } } - addHistoryDamage(dmgObj: Omit): void { - this.history.addDamage({ - ...dmgObj, - round: this.round.count, - }); + recordOrderResult(item: HistoryItem) { + this.history.addHistoryForRound(item, this.round.count); + } + + getRoundResults() { + return this.history.getHistoryForRound(this.round.count); } /** @@ -281,10 +282,9 @@ export default class GameService { this.flags.global = {}; } - async sendMessages(): Promise { - const messages = this.battleLog.format(); - await channelHelper.sendBattleLogMessages(messages); - this.battleLog.reset(); + async sendMessages(messages: HistoryItem[]): Promise { + console.log(messages); + await this.logger.sendBattleLog(messages); } /** @@ -300,7 +300,7 @@ export default class GameService { break; } case RoundStatus.END_ROUND: { - void this.sendMessages(); + void this.sendMessages(this.getRoundResults()); this.sortDead(); this.players.reset(); this.orders.reset(); diff --git a/src/arena/HistoryService.js b/src/arena/HistoryService.js deleted file mode 100644 index 19a50a0e..00000000 --- a/src/arena/HistoryService.js +++ /dev/null @@ -1,50 +0,0 @@ -const _ = require('lodash'); - -/** - * @typedef {Object} historyObj - * @property {string} initiator - * @property {string} target - * @property {string} dmgType - * @property {number} dmg - * @property {string} actionType - * @property {number} round -*/ - -/** - * Класс для хранения истории выполненных заказов - */ - -class HistoryService { - constructor() { - /** @type {historyObj[]} */ - this.damages = []; - /** @type {historyObj[]} */ - this.fails = []; - } - - /** - * @param {historyObj} dmgObj - * @param {number} round - */ - addDamage(dmgObj) { - const historyObj = _.pick(dmgObj, [ - 'initiator', - 'target', - 'dmgType', - 'dmg', - 'actionType', - 'round', - ]); - this.damages.push(historyObj); - } - - /** - * @param {number} round - * @returns {historyObj[]} - */ - getRoundDamage(round) { - return this.damages.filter((damage) => damage.round === round); - } -} - -module.exports = HistoryService; diff --git a/src/arena/HistoryService/HistoryService.ts b/src/arena/HistoryService/HistoryService.ts new file mode 100644 index 00000000..00b6ab9b --- /dev/null +++ b/src/arena/HistoryService/HistoryService.ts @@ -0,0 +1,27 @@ +import type { FailArgs, SuccessArgs } from '@/arena/Constuructors/types'; +import { isSuccessDamageResult } from '@/arena/Constuructors/utils'; + +export type HistoryItem = SuccessArgs | FailArgs; + +/** + * Класс для хранения истории выполненных заказов + */ +export class HistoryService { + private roundsHistoryMap = new Map(); + + getHistoryForRound(round: number) { + return this.roundsHistoryMap.get(round) ?? []; + } + + addHistoryForRound(item: HistoryItem, round: number) { + const roundHistory = this.getHistoryForRound(round); + + roundHistory.push(item); + this.roundsHistoryMap.set(round, roundHistory); + } + + hasDamageForRound(round: number) { + const roundHistory = this.getHistoryForRound(round); + return roundHistory.some(isSuccessDamageResult); + } +} diff --git a/src/arena/HistoryService/index.ts b/src/arena/HistoryService/index.ts new file mode 100644 index 00000000..f70c6e4c --- /dev/null +++ b/src/arena/HistoryService/index.ts @@ -0,0 +1 @@ +export * from './HistoryService'; diff --git a/src/arena/BattleLog/BattleLog.test.ts b/src/arena/LogService/LogService.test.ts similarity index 77% rename from src/arena/BattleLog/BattleLog.test.ts rename to src/arena/LogService/LogService.test.ts index f230ea52..0df969bd 100644 --- a/src/arena/BattleLog/BattleLog.test.ts +++ b/src/arena/LogService/LogService.test.ts @@ -1,68 +1,68 @@ -import { BattleLog } from '@/arena/BattleLog'; +import { LogService } from '@/arena/LogService'; import TestUtils from '@/utils/testUtils'; // npm t src/arena/BattleLog/BattleLog.test.ts describe('BattleLog', () => { - const battlelog = new BattleLog(); + let messages: string[] = []; + const writer = (data: string[]) => { + messages = data; + }; + + const logService = new LogService(writer); afterEach(() => { - battlelog.reset(); + messages = []; }); it('should format magic messages', async () => { - battlelog.success({ + await logService.sendBattleLog([{ actionType: 'magic', action: 'Magic', initiator: 'Player 1', target: 'Player 2', exp: 25, - }); - - battlelog.success({ + }, { actionType: 'magic', action: 'Magic', initiator: 'Player 1', target: 'Player 2', exp: 25, effect: 10, - }); - - battlelog.fail({ + }, + { actionType: 'magic', action: 'Magic', initiator: 'Player 1', target: 'Player 2', message: 'GOD_FAIL', - }); + }]); - expect(battlelog.format()).toMatchSnapshot(); + expect(messages).toMatchSnapshot(); }); it('should format skill messages', async () => { - battlelog.success({ + await logService.sendBattleLog([{ actionType: 'skill', action: 'Skill', initiator: 'Player 1', target: 'Player 2', exp: 25, - }); - - battlelog.fail({ + }, { actionType: 'skill', action: 'Skill', initiator: 'Player 1', target: 'Player 2', message: 'SKILL_FAIL', - }); + }]); - expect(battlelog.format()).toMatchSnapshot(); + expect(messages).toMatchSnapshot(); }); it('should format phys messages', async () => { const weapon = await TestUtils.getWeapon(); - battlelog.success({ + await logService.sendBattleLog([{ actionType: 'phys', action: 'Phys', initiator: 'Player 1', @@ -72,22 +72,20 @@ describe('BattleLog', () => { exp: 25, dmg: 10, hp: 90, - }); - - battlelog.fail({ + }, { actionType: 'phys', action: 'Phys', initiator: 'Player 1', target: 'Player 2', weapon, message: 'DEF', - }); + }]); - expect(battlelog.format()).toMatchSnapshot(); + expect(messages).toMatchSnapshot(); }); it('should format dmg-magic messages', async () => { - battlelog.success({ + await logService.sendBattleLog([{ actionType: 'dmg-magic', action: 'Dmg Magic', initiator: 'Player 1', @@ -96,21 +94,20 @@ describe('BattleLog', () => { exp: 25, dmg: 10, hp: 90, - }); - - battlelog.fail({ + }, + { actionType: 'dmg-magic', action: 'Phys', initiator: 'Player 1', target: 'Player 2', message: 'NO_MANA', - }); + }]); - expect(battlelog.format()).toMatchSnapshot(); + expect(messages).toMatchSnapshot(); }); it('should format heal-magic messages', async () => { - battlelog.success({ + await logService.sendBattleLog([{ actionType: 'heal-magic', action: 'Heal Magic', initiator: 'Player 1', @@ -118,21 +115,19 @@ describe('BattleLog', () => { exp: 10, hp: 110, effect: 10, - }); - - battlelog.fail({ + }, { actionType: 'heal-magic', action: 'Heal Magic', initiator: 'Player 1', target: 'Player 2', message: 'SILENCED', - }); + }]); - expect(battlelog.format()).toMatchSnapshot(); + expect(messages).toMatchSnapshot(); }); it('should format heal messages with rigth order', async () => { - battlelog.success({ + await logService.sendBattleLog([{ actionType: 'heal', action: 'Heal', initiator: 'Player 1', @@ -142,9 +137,7 @@ describe('BattleLog', () => { }], hp: 110, effect: 10, - }); - - battlelog.success({ + }, { actionType: 'heal', action: 'Heal', initiator: 'Player 3', @@ -154,65 +147,55 @@ describe('BattleLog', () => { }], hp: 120, effect: 10, - }); - - battlelog.fail({ + }, { actionType: 'heal', action: 'Heal', initiator: 'Player 1', target: 'Player 2', message: 'SILENCED', - }); + }]); - expect(battlelog.format()).toMatchSnapshot(); + expect(messages).toMatchSnapshot(); }); it('should format long magic messages with rigth order', async () => { - battlelog.success({ + await logService.sendBattleLog([{ actionType: 'magic-long', action: 'Magic 1', initiator: 'Player 2', target: 'Player 1', exp: 25, - }); - - battlelog.success({ + }, { actionType: 'magic-long', action: 'Magic 1', initiator: 'Player 1', target: 'Player 2', exp: 25, - }); - - battlelog.success({ + }, { actionType: 'magic-long', action: 'Magic 1', initiator: 'Player 1', target: 'Player 2', exp: 25, - }); - - battlelog.success({ + }, { actionType: 'magic-long', action: 'Magic 2', initiator: 'Player 1', target: 'Player 2', exp: 25, - }); - - battlelog.success({ + }, { actionType: 'magic-long', action: 'Magic 2', initiator: 'Player 2', target: 'Player 1', exp: 25, - }); + }]); - expect(battlelog.format()).toMatchSnapshot(); + expect(messages).toMatchSnapshot(); }); it('should format long dmg magic messages with rigth order', async () => { - battlelog.success({ + await logService.sendBattleLog([{ actionType: 'dmg-magic-long', action: 'Magic 1', initiator: 'Player 2', @@ -221,9 +204,7 @@ describe('BattleLog', () => { exp: 25, dmg: 25, hp: 75, - }); - - battlelog.success({ + }, { actionType: 'dmg-magic-long', action: 'Magic 1', initiator: 'Player 1', @@ -232,9 +213,7 @@ describe('BattleLog', () => { exp: 25, dmg: 25, hp: 75, - }); - - battlelog.success({ + }, { actionType: 'dmg-magic-long', action: 'Magic 1', initiator: 'Player 1', @@ -243,9 +222,7 @@ describe('BattleLog', () => { exp: 25, dmg: 25, hp: 50, - }); - - battlelog.success({ + }, { actionType: 'dmg-magic-long', action: 'Magic 2', initiator: 'Player 1', @@ -254,9 +231,7 @@ describe('BattleLog', () => { exp: 25, dmg: 25, hp: 25, - }); - - battlelog.success({ + }, { actionType: 'dmg-magic-long', action: 'Magic 2', initiator: 'Player 2', @@ -265,21 +240,21 @@ describe('BattleLog', () => { exp: 25, dmg: 25, hp: 50, - }); + }]); - expect(battlelog.format()).toMatchSnapshot(); + expect(messages).toMatchSnapshot(); }); it('should use custom message', async () => { - battlelog.success({ + await logService.sendBattleLog([{ actionType: 'skill', action: 'Skill', exp: 10, initiator: 'Initiator', target: 'Target', msg: (args) => `custom ${args.initiator}|${args.target}|${args.action}|${args.actionType}`, - }); + }]); - expect(battlelog.format()).toMatchSnapshot(); + expect(messages).toMatchSnapshot(); }); }); diff --git a/src/arena/BattleLog/BattleLog.ts b/src/arena/LogService/LogService.ts similarity index 60% rename from src/arena/BattleLog/BattleLog.ts rename to src/arena/LogService/LogService.ts index 6ec8762e..2e43eb84 100644 --- a/src/arena/BattleLog/BattleLog.ts +++ b/src/arena/LogService/LogService.ts @@ -1,4 +1,7 @@ import type { SuccessArgs, FailArgs } from '@/arena/Constuructors/types'; +import { isSuccessResult } from '@/arena/Constuructors/utils'; +import type { HistoryItem } from '@/arena/HistoryService'; +import { sendBattleLogMessages } from '@/helpers/channelHelper'; import { joinLongDmgMessages, joinLongMessages, formatMessage, joinHealMessages, } from './utils'; @@ -6,24 +9,46 @@ import { export type Message = SuccessArgs | FailArgs type Formatter = (message: Message) => string; +type Writer = (data: string | string[]) => void | Promise /** * Класс вывода данных в battlelog * @todo WIP класс в стадии формирования * @see https://trello.com/c/qxnIM1Yq/17 */ -export class BattleLog { +export class LogService { private messages: Message[] = []; constructor( + private writer: Writer = sendBattleLogMessages, private formatter: Formatter = formatMessage, ) {} + async sendBattleLog(history: HistoryItem[]) { + history.forEach((item) => { + if (isSuccessResult(item)) { + this.success(item); + } else { + this.fail(item); + } + }); + + try { + const x = this.format(); + console.log(x); + await this.writer(x); + } catch (e) { + console.log('sendBattleLog: ', e); + } finally { + this.reset(); + } + } + /** * Удачный проход action * @param message сообщение */ - success(message: SuccessArgs) { + private success(message: SuccessArgs) { switch (message.actionType) { case 'heal': this.messages = joinHealMessages(this.messages, message); @@ -44,15 +69,15 @@ export class BattleLog { * Неудачный проход action * @param message сообщение */ - fail(message: FailArgs): void { + private fail(message: FailArgs): void { this.messages.push(message); } - format() { + private format() { return this.messages.map(this.formatter, this); } - reset() { + private reset() { this.messages = []; } } diff --git a/src/arena/BattleLog/__snapshots__/BattleLog.test.ts.snap b/src/arena/LogService/__snapshots__/LogService.test.ts.snap similarity index 100% rename from src/arena/BattleLog/__snapshots__/BattleLog.test.ts.snap rename to src/arena/LogService/__snapshots__/LogService.test.ts.snap diff --git a/src/arena/LogService/index.ts b/src/arena/LogService/index.ts new file mode 100644 index 00000000..3244aac4 --- /dev/null +++ b/src/arena/LogService/index.ts @@ -0,0 +1 @@ +export * from './LogService'; diff --git a/src/arena/BattleLog/utils/format-action.ts b/src/arena/LogService/utils/format-action.ts similarity index 100% rename from src/arena/BattleLog/utils/format-action.ts rename to src/arena/LogService/utils/format-action.ts diff --git a/src/arena/BattleLog/utils/format-error.ts b/src/arena/LogService/utils/format-error.ts similarity index 100% rename from src/arena/BattleLog/utils/format-error.ts rename to src/arena/LogService/utils/format-error.ts diff --git a/src/arena/BattleLog/utils/format-exp.ts b/src/arena/LogService/utils/format-exp.ts similarity index 100% rename from src/arena/BattleLog/utils/format-exp.ts rename to src/arena/LogService/utils/format-exp.ts diff --git a/src/arena/LogService/utils/format-message.ts b/src/arena/LogService/utils/format-message.ts new file mode 100644 index 00000000..8473806b --- /dev/null +++ b/src/arena/LogService/utils/format-message.ts @@ -0,0 +1,13 @@ +import type { FailArgs, SuccessArgs } from '@/arena/Constuructors/types'; +import { isSuccessResult } from '@/arena/Constuructors/utils'; +import { formatAction } from './format-action'; +import { formatError } from './format-error'; +import { formatExp } from './format-exp'; + +export function formatMessage(msgObj: SuccessArgs | FailArgs) { + if (isSuccessResult(msgObj)) { + return `${formatAction(msgObj)}${formatExp(msgObj)}`; + } + + return formatError(msgObj); +} diff --git a/src/arena/BattleLog/utils/index.ts b/src/arena/LogService/utils/index.ts similarity index 100% rename from src/arena/BattleLog/utils/index.ts rename to src/arena/LogService/utils/index.ts diff --git a/src/arena/BattleLog/utils/join-long-messages.ts b/src/arena/LogService/utils/join-long-messages.ts similarity index 93% rename from src/arena/BattleLog/utils/join-long-messages.ts rename to src/arena/LogService/utils/join-long-messages.ts index f38d0db3..b5d0c5e9 100644 --- a/src/arena/BattleLog/utils/join-long-messages.ts +++ b/src/arena/LogService/utils/join-long-messages.ts @@ -3,8 +3,9 @@ import type { HealNext } from '@/arena/Constuructors/HealMagicConstructor'; import type { LongDmgMagicNext } from '@/arena/Constuructors/LongDmgMagicConstructor'; import type { LongMagicNext } from '@/arena/Constuructors/LongMagicConstructor'; import type { SuccessArgs } from '@/arena/Constuructors/types'; +import { isSuccessResult } from '@/arena/Constuructors/utils'; import { floatNumber } from '@/utils/floatNumber'; -import type { Message } from '../BattleLog'; +import type { Message } from '../LogService'; export function joinHealMessages(messages: Message[], newMessage: HealNext): Message[] { const copy = cloneDeep(messages); @@ -64,5 +65,5 @@ function isSameMessage( && a.actionType === b.actionType && (options?.ignoreInitiator ? true : a.initiator === b.initiator) && (options?.ignoreTarget ? true : a.target === b.target) - && !('message' in a); + && isSuccessResult(a); } diff --git a/src/arena/config.ts b/src/arena/config.ts index b85fc6cb..959d1b58 100644 --- a/src/arena/config.ts +++ b/src/arena/config.ts @@ -129,7 +129,7 @@ export default { 'frostTouch', 'acidSpittle', 'chainLightning', 'fireRain', 'wild_lighting', 'rockfall', - 'physical_sadness'], + 'physicalSadness'], 'magic_sadness', [ 'lightHeal', diff --git a/src/arena/magics/__snapshots__/chainLightning.test.ts.snap b/src/arena/magics/__snapshots__/chainLightning.test.ts.snap index 76b76584..09540239 100644 --- a/src/arena/magics/__snapshots__/chainLightning.test.ts.snap +++ b/src/arena/magics/__snapshots__/chainLightning.test.ts.snap @@ -2,10 +2,10 @@ exports[`chainLightning should hit 3 targets 1`] = ` [ - 8, + 5.89, 5.63, - 5.88, - 6.13, + 6.1, + 8, 8, 8, 8, @@ -20,24 +20,48 @@ exports[`chainLightning should hit 3 targets 2`] = `63`; exports[`chainLightning should hit 3 targets 3`] = ` [ - "*architecto* сотворил _Цепь молний_ на *aspernatur* нанеся 2.34 урона -\\[ aspernatur ⚡ 💔-2.34/5.63 📖31 -quia ⚡ 💔-2.12/5.88 📖17 -dicta ⚡ 💔-1.87/6.13 📖15 ]", + { + "action": "Цепь молний", + "actionType": "aoe-dmg-magic", + "dmg": 2.37, + "dmgType": "lighting", + "effect": undefined, + "exp": 31, + "expArr": [ + { + "exp": 17, + "hp": 5.89, + "id": "2bea7421-d53a-4131-b215-51c23ea98de9", + "name": "sed", + "val": 2.11, + }, + { + "exp": 15, + "hp": 6.1, + "id": "551bb8cb-f2ea-4063-a9cf-7fe83e0ae647", + "name": "culpa", + "val": 1.9, + }, + ], + "hp": 5.63, + "initiator": "sed", + "msg": undefined, + "target": "quod", + }, ] `; exports[`chainLightning should hit 3 targets without clan 1`] = ` [ 5.89, + 6.13, 8, 8, 8, 8, 8, 8, - 6.13, - 5.66, + 5.63, 8, 8, ] @@ -47,19 +71,42 @@ exports[`chainLightning should hit 3 targets without clan 2`] = `63`; exports[`chainLightning should hit 3 targets without clan 3`] = ` [ - "*qui* сотворил _Цепь молний_ на *ex* нанеся 2.34 урона -\\[ ex ⚡ 💔-2.34/5.66 📖31 -qui ⚡ 💔-2.11/5.89 📖17 -est ⚡ 💔-1.87/6.13 📖15 ]", + { + "action": "Цепь молний", + "actionType": "aoe-dmg-magic", + "dmg": 2.34, + "dmgType": "lighting", + "effect": undefined, + "exp": 31, + "expArr": [ + { + "exp": 17, + "hp": 5.89, + "id": "2d0ef768-4c94-427f-a50f-8363cd53ed98", + "name": "id", + "val": 2.11, + }, + { + "exp": 15, + "hp": 6.13, + "id": "33c99000-70a6-4e90-bfc6-5f192d396a54", + "name": "molestiae", + "val": 1.87, + }, + ], + "hp": 5.63, + "initiator": "id", + "msg": undefined, + "target": "magni", + }, ] `; exports[`chainLightning should hit 4 targets 1`] = ` [ - 8, - 4.64, 4.98, - 5.28, + 4.64, + 5.31, 5.65, 8, 8, @@ -67,6 +114,7 @@ exports[`chainLightning should hit 4 targets 1`] = ` 8, 8, 8, + 8, ] `; @@ -74,22 +122,52 @@ exports[`chainLightning should hit 4 targets 2`] = `104`; exports[`chainLightning should hit 4 targets 3`] = ` [ - "*explicabo* сотворил _Цепь молний_ на *et* нанеся 3.36 урона -\\[ et ⚡ 💔-3.36/4.64 📖39 -eum ⚡ 💔-3.02/4.98 📖24 -veritatis ⚡ 💔-2.72/5.28 📖22 -deserunt ⚡ 💔-2.35/5.65 📖19 ]", + { + "action": "Цепь молний", + "actionType": "aoe-dmg-magic", + "dmg": 3.36, + "dmgType": "lighting", + "effect": undefined, + "exp": 39, + "expArr": [ + { + "exp": 24, + "hp": 4.98, + "id": "14edb8f1-372b-4e3b-913c-9c3e0b812060", + "name": "ex", + "val": 3.02, + }, + { + "exp": 22, + "hp": 5.31, + "id": "00831d48-28fd-4413-9199-e6850dc43bc9", + "name": "et", + "val": 2.69, + }, + { + "exp": 19, + "hp": 5.65, + "id": "76dcb686-3502-41e0-b1c3-8b08a0214f59", + "name": "repellendus", + "val": 2.35, + }, + ], + "hp": 4.64, + "initiator": "ex", + "msg": undefined, + "target": "aut", + }, ] `; exports[`chainLightning should hit 5 targets 1`] = ` [ - 8, + 4.06, 3.59, - 4.03, - 4.46, - 4.93, - 5.35, + 4.47, + 4.9, + 5.37, + 8, 8, 8, 8, @@ -102,11 +180,47 @@ exports[`chainLightning should hit 5 targets 2`] = `153`; exports[`chainLightning should hit 5 targets 3`] = ` [ - "*asperiores* сотворил _Цепь молний_ на *magni* нанеся 4.41 урона -\\[ magni ⚡ 💔-4.41/3.59 📖47 -labore ⚡ 💔-3.97/4.03 📖32 -iure ⚡ 💔-3.54/4.46 📖28 -recusandae ⚡ 💔-3.07/4.93 📖25 -ipsam ⚡ 💔-2.65/5.35 📖21 ]", + { + "action": "Цепь молний", + "actionType": "aoe-dmg-magic", + "dmg": 4.38, + "dmgType": "lighting", + "effect": undefined, + "exp": 47, + "expArr": [ + { + "exp": 32, + "hp": 4.06, + "id": "41b8eded-0d04-420e-a196-bf683b452acd", + "name": "asperiores", + "val": 3.94, + }, + { + "exp": 28, + "hp": 4.47, + "id": "700c9fab-547c-4311-97e1-5e412801ae3e", + "name": "labore", + "val": 3.53, + }, + { + "exp": 25, + "hp": 4.9, + "id": "54790999-3f94-4b31-a6fb-6680c80a28cf", + "name": "iure", + "val": 3.1, + }, + { + "exp": 21, + "hp": 5.37, + "id": "19ee8272-fc66-482a-b559-dab2eb9ac55c", + "name": "recusandae", + "val": 2.63, + }, + ], + "hp": 3.59, + "initiator": "asperiores", + "msg": undefined, + "target": "magni", + }, ] `; diff --git a/src/arena/magics/__snapshots__/fireBall.test.ts.snap b/src/arena/magics/__snapshots__/fireBall.test.ts.snap index 6bbbec6e..46827be4 100644 --- a/src/arena/magics/__snapshots__/fireBall.test.ts.snap +++ b/src/arena/magics/__snapshots__/fireBall.test.ts.snap @@ -10,33 +10,69 @@ exports[`fireBall should hit 1 target 1`] = ` 8, 8, 8, - -5.59, + -5.44, 8, 8, ] `; -exports[`fireBall should hit 1 target 2`] = `120`; +exports[`fireBall should hit 1 target 2`] = `119`; exports[`fireBall should hit 1 target 3`] = ` [ - "*explicabo* сотворил _Огненный шар_ на *aperiam* нанеся 8.75 урона -\\[ aperiam 🔥 💔-8.75/-5.59 📖80 -aperiam 🔥 💔-1.21/-1.96 📖10 -aperiam 🔥 💔-1.21/-3.17 📖10 -aperiam 🔥 💔-1.21/-4.38 📖10 -aperiam 🔥 💔-1.21/-5.59 📖10 ]", + { + "action": "Огненный шар", + "actionType": "aoe-dmg-magic", + "dmg": 8.64, + "dmgType": "fire", + "effect": undefined, + "exp": 79, + "expArr": [ + { + "exp": 10, + "hp": -1.84, + "id": "14edb8f1-372b-4e3b-913c-9c3e0b812060", + "name": "culpa", + "val": 1.2, + }, + { + "exp": 10, + "hp": -3.04, + "id": "00831d48-28fd-4413-9199-e6850dc43bc9", + "name": "culpa", + "val": 1.2, + }, + { + "exp": 10, + "hp": -4.24, + "id": "76dcb686-3502-41e0-b1c3-8b08a0214f59", + "name": "culpa", + "val": 1.2, + }, + { + "exp": 10, + "hp": -5.44, + "id": "53142b23-e95f-40db-8473-8fde2c0a107d", + "name": "culpa", + "val": 1.2, + }, + ], + "hp": -5.44, + "initiator": "ex", + "msg": undefined, + "target": "culpa", + }, ] `; exports[`fireBall should hit 6 targets 1`] = ` [ 8, - -0.72, - 6.79, - 7.09, - 6.8, - 6.79, + -5.56, + 8, + 8, + 8, + 8, 8, 8, 8, @@ -45,15 +81,51 @@ exports[`fireBall should hit 6 targets 1`] = ` ] `; -exports[`fireBall should hit 6 targets 2`] = `117`; +exports[`fireBall should hit 6 targets 2`] = `120`; exports[`fireBall should hit 6 targets 3`] = ` [ - "*asperiores* сотворил _Огненный шар_ на *magni* нанеся 8.72 урона -\\[ magni 🔥 💔-8.72/-0.72 📖80 -labore 🔥 💔-1.21/6.79 📖10 -iure 🔥 💔-0.91/7.09 📖7 -recusandae 🔥 💔-1.2/6.8 📖10 -ipsam 🔥 💔-1.21/6.79 📖10 ]", + { + "action": "Огненный шар", + "actionType": "aoe-dmg-magic", + "dmg": 8.72, + "dmgType": "fire", + "effect": undefined, + "exp": 80, + "expArr": [ + { + "exp": 10, + "hp": -1.93, + "id": "41b8eded-0d04-420e-a196-bf683b452acd", + "name": "magni", + "val": 1.21, + }, + { + "exp": 10, + "hp": -3.14, + "id": "700c9fab-547c-4311-97e1-5e412801ae3e", + "name": "magni", + "val": 1.21, + }, + { + "exp": 10, + "hp": -4.35, + "id": "54790999-3f94-4b31-a6fb-6680c80a28cf", + "name": "magni", + "val": 1.21, + }, + { + "exp": 10, + "hp": -5.56, + "id": "19ee8272-fc66-482a-b559-dab2eb9ac55c", + "name": "magni", + "val": 1.21, + }, + ], + "hp": -5.56, + "initiator": "asperiores", + "msg": undefined, + "target": "magni", + }, ] `; diff --git a/src/arena/magics/__snapshots__/physicalSadness.test.ts.snap b/src/arena/magics/__snapshots__/physicalSadness.test.ts.snap new file mode 100644 index 00000000..81f89c1f --- /dev/null +++ b/src/arena/magics/__snapshots__/physicalSadness.test.ts.snap @@ -0,0 +1,108 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`physicalSadness should hit target with single hit 1`] = `-3.12`; + +exports[`physicalSadness should hit target with single hit 2`] = `97`; + +exports[`physicalSadness should hit target with single hit 3`] = ` +[ + { + "action": "attack", + "actionType": "phys", + "dmg": 10.9, + "dmgType": "physical", + "exp": 87, + "hp": -2.9, + "initiator": "magni", + "target": "asperiores", + "weapon": { + "code": "a100", + }, + }, + { + "action": "Вселенская скорбь", + "actionType": "dmg-magic", + "dmg": 11.12, + "dmgType": "physical", + "effect": undefined, + "exp": 97, + "hp": -3.12, + "initiator": "asperiores", + "msg": undefined, + "target": "magni", + }, +] +`; + +exports[`physicalSadness should hit targets with multiple hit 1`] = `5.22`; + +exports[`physicalSadness should hit targets with multiple hit 2`] = `30`; + +exports[`physicalSadness should hit targets with multiple hit 3`] = ` +[ + { + "action": "attack", + "actionType": "phys", + "dmg": 2.73, + "dmgType": "physical", + "exp": 22, + "hp": 5.27, + "initiator": "magni", + "target": "asperiores", + "weapon": { + "code": "a100", + }, + }, + { + "action": "attack", + "actionType": "phys", + "dmg": 2.73, + "dmgType": "physical", + "exp": 22, + "hp": 2.54, + "initiator": "magni", + "target": "asperiores", + "weapon": { + "code": "a100", + }, + }, + { + "action": "attack", + "actionType": "phys", + "dmg": 2.73, + "dmgType": "physical", + "exp": 22, + "hp": -0.19, + "initiator": "magni", + "target": "asperiores", + "weapon": { + "code": "a100", + }, + }, + { + "action": "attack", + "actionType": "phys", + "dmg": 2.73, + "dmgType": "physical", + "exp": 22, + "hp": -2.92, + "initiator": "magni", + "target": "asperiores", + "weapon": { + "code": "a100", + }, + }, + { + "action": "Вселенская скорбь", + "actionType": "dmg-magic", + "dmg": 2.78, + "dmgType": "physical", + "effect": undefined, + "exp": 30, + "hp": 5.22, + "initiator": "asperiores", + "msg": undefined, + "target": "magni", + }, +] +`; diff --git a/src/arena/magics/chainLightning.test.ts b/src/arena/magics/chainLightning.test.ts index de854f29..3c63fe7b 100644 --- a/src/arena/magics/chainLightning.test.ts +++ b/src/arena/magics/chainLightning.test.ts @@ -45,7 +45,7 @@ describe('chainLightning', () => { game.players.players.map((player) => player.stats.val('hp')), ).toMatchSnapshot(); expect(game.players.players[0].stats.val('exp')).toMatchSnapshot(); - expect(game.battleLog.format()).toMatchSnapshot(); + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); }); it('should hit 4 targets', () => { @@ -58,7 +58,7 @@ describe('chainLightning', () => { game.players.players.map((player) => player.stats.val('hp')), ).toMatchSnapshot(); expect(game.players.players[0].stats.val('exp')).toMatchSnapshot(); - expect(game.battleLog.format()).toMatchSnapshot(); + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); }); it('should hit 3 targets', () => { @@ -71,7 +71,7 @@ describe('chainLightning', () => { game.players.players.map((player) => player.stats.val('hp')), ).toMatchSnapshot(); expect(game.players.players[0].stats.val('exp')).toMatchSnapshot(); - expect(game.battleLog.format()).toMatchSnapshot(); + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); }); it('should hit 3 targets without clan', () => { @@ -84,6 +84,6 @@ describe('chainLightning', () => { game.players.players.map((player) => player.stats.val('hp')), ).toMatchSnapshot(); expect(game.players.players[0].stats.val('exp')).toMatchSnapshot(); - expect(game.battleLog.format()).toMatchSnapshot(); + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); }); }); diff --git a/src/arena/magics/fireBall.test.ts b/src/arena/magics/fireBall.test.ts index adc4e95e..06669179 100644 --- a/src/arena/magics/fireBall.test.ts +++ b/src/arena/magics/fireBall.test.ts @@ -50,7 +50,7 @@ describe('fireBall', () => { game.players.players.map((player) => player.stats.val('hp')), ).toMatchSnapshot(); expect(game.players.players[0].stats.val('exp')).toMatchSnapshot(); - expect(game.battleLog.format()).toMatchSnapshot(); + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); }); it('should hit 1 target', () => { @@ -62,6 +62,6 @@ describe('fireBall', () => { game.players.players.map((player) => player.stats.val('hp')), ).toMatchSnapshot(); expect(game.players.players[0].stats.val('exp')).toMatchSnapshot(); - expect(game.battleLog.format()).toMatchSnapshot(); + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); }); }); diff --git a/src/arena/magics/heal.ts b/src/arena/magics/heal.ts index 26be7c9b..9b0c68be 100644 --- a/src/arena/magics/heal.ts +++ b/src/arena/magics/heal.ts @@ -26,8 +26,7 @@ export class HealMagic extends CommonMagic { } next(): void { - const { target, initiator } = this.params; - const { battleLog } = this.params.game; + const { target, initiator, game } = this.params; const args: HealMagicNext = { exp: this.status.exp, action: this.displayName, @@ -38,6 +37,7 @@ export class HealMagic extends CommonMagic { effect: this.status.effect, msg: this.customMessage?.bind(this), }; - battleLog.success(args); + + game.recordOrderResult(args); } } diff --git a/src/arena/magics/index.ts b/src/arena/magics/index.ts index 58d7b36c..66bef578 100644 --- a/src/arena/magics/index.ts +++ b/src/arena/magics/index.ts @@ -24,3 +24,4 @@ export { default as stoneSkin } from './stoneSkin'; export { default as strongHeal } from './strongHeal'; export { default as anathema } from './anathema'; export { default as fireBall } from './fireBall'; +export { default as physicalSadness } from './physicalSadness'; diff --git a/src/arena/magics/physicalSadness.test.ts b/src/arena/magics/physicalSadness.test.ts new file mode 100644 index 00000000..f580a522 --- /dev/null +++ b/src/arena/magics/physicalSadness.test.ts @@ -0,0 +1,76 @@ +import casual from 'casual'; +import { attack } from '@/arena/actions'; +import GameService from '@/arena/GameService'; +import { profsData } from '@/data/profs'; +import { type Char } from '@/models/character'; +import TestUtils from '@/utils/testUtils'; +import physicalSadness from './physicalSadness'; + +// npm t src/arena/magics/physicalSadness.test.ts + +describe('physicalSadness', () => { + let game: GameService; + let initiator: Char; + let target: Char; + + beforeAll(async () => { + casual.seed(1); + const harks = { ...profsData.m.hark, wis: 20 }; + + initiator = await TestUtils.createCharacter({ prof: 'm', magics: { physicalSadness: 1 }, harks }); + target = await TestUtils.createCharacter({}, { withWeapon: true }); + }); + + beforeEach(async () => { + game = new GameService([initiator.id, target.id]); + }); + + beforeEach(() => { + jest.spyOn(global.Math, 'random').mockReturnValue(0.15); + }); + + afterEach(() => { + jest.spyOn(global.Math, 'random').mockRestore(); + }); + + it('should hit target with single hit', () => { + game.players.players[0].proc = 1; + game.players.players[1].proc = 1; + + attack.cast(game.players.players[1], game.players.players[0], game); + + physicalSadness.cast(game.players.players[0], game.players.players[1], game); + + expect(game.players.players[1].stats.val('hp')).toMatchSnapshot(); + expect(game.players.players[0].stats.val('exp')).toMatchSnapshot(); + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); + }); + + it('should hit targets with multiple hit', () => { + game.players.players[0].proc = 1; + game.players.players[1].proc = 0.25; + + attack.cast(game.players.players[1], game.players.players[0], game); + attack.cast(game.players.players[1], game.players.players[0], game); + attack.cast(game.players.players[1], game.players.players[0], game); + attack.cast(game.players.players[1], game.players.players[0], game); + + physicalSadness.cast(game.players.players[0], game.players.players[1], game); + + expect(game.players.players[1].stats.val('hp')).toMatchSnapshot(); + expect(game.players.players[0].stats.val('exp')).toMatchSnapshot(); + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); + }); + + it('should not hit targets if was not any physical damage', () => { + game.players.players[0].proc = 1; + game.players.players[1].proc = 1; + + const initTargetHp = game.players.players[1].stats.val('hp'); + + physicalSadness.cast(game.players.players[0], game.players.players[1], game); + + expect(game.players.players[1].stats.val('hp')).toEqual(initTargetHp); + expect(game.players.players[0].stats.val('exp')).toEqual(physicalSadness.baseExp); + }); +}); diff --git a/src/arena/magics/physicalSadness.ts b/src/arena/magics/physicalSadness.ts new file mode 100644 index 00000000..40c0a85e --- /dev/null +++ b/src/arena/magics/physicalSadness.ts @@ -0,0 +1,53 @@ +import { DmgMagic } from '../Constuructors/DmgMagicConstructor'; +import type { PhysNext } from '../Constuructors/types'; +import { isSuccessDamageResult } from '../Constuructors/utils'; + +const isPhysicalDamage = (result): result is PhysNext => { + if (isSuccessDamageResult(result)) { + return result.actionType === 'phys'; + } + + return false; +}; + +class PhysicalSadness extends DmgMagic { + constructor() { + super({ + name: 'physicalSadness', + displayName: 'Вселенская скорбь', + desc: 'Заставляет цель ощущать всю боль физически раненых этого раунда.', + cost: 20, + baseExp: 8, + costType: 'mp', + lvl: 4, + orderType: 'enemy', + aoeType: 'target', + magType: 'bad', + chance: [95, 95, 95], + effect: ['1d1', '1d2', '1d1+1'], + dmgType: 'physical', + profList: ['m'], + }); + } + + run(): void { + const { target, game } = this.params; + const results = game.getRoundResults(); + const physicalDamageResults = results.filter(isPhysicalDamage); + + if (!physicalDamageResults.length) { + return; + } + + const effect = this.effectVal(); + + const totalHit = physicalDamageResults.reduce((sum, result) => sum + result.dmg, 0); + const hit = effect * (totalHit / physicalDamageResults.length); + + this.status.hit = hit; + + target.stats.down('hp', hit); + } +} + +export default new PhysicalSadness(); diff --git a/src/utils/testUtils.ts b/src/utils/testUtils.ts index cd88d30b..4126370a 100644 --- a/src/utils/testUtils.ts +++ b/src/utils/testUtils.ts @@ -2,15 +2,19 @@ import casual from 'casual'; import type { AnyKeys } from 'mongoose'; import CharacterService from '@/arena/CharacterService'; import { ClanService } from '@/arena/ClanService'; -import { profsList, profsData, Prof } from '@/data/profs'; -import { Char, CharModel } from '@/models/character'; -import { Clan, ClanModel } from '@/models/clan'; -import { Item, ItemModel } from '@/models/item'; +import type { HistoryItem } from '@/arena/HistoryService'; +import { profsList, profsData, type Prof } from '@/data/profs'; +import { type Char, CharModel } from '@/models/character'; +import { type Clan, ClanModel } from '@/models/clan'; +import { InventoryModel } from '@/models/inventory'; +import { type Item, ItemModel } from '@/models/item'; const functions = casual.functions(); export default class TestUtils { - static async createCharacter(params?: Partial) { + static async createCharacter(params?: Partial, { + withWeapon = false, + } = {}) { const prof: Prof = params?.prof ?? casual.random_element([...profsList]); const char = await CharModel.create({ tgId: casual.integer(1_000_000, 9_999_999), @@ -21,6 +25,20 @@ export default class TestUtils { magics: profsData[prof].mag, ...params, }); + + if (withWeapon) { + const weapon = await this.getWeapon(); + + char.inventory = await InventoryModel.create([{ + wear: weapon.wear, + code: weapon.code, + owner: char.id, + putOn: true, + }]); + + await char.save(); + } + await CharacterService.getCharacter(char.tgId); return char; } @@ -54,4 +72,21 @@ export default class TestUtils { await ItemModel.load(); return ItemModel.findOne({ code: 'a100' }).orFail(); } + + static normalizeRoundHistory(history: HistoryItem[]) { + return history.map((item) => { + if ('weapon' in item) { + // @ts-expect-error todo + item.weapon = { code: item.weapon.code }; + } + if ('expArr' in item) { + item.expArr = item.expArr.map((item) => ({ + ...item, + id: casual.uuid, + })); + } + + return item; + }); + } }