From 136c6d05bf6af47e3c2c094c482f5096a36925a3 Mon Sep 17 00:00:00 2001 From: Evgeny Kirpichyov <47474371+kyvg@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:15:01 +0200 Subject: [PATCH] Affects v2 (#309) * Rewrite PhysConstructor to TS * Affects v2 --- .eslintrc.js | 1 + src/arena/BattleService.ts | 3 + src/arena/Constuructors/AffectableAction.ts | 47 ++++ .../Constuructors/AoeDmgMagicConstructor.ts | 34 +-- src/arena/Constuructors/BaseAction.ts | 25 ++ .../Constuructors/DmgMagicConstructor.ts | 45 ++- src/arena/Constuructors/FlagsConstructor.ts | 2 + .../Constuructors/HealMagicConstructor.ts | 36 +-- .../Constuructors/LongDmgMagicConstructor.ts | 13 +- .../Constuructors/LongMagicConstructor.ts | 18 +- src/arena/Constuructors/MagicConstructor.ts | 119 ++++---- src/arena/Constuructors/PhysConstructor.js | 221 --------------- src/arena/Constuructors/PhysConstructor.ts | 185 +++++++++++++ src/arena/Constuructors/PreAffect.ts | 10 - src/arena/Constuructors/SkillConstructor.ts | 73 ++--- .../Constuructors/interfaces/PostAffect.ts | 10 + .../Constuructors/interfaces/PreAffect.ts | 9 + src/arena/Constuructors/types.ts | 35 +-- src/arena/Constuructors/utils/index.ts | 17 +- src/arena/LogService/LogService.test.ts | 260 ------------------ .../__snapshots__/LogService.test.ts.snap | 84 ------ src/arena/LogService/utils/format-action.ts | 12 +- src/arena/LogService/utils/format-cause.ts | 5 +- src/arena/LogService/utils/format-error.ts | 146 ++++------ src/arena/LogService/utils/format-exp.ts | 4 +- src/arena/LogService/utils/format-message.ts | 12 +- src/arena/MiscService.js | 3 + src/arena/PlayersService/Player.ts | 8 + .../__snapshots__/handsHeal.test.ts.snap | 8 + .../__snapshots__/protect.test.ts.snap | 11 + src/arena/actions/attack.ts | 20 +- src/arena/actions/handsHeal.test.ts | 44 +++ src/arena/actions/handsHeal.ts | 14 +- src/arena/actions/protect.test.ts | 57 ++++ src/arena/actions/protect.ts | 11 +- src/arena/errors/CastError.ts | 7 + .../magics/__snapshots__/blight.test.ts.snap | 18 +- .../__snapshots__/bodySpirit.test.ts.snap | 42 ++- .../__snapshots__/chainLightning.test.ts.snap | 166 ++--------- .../magics/__snapshots__/eclipse.test.ts.snap | 8 + .../__snapshots__/fireBall.test.ts.snap | 104 +------ .../__snapshots__/lightShield.test.ts.snap | 14 + .../__snapshots__/paralysis.test.ts.snap | 8 + .../physicalSadness.test.ts.snap | 115 ++------ src/arena/magics/blight.ts | 4 +- src/arena/magics/bodySpirit.ts | 2 +- src/arena/magics/eclipse.test.ts | 48 ++++ src/arena/magics/eclipse.ts | 10 +- src/arena/magics/index.ts | 1 + src/arena/magics/lightShield.test.ts | 46 ++++ src/arena/magics/lightShield.ts | 67 +++++ src/arena/magics/paralysis.test.ts | 48 ++++ src/arena/magics/paralysis.ts | 10 +- src/arena/magics/physicalSadness.ts | 15 +- src/arena/magics/silence.ts | 12 +- src/arena/magics/vampirism.ts | 2 +- .../skills/__snapshots__/disarm.test.ts.snap | 57 +--- .../skills/__snapshots__/dodge.test.ts.snap | 59 +--- .../skills/__snapshots__/parry.test.ts.snap | 59 +--- src/arena/skills/disarm.test.ts | 1 + src/arena/skills/disarm.ts | 11 +- src/arena/skills/dodge.test.ts | 1 + src/arena/skills/dodge.ts | 9 +- src/arena/skills/parry.test.ts | 1 + src/arena/skills/parry.ts | 9 +- src/utils/registerAffects.ts | 24 +- src/utils/testUtils.ts | 16 +- 67 files changed, 1106 insertions(+), 1490 deletions(-) create mode 100644 src/arena/Constuructors/AffectableAction.ts create mode 100644 src/arena/Constuructors/BaseAction.ts delete mode 100644 src/arena/Constuructors/PhysConstructor.js create mode 100644 src/arena/Constuructors/PhysConstructor.ts delete mode 100644 src/arena/Constuructors/PreAffect.ts create mode 100644 src/arena/Constuructors/interfaces/PostAffect.ts create mode 100644 src/arena/Constuructors/interfaces/PreAffect.ts delete mode 100644 src/arena/LogService/LogService.test.ts delete mode 100644 src/arena/LogService/__snapshots__/LogService.test.ts.snap create mode 100644 src/arena/actions/__snapshots__/handsHeal.test.ts.snap create mode 100644 src/arena/actions/__snapshots__/protect.test.ts.snap create mode 100644 src/arena/actions/handsHeal.test.ts create mode 100644 src/arena/actions/protect.test.ts create mode 100644 src/arena/errors/CastError.ts create mode 100644 src/arena/magics/__snapshots__/eclipse.test.ts.snap create mode 100644 src/arena/magics/__snapshots__/lightShield.test.ts.snap create mode 100644 src/arena/magics/__snapshots__/paralysis.test.ts.snap create mode 100644 src/arena/magics/eclipse.test.ts create mode 100644 src/arena/magics/lightShield.test.ts create mode 100644 src/arena/magics/lightShield.ts create mode 100644 src/arena/magics/paralysis.test.ts diff --git a/.eslintrc.js b/.eslintrc.js index 9daf7b7a..614306c7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,6 +30,7 @@ module.exports = { camelcase: 0, 'lines-between-class-members': ["error", "always", {exceptAfterSingleLine: true}], 'no-void': ['error', { allowAsStatement: true }], + '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/no-useless-constructor': 'warn', '@typescript-eslint/no-var-requires': 0, '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: "^_"}], diff --git a/src/arena/BattleService.ts b/src/arena/BattleService.ts index 4b94f4e3..0aba4530 100644 --- a/src/arena/BattleService.ts +++ b/src/arena/BattleService.ts @@ -55,6 +55,9 @@ function getTargetKeyboard(charId: string, game: Game, action: string) { if (orderType === 'enemy') { return !game.isPlayersAlly(player, target); } + if (orderType === 'team') { + return game.isPlayersAlly(player, target); + } return !orders.some((order) => target.id === order.target && action === order.action); }) diff --git a/src/arena/Constuructors/AffectableAction.ts b/src/arena/Constuructors/AffectableAction.ts new file mode 100644 index 00000000..3803fb12 --- /dev/null +++ b/src/arena/Constuructors/AffectableAction.ts @@ -0,0 +1,47 @@ +import { BaseAction } from './BaseAction'; +import type { PostAffect } from './interfaces/PostAffect'; +import type { PreAffect } from './interfaces/PreAffect'; +import type { SuccessArgs } from './types'; + +export abstract class AffectableAction extends BaseAction { + private preAffects: PreAffect[] = []; + private postAffects: PostAffect[] = []; + private affects: SuccessArgs[] = []; + + registerPreAffects(preAffects: PreAffect[]) { + this.preAffects = preAffects; + } + + registerPostAffects(postAffects: PostAffect[]) { + this.postAffects = postAffects; + } + + checkPreAffects(params = this.params, status = this.status) { + this.preAffects.forEach((preAffect) => { + preAffect.preAffect(params, status); + }); + } + + checkPostAffects(params = this.params, status = this.status) { + this.postAffects.forEach((preAffect) => { + const result = preAffect.postAffect(params, status); + if (!result) { + return; + } + + const normalizedResult = Array.isArray(result) ? result : [result]; + + this.affects.push(...normalizedResult); + }); + } + + getAffects() { + return this.affects; + } + + reset() { + super.reset(); + + this.affects = []; + } +} diff --git a/src/arena/Constuructors/AoeDmgMagicConstructor.ts b/src/arena/Constuructors/AoeDmgMagicConstructor.ts index f0e371d5..9c6cb1d4 100644 --- a/src/arena/Constuructors/AoeDmgMagicConstructor.ts +++ b/src/arena/Constuructors/AoeDmgMagicConstructor.ts @@ -1,6 +1,8 @@ import { floatNumber } from '../../utils/floatNumber'; import { DmgMagic } from './DmgMagicConstructor'; -import type { BaseNext, DamageType, ExpArr } from './types'; +import type { + BaseNext, DamageType, ExpArr, SuccessArgs, +} from './types'; export type AoeDmgMagicNext = BaseNext & { actionType: 'aoe-dmg-magic' @@ -14,7 +16,7 @@ export abstract class AoeDmgMagic extends DmgMagic { status: { exp: number; expArr: ExpArr; - hit: number; + effect: number; }; getExp({ initiator, target, game } = this.params): void { @@ -46,31 +48,31 @@ export abstract class AoeDmgMagic extends DmgMagic { }); } - resetStatus(): void { + reset(): void { + super.reset(); + this.status = { exp: 0, - hit: 0, + effect: 0, expArr: [], }; } - /** - * Магия прошла удачно - * @param initiator объект персонажаы - * @param target объект цели магии - * @todo тут нужен вывод требуемых параметров - */ - next(): void { - const { game, target } = this.params; - const args: AoeDmgMagicNext = { - ...this.getNextArgs(), + getSuccessResult({ initiator, target } = this.params): SuccessArgs { + const result: AoeDmgMagicNext = { + exp: this.status.exp, + action: this.displayName, + target: target.nick, + initiator: initiator.nick, actionType: 'aoe-dmg-magic', expArr: this.status.expArr, - dmg: floatNumber(this.status.hit), + dmg: floatNumber(this.status.effect), hp: target.stats.val('hp'), dmgType: this.dmgType, }; - game.recordOrderResult(args); + this.reset(); + + return result; } } diff --git a/src/arena/Constuructors/BaseAction.ts b/src/arena/Constuructors/BaseAction.ts new file mode 100644 index 00000000..fd951200 --- /dev/null +++ b/src/arena/Constuructors/BaseAction.ts @@ -0,0 +1,25 @@ +import type GameService from '@/arena/GameService'; +import type { Player } from '@/arena/PlayersService'; + +export abstract class BaseAction { + params: { + initiator: Player; + target: Player; + game: GameService + }; + + status = { effect: 0, exp: 0 }; + + abstract cast(initiator: Player, target: Player, game: GameService) + + /** + * @param initiator объект персонажа + * @param target объект персонажа + * @param game Объект игры для доступа ко всему + */ + abstract run(initiator: Player, target: Player, game: GameService) + + reset() { + this.status = { effect: 0, exp: 0 }; + } +} diff --git a/src/arena/Constuructors/DmgMagicConstructor.ts b/src/arena/Constuructors/DmgMagicConstructor.ts index 5b533f94..cd466352 100644 --- a/src/arena/Constuructors/DmgMagicConstructor.ts +++ b/src/arena/Constuructors/DmgMagicConstructor.ts @@ -1,7 +1,8 @@ import { floatNumber } from '../../utils/floatNumber'; import type { Player } from '../PlayersService'; -import { Magic, MagicArgs } from './MagicConstructor'; -import type { BaseNext, DamageType } from './types'; +import type { MagicArgs } from './MagicConstructor'; +import { Magic } from './MagicConstructor'; +import type { BaseNext, DamageType, SuccessArgs } from './types'; export type DmgMagicNext = BaseNext & { actionType: 'dmg-magic' @@ -20,11 +21,6 @@ export interface DmgMagic extends DmgMagicArgs, Magic { * Общий конструктор не длительных магий */ export abstract class DmgMagic extends Magic { - status: { - exp: number; - hit: number; - }; - /** * Создание магии */ @@ -40,7 +36,7 @@ export abstract class DmgMagic extends Magic { const effect = this.getEffectVal({ initiator, target, game }); const modifiedEffect = this.modifyEffect(effect, { initiator, target, game }); - this.status.hit = modifiedEffect; + this.status.effect = modifiedEffect; return modifiedEffect; } @@ -76,11 +72,11 @@ export abstract class DmgMagic extends Magic { * кол-во единиц за использование магии * Если кастеру хватило mp/en продолжаем,если нет, то возвращаем false */ - getExp({ initiator, target, game } = this.params): void { - if (game.isPlayersAlly(initiator, target) && !initiator.flags.isGlitched) { + getExp({ initiator, target } = this.params): void { + if (initiator.isAlly(target) && !initiator.flags.isGlitched) { this.status.exp = 0; } else { - const dmgExp = this.calculateExp(this.status.hit, this.baseExp); + const dmgExp = this.calculateExp(this.status.effect, this.baseExp); this.status.exp = dmgExp; initiator.stats.up('exp', dmgExp); @@ -91,29 +87,28 @@ export abstract class DmgMagic extends Magic { return Math.round(hit * 8) + baseExp; } - resetStatus(): void { + reset(): void { this.status = { exp: 0, - hit: 0, + effect: 0, }; } - /** - * Магия прошла удачно - * @param initiator объект персонажаы - * @param target объект цели магии - * @todo тут нужен вывод требуемых параметров - */ - next(): void { - const { game, target } = this.params; - const args: DmgMagicNext = { - ...this.getNextArgs(), + getSuccessResult({ initiator, target } = this.params): SuccessArgs { + const result: DmgMagicNext = { + exp: this.status.exp, + action: this.displayName, actionType: 'dmg-magic', - dmg: floatNumber(this.status.hit), + target: target.nick, + initiator: initiator.nick, + msg: this.customMessage?.bind(this), + dmg: floatNumber(this.status.effect), hp: target.stats.val('hp'), dmgType: this.dmgType, }; - game.recordOrderResult(args); + this.reset(); + + return result; } } diff --git a/src/arena/Constuructors/FlagsConstructor.ts b/src/arena/Constuructors/FlagsConstructor.ts index ba4f2a6d..31a8e04b 100644 --- a/src/arena/Constuructors/FlagsConstructor.ts +++ b/src/arena/Constuructors/FlagsConstructor.ts @@ -19,6 +19,7 @@ export default class FlagsConstructor { isShielded = 0; isParalysed = false; isDisarmed = false; + isLightShielded: Flag[] = []; /** * Обнуление флагов @@ -35,5 +36,6 @@ export default class FlagsConstructor { this.isParry = 0; this.isDisarmed = false; this.isShielded = 0; + this.isLightShielded = []; } } diff --git a/src/arena/Constuructors/HealMagicConstructor.ts b/src/arena/Constuructors/HealMagicConstructor.ts index 795e9e1d..86337f59 100644 --- a/src/arena/Constuructors/HealMagicConstructor.ts +++ b/src/arena/Constuructors/HealMagicConstructor.ts @@ -2,9 +2,11 @@ import { floatNumber } from '../../utils/floatNumber'; import type Game from '../GameService'; import MiscService from '../MiscService'; import type { Player } from '../PlayersService'; +import { AffectableAction } from './AffectableAction'; import type { - BaseNext, Breaks, BreaksMessage, CustomMessage, ExpArr, OrderType, + BaseNext, BreaksMessage, CustomMessage, ExpArr, FailArgs, OrderType, SuccessArgs, } from './types'; +import { handleCastError } from './utils'; export type HealNext = Omit & { actionType: 'heal'; @@ -32,19 +34,9 @@ export interface Heal extends HealArgs, CustomMessage { /** * Heal Class */ -export abstract class Heal { - params!: { - initiator: Player; - target: Player; - game: Game; - }; - - status = { - exp: 0, - val: 0, - }; - +export abstract class Heal extends AffectableAction { constructor(params: HealArgs) { + super(); Object.assign(this, params); } @@ -62,6 +54,7 @@ export abstract class Heal { }; try { + this.checkPreAffects(); this.run(initiator, target, game); // Получение экспы за хил следует вынести в отдельный action следующий // за самим хилом, дабы выдать exp всем хиллерам после формирования @@ -70,12 +63,12 @@ export abstract class Heal { // this.backToLife(); this.next(); } catch (e) { - game.recordOrderResult(e); + handleCastError(e, (reason) => { + game.recordOrderResult(this.breaks(reason)); + }); } } - abstract run(initiator: Player, target: Player, game: Game): void; - /** * Функция выполняет проверку, является ли хил "воскресившим", т.е если * цель до выполнения лечения имела статус "isDead", а после хила имеет хп > 0 @@ -86,14 +79,15 @@ export abstract class Heal { /** * @param obj */ - breaks(message: BreaksMessage): Breaks { + breaks(reason: BreaksMessage | SuccessArgs | SuccessArgs[]): FailArgs { const { target, initiator } = this.params; return { - message, + reason, target: target.nick, initiator: initiator.nick, actionType: 'heal', action: this.displayName, + weapon: initiator.weapon.item, }; } @@ -117,7 +111,7 @@ export abstract class Heal { getExp(initiator: Player, target: Player, game: Game): number { if (game.isPlayersAlly(initiator, target)) { - const healEffect = this.status.val; + const healEffect = this.status.effect; const exp = Math.round(healEffect * 10); initiator.stats.up('exp', exp); return exp; @@ -141,7 +135,7 @@ export abstract class Heal { id: initiator.id, name: initiator.nick, exp: this.status.exp, - val: this.status.val, + val: this.status.effect, }; const args: HealNext = { expArr: [exp], @@ -149,7 +143,7 @@ export abstract class Heal { actionType: 'heal', target: target.nick, initiator: initiator.nick, - effect: this.status.val, + effect: this.status.effect, hp: target.stats.val('hp'), }; game.recordOrderResult(args); diff --git a/src/arena/Constuructors/LongDmgMagicConstructor.ts b/src/arena/Constuructors/LongDmgMagicConstructor.ts index 015a35fb..3b33bbc1 100644 --- a/src/arena/Constuructors/LongDmgMagicConstructor.ts +++ b/src/arena/Constuructors/LongDmgMagicConstructor.ts @@ -1,4 +1,5 @@ import { floatNumber } from '../../utils/floatNumber'; +import CastError from '../errors/CastError'; import type Game from '../GameService'; import type { Player } from '../PlayersService'; import { DmgMagic } from './DmgMagicConstructor'; @@ -36,7 +37,7 @@ export abstract class LongDmgMagic extends DmgMagic { game.longActions[this.name] ??= []; this.buff = game.longActions[this.name] ?? []; this.getCost(initiator); - this.checkPreAffects(initiator, target, game); + this.checkPreAffects({ initiator, target, game }); this.isBlurredMind(); // проверка не запудрило this.checkChance(); this.run(initiator, target, game); // вызов кастомного обработчика @@ -47,7 +48,7 @@ export abstract class LongDmgMagic extends DmgMagic { } catch (failMsg) { game.recordOrderResult(failMsg); } finally { - this.resetStatus(); + this.reset(); } } @@ -75,14 +76,14 @@ export abstract class LongDmgMagic extends DmgMagic { const initiator = game.players.getById(item.initiator); const target = game.players.getById(item.target); if (!initiator) { - throw this.breaks('NO_INITIATOR'); + throw new CastError('NO_INITIATOR'); } if (!target) { - throw this.breaks('NO_TARGET'); + throw new CastError('NO_TARGET'); } this.params = { initiator, target, game }; this.params.initiator.proc = item.proc; - this.checkPreAffects(initiator, target, game); + this.checkPreAffects({ initiator, target, game }); this.isBlurredMind(); // проверка не запудрило this.checkChance(); this.runLong(initiator, target, game); // вызов кастомного обработчика @@ -129,7 +130,7 @@ export abstract class LongDmgMagic extends DmgMagic { const { game } = this.params; const dmgObj: LongDmgMagicNext = { exp: this.status.exp, - dmg: floatNumber(this.status.hit), + dmg: floatNumber(this.status.effect), action: this.displayName, actionType: 'dmg-magic-long', target: target.nick, diff --git a/src/arena/Constuructors/LongMagicConstructor.ts b/src/arena/Constuructors/LongMagicConstructor.ts index 731dfcba..8caa528b 100644 --- a/src/arena/Constuructors/LongMagicConstructor.ts +++ b/src/arena/Constuructors/LongMagicConstructor.ts @@ -1,8 +1,10 @@ +import CastError from '../errors/CastError'; import type Game from '../GameService'; import type { Player } from '../PlayersService'; import { CommonMagic } from './CommonMagicConstructor'; import type { MagicNext } from './MagicConstructor'; import type { BaseNext, LongCustomMessage } from './types'; +import { handleCastError } from './utils'; export type LongItem = { initiator: string; @@ -40,7 +42,7 @@ export abstract class LongMagic extends CommonMagic { game.longActions[this.name] ??= []; this.buff = game.longActions[this.name] ?? []; this.getCost(initiator); - this.checkPreAffects(initiator, target, game); + this.checkPreAffects({ initiator, target, game }); this.isBlurredMind(); // проверка не запудрило this.checkChance(); this.run(initiator, target, game); // вызов кастомного обработчика @@ -48,10 +50,12 @@ export abstract class LongMagic extends CommonMagic { this.getExp(); this.next(); this.postRun(initiator, target, game); - } catch (failMsg) { - game.recordOrderResult(failMsg); + } catch (e) { + handleCastError(e, (reason) => { + game.recordOrderResult(this.getFailResult(reason)); + }); } finally { - this.resetStatus(); + this.reset(); } } @@ -75,14 +79,14 @@ export abstract class LongMagic extends CommonMagic { const initiator = game.players.getById(item.initiator); const target = game.players.getById(item.target); if (!initiator) { - throw this.breaks('NO_INITIATOR'); + throw new CastError('NO_INITIATOR'); } if (!target) { - throw this.breaks('NO_TARGET'); + throw new CastError('NO_TARGET'); } this.params = { initiator, target, game }; this.params.initiator.proc = item.proc; - this.checkPreAffects(initiator, target, game); + this.checkPreAffects({ initiator, target, game }); this.isBlurredMind(); // проверка не запудрило this.checkChance(); this.runLong(initiator, target, game); // вызов кастомного обработчика diff --git a/src/arena/Constuructors/MagicConstructor.ts b/src/arena/Constuructors/MagicConstructor.ts index 17ae3482..b3f2b669 100644 --- a/src/arena/Constuructors/MagicConstructor.ts +++ b/src/arena/Constuructors/MagicConstructor.ts @@ -1,11 +1,14 @@ import { floatNumber } from '../../utils/floatNumber'; +import CastError from '../errors/CastError'; import type Game from '../GameService'; import type * as magics from '../magics'; import MiscService from '../MiscService'; import type { Player } from '../PlayersService'; +import { AffectableAction } from './AffectableAction'; import type { - BaseNext, Breaks, BreaksMessage, CustomMessage, + BaseNext, BreaksMessage, CustomMessage, FailArgs, SuccessArgs, } from './types'; +import { handleCastError } from './utils'; export type MagicNext = BaseNext & { actionType: 'magic'; @@ -19,7 +22,7 @@ export interface MagicArgs { cost: number; costType: 'mp' | 'en'; lvl: number; - orderType: 'all' | 'any' | 'enemy' | 'self'; + orderType: 'all' | 'any' | 'enemy' | 'self' | 'team'; aoeType: 'target' | 'team' | 'targetAoe'; baseExp: number; effect: string[]; @@ -34,17 +37,8 @@ export interface MagicArgs { export interface Magic extends MagicArgs, CustomMessage { } -export abstract class Magic { - params!: { - initiator: Player; - target: Player; - game: Game; - }; - - status: { - exp: number; - effect?: number; - }; +export abstract class Magic extends AffectableAction { + name: keyof typeof magics; isLong = false; @@ -53,8 +47,10 @@ export abstract class Magic { * @param magObj Объект создаваемой магии */ constructor(magObj: MagicArgs) { + super(); + Object.assign(this, magObj); - this.resetStatus(); + this.reset(); } // Дальше идут общие методы для всех магий @@ -71,7 +67,7 @@ export abstract class Magic { }; try { this.getCost(initiator); - this.checkPreAffects(initiator, target, game); + this.checkPreAffects(); this.isBlurredMind(); // проверка не запудрило this.checkChance(); this.run(initiator, target, game); // вызов кастомного обработчика @@ -79,12 +75,14 @@ export abstract class Magic { this.checkTargetIsDead(); this.next(); - } catch (failMsg) { + } catch (e) { // @fixme прокидываем ошибку выше для длительных кастов - if (this.isLong) throw (failMsg); - game.recordOrderResult(failMsg); + if (this.isLong) throw (e); + handleCastError(e, (reason) => { + game.recordOrderResult(this.getFailResult(reason)); + }); } finally { - this.resetStatus(); + this.reset(); } } @@ -100,7 +98,7 @@ export abstract class Magic { if (costValue >= 0) { initiator.stats.set(this.costType, costValue); } else { - throw this.breaks('NO_MANA'); + throw new CastError('NO_MANA'); } } @@ -157,7 +155,7 @@ export abstract class Magic { // Магия прошла, проверяем что скажут боги if (this.godCheck()) { // Боги фейлят шанс - throw this.breaks('GOD_FAIL'); + throw new CastError('GOD_FAIL'); } else { // Магия прошла return true; @@ -169,7 +167,7 @@ export abstract class Magic { return true; } // Магия остается фейловой - throw this.breaks('CHANCE_FAIL'); + throw new CastError('CHANCE_FAIL'); } } @@ -217,13 +215,6 @@ export abstract class Magic { return MiscService.rndm('1d100') <= 5; } - /** - * @param initiator объект персонажа - * @param target объект персонажа - * @param game Объект игры для доступа ко всему - */ - abstract run(initiator: Player, target: Player, game: Game): void - /** * Проверка на запудривание мозгов * @todo нужно вынести этот метод в orders или к Players Obj @@ -235,22 +226,6 @@ export abstract class Magic { } } - /** - * Проверка на запудривание мозгов - * @param initiator объект персонажа - * @param _target объект цели магии - * @param _game Объект игры для доступа ко всему - * @todo нужно вынести этот метод в orders - */ - // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars - checkPreAffects(initiator: Player, _target: Player, _game: Game): void { - const { isSilenced } = initiator.flags; - if (isSilenced.some((e) => e.initiator !== this.name)) { - // если кастер находится под безмолвием/бунтом богов - throw this.breaks('SILENCED'); - } - } - /** * Проверка убита ли цель * @todo после того как был нанесен урон любым dmg action, следует производить @@ -263,23 +238,8 @@ export abstract class Magic { } } - /** - * @param msg строка остановки магии (причина) - * @return объект остановки магии - */ - breaks(msg: BreaksMessage): Breaks { - return { - actionType: 'magic', - message: msg, - action: this.displayName, - initiator: this.params.initiator.nick, - target: this.params.target.nick, - }; - } - - protected getNextArgs(): MagicNext { - const { target, initiator } = this.params; - return { + getSuccessResult({ initiator, target } = this.params): SuccessArgs { + const result: MagicNext = { exp: this.status.exp, action: this.displayName, actionType: 'magic', @@ -288,21 +248,40 @@ export abstract class Magic { effect: this.status.effect, msg: this.customMessage?.bind(this), }; + + this.reset(); + + return result; } - resetStatus() { + getFailResult( + reason: BreaksMessage | SuccessArgs | SuccessArgs[], + params = this.params, + ): FailArgs { + const result: FailArgs = { + actionType: 'phys', + reason, + action: this.displayName, + initiator: params.initiator.nick, + target: params.target.nick, + weapon: params.initiator.weapon.item, + }; + + this.reset(); + + return result; + } + + reset() { this.status = { exp: 0, effect: 0, }; } - /** - * Магия прошла удачно - * @todo тут нужен вывод требуемых параметров - */ - next(): void { - const { game } = this.params; - game.recordOrderResult(this.getNextArgs()); + next({ initiator, target, game } = this.params): void { + const result = this.getSuccessResult({ initiator, target, game }); + + game.recordOrderResult(result); } } diff --git a/src/arena/Constuructors/PhysConstructor.js b/src/arena/Constuructors/PhysConstructor.js deleted file mode 100644 index bbce4c3e..00000000 --- a/src/arena/Constuructors/PhysConstructor.js +++ /dev/null @@ -1,221 +0,0 @@ -const { floatNumber } = require('../../utils/floatNumber'); -const MiscService = require('../MiscService'); -const { isSuccessResult } = require('./utils'); - -/** - * @typedef {import ('../GameService').default} game - * @typedef {import ('../PlayersService').Player} player - */ - -/** - * Конструктор физической атаки - * (возможно физ скилы) - * @todo Сейчас при отсутствие защиты на цели, не учитывается статик протект( - * ???) Т.е если цель не защищается атака по ней на 95% удачна - * */ -class PhysConstructor { /** - * @type {import('./PreAffect').PreAffect[]} - * */ - preAffects; - /** - * Конструктор атаки - * @param {atkAct} atkAct имя actions - * @typedef {Object} atkAct - * @property {String} name - * @property {string} displayName - * @property {String} desc - * @property {Number} lvl - * @property {String} orderType - * - * @param {import('./PreAffect').PreAffect[]} preAffects - */ - constructor(atkAct) { - this.name = atkAct.name; - this.displayName = atkAct.displayName; - this.desc = atkAct.desc; - this.lvl = atkAct.lvl; - this.orderType = atkAct.orderType; - this.status = { hit: 0, exp: 0 }; - /** - * @type {import('./PreAffect').PreAffect[]} - * */ - this.preAffects = []; - } - - /** - * Основная функция выполнения. Из неё дёргаются все зависимости - * Общий метод для скилов физической атаки - * @param {player} initiator Объект кастера - * @param {player} target Объект цели - * @param {game} game Объект игры (не обязателен) - */ - cast(initiator, target, game) { - this.params = { - initiator, target, game, - }; - this.status = { hit: 0, exp: 0 }; - try { - this.fitsCheck(); - this.calculateHit(); - this.checkPreAffects(); - this.isBlurredMind(); - this.applyHit(); - - this.checkPostEffect(); - this.checkTargetIsDead(); - this.next(); - } catch (e) { - this.next(e); - } - } - - /** - * Проверка флагов влияющих на физический урон - */ - checkPreAffects() { - if (this.params.game.flags.global.isEclipsed) throw this.breaks('ECLIPSE'); - - this.preAffects.forEach((preAffect) => { - const result = preAffect.check(this.params, { value: this.status.hit }); - - if (result && isSuccessResult(result)) { - throw this.breaks(result.message, result); - } - }); - } - - /** - * Проверка флагов влияющих на физический урон - */ - fitsCheck() { - const { initiator } = this.params; - if (!initiator.weapon.hasWeapon()) { - throw this.breaks('NO_WEAPON'); - } - } - - /** - * Проверка флагов влияющих на выбор цели - */ - isBlurredMind() { - const { initiator, game } = this.params; - if (initiator.flags.isGlitched) { - // Меняем цель внутри атаки на любого живого в игре - this.params.target = game.players.randomAlive; - } - if (initiator.flags.isMad) { - this.params.target = initiator; - } - if (initiator.flags.isParalysed) { - throw this.breaks('PARALYSED'); - } - } - - /** - * Проверка прохождения защиты цели - * Если проверка провалена, выставляем флаг isHited, означающий что - * атака прошла - */ - calculateHit() { - const { initiator } = this.params; - - const initiatorHitParam = initiator.stats.val('hit'); - const hitval = MiscService.randInt( - initiatorHitParam.min, - initiatorHitParam.max, - ); - this.status.hit = floatNumber(hitval * initiator.proc); - } - - applyHit() { - const { initiator } = this.params; - - this.params.target.flags.isHited = { - initiator: initiator.nick, val: this.status.hit, - }; - this.run(); - } - - /** - * Запуск работы actions - */ - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function - run() { - } - - /** - * Проверка postEffector от Fits - */ - checkPostEffect() { - return this; - } - - /** - * Функция агрегации данных после выполннения действия - */ - next(failMsg) { - const { initiator, target, game } = this.params; - const weapon = initiator.weapon.item; - if (failMsg) { - game.recordOrderResult({ ...failMsg, weapon }); - } else { - const msg = { - exp: this.status.exp, - action: this.name, - actionType: 'phys', - target: target.nick, - dmg: floatNumber(this.status.hit), - hp: target.stats.val('hp'), - initiator: initiator.nick, - weapon, - dmgType: 'physical', - }; - game.recordOrderResult(msg); - } - } - - /** - * Проверка убита ли цель - * @todo после того как был нанесен урон любым dmg action, следует производить - * общую проверку - */ - checkTargetIsDead() { - const { initiator, target } = this.params; - const hpNow = target.stats.val('hp'); - if (hpNow <= 0 && !target.getKiller()) { - target.setKiller(initiator); - } - } - - /** - * @param {string} message строка остановки атаки (причина) - * @param {import('./types').SuccessArgs} cause строка остановки атаки (причина) - */ - breaks(message, cause) { - return { - actionType: 'phys', - message, - cause, - action: this.name, - initiator: this.params.initiator.nick, - target: this.params.target.nick, - }; - } - - /** - * Расчитываем полученный exp - */ - getExp() { - const { initiator, target, game } = this.params; - - if (game.isPlayersAlly(initiator, target) && !initiator.flags.isGlitched) { - this.status.exp = 0; - } else { - const exp = this.status.hit * 8; - this.status.exp = Math.round(exp); - initiator.stats.mode('up', 'exp', this.status.exp); - } - } -} - -module.exports = PhysConstructor; diff --git a/src/arena/Constuructors/PhysConstructor.ts b/src/arena/Constuructors/PhysConstructor.ts new file mode 100644 index 00000000..d01353ce --- /dev/null +++ b/src/arena/Constuructors/PhysConstructor.ts @@ -0,0 +1,185 @@ +import type { Item } from '@/models/item'; +import { floatNumber } from '../../utils/floatNumber'; +import CastError from '../errors/CastError'; +import type GameService from '../GameService'; +import MiscService from '../MiscService'; +import type { Player } from '../PlayersService'; +import { AffectableAction } from './AffectableAction'; +import type { + BaseNext, BreaksMessage, DamageType, FailArgs, OrderType, SuccessArgs, +} from './types'; + +import { handleCastError } from './utils'; + +export type PhysNext = BaseNext & { + actionType: 'phys'; + dmg: number; + hp: number; + weapon: Item | undefined; + dmgType: DamageType, +} +/** + * Конструктор физической атаки + * (возможно физ скилы) + * @todo Сейчас при отсутствие защиты на цели, не учитывается статик протект( + * ???) Т.е если цель не защищается атака по ней на 95% удачна + * */ +export default abstract class PhysConstructor extends AffectableAction { + name: string; + displayName: string; + desc: string; + lvl: number; + orderType: OrderType; + + constructor(atkAct) { + super(); + + this.name = atkAct.name; + this.displayName = atkAct.displayName; + this.desc = atkAct.desc; + this.lvl = atkAct.lvl; + this.orderType = atkAct.orderType; + this.status = { effect: 0, exp: 0 }; + } + + /** + * Основная функция выполнения. Из неё дёргаются все зависимости + * Общий метод для скилов физической атаки + * @param initiator Объект кастера + * @param target Объект цели + * @param game Объект игры + */ + cast(initiator: Player, target: Player, game: GameService) { + this.params = { + initiator, target, game, + }; + this.reset(); + + try { + this.fitsCheck(); + this.calculateHit(); + this.checkPreAffects(); + this.isBlurredMind(); + + this.run(initiator, target, game); + this.getExp(); + + this.checkPostAffects(); + this.checkTargetIsDead(); + this.next(); + } catch (e) { + handleCastError(e, (reason) => { + game.recordOrderResult(this.getFailResult(reason)); + }); + } + } + + /** + * Проверка флагов влияющих на физический урон + */ + fitsCheck() { + const { initiator } = this.params; + if (!initiator.weapon.hasWeapon()) { + throw new CastError('NO_WEAPON'); + } + } + + /** + * Проверка флагов влияющих на выбор цели + */ + isBlurredMind() { + const { initiator, game } = this.params; + if (initiator.flags.isGlitched) { + // Меняем цель внутри атаки на любого живого в игре + this.params.target = game.players.randomAlive; + } + if (initiator.flags.isMad) { + this.params.target = initiator; + } + } + + /** + * Проверка прохождения защиты цели + * Если проверка провалена, выставляем флаг isHited, означающий что + * атака прошла + */ + calculateHit() { + const { initiator } = this.params; + + const initiatorHitParam = initiator.stats.val('hit'); + const hitval = MiscService.randInt( + initiatorHitParam.min, + initiatorHitParam.max, + ); + this.status.effect = floatNumber(hitval * initiator.proc); + } + + /** + * Рассчитываем полученный exp + */ + getExp({ initiator, target, game } = this.params) { + if (game.isPlayersAlly(initiator, target) && !initiator.flags.isGlitched) { + this.status.exp = 0; + } else { + const exp = this.status.effect * 8; + this.status.exp = Math.round(exp); + initiator.stats.up('exp', this.status.exp); + } + } + + getSuccessResult({ initiator, target } = this.params): SuccessArgs { + const result: PhysNext = { + exp: this.status.exp, + action: this.displayName, + actionType: 'phys', + target: target.nick, + dmg: floatNumber(this.status.effect), + hp: target.stats.val('hp'), + initiator: initiator.nick, + weapon: initiator.weapon.item, + dmgType: 'physical', + affects: this.getAffects(), + }; + + this.reset(); + + return result; + } + + getFailResult( + reason: BreaksMessage | SuccessArgs | SuccessArgs[], + params = this.params, + ): FailArgs { + const result: FailArgs = { + actionType: 'phys', + reason, + action: this.displayName, + initiator: params.initiator.nick, + target: params.target.nick, + weapon: params.initiator.weapon.item, + }; + + this.reset(); + + return result; + } + + /** + * Проверка убита ли цель + * @todo после того как был нанесен урон любым dmg action, следует производить + * общую проверку + */ + checkTargetIsDead() { + const { initiator, target } = this.params; + const hpNow = target.stats.val('hp'); + if (hpNow <= 0 && !target.getKiller()) { + target.setKiller(initiator); + } + } + + next({ initiator, target, game } = this.params): void { + const result = this.getSuccessResult({ initiator, target, game }); + + game.recordOrderResult(result); + } +} diff --git a/src/arena/Constuructors/PreAffect.ts b/src/arena/Constuructors/PreAffect.ts deleted file mode 100644 index 1f6bf013..00000000 --- a/src/arena/Constuructors/PreAffect.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type GameService from '../GameService'; -import type { Player } from '../PlayersService'; -import type { SuccessArgs } from './types'; - -export interface PreAffect { - check( - params: { initiator: Player, target: Player, game: GameService}, - status: { value: number } - ): SuccessArgs | void -} diff --git a/src/arena/Constuructors/SkillConstructor.ts b/src/arena/Constuructors/SkillConstructor.ts index cdd789c7..456fe094 100644 --- a/src/arena/Constuructors/SkillConstructor.ts +++ b/src/arena/Constuructors/SkillConstructor.ts @@ -1,9 +1,11 @@ import type { Profs } from '../../data'; +import CastError from '../errors/CastError'; import type Game from '../GameService'; import MiscService from '../MiscService'; import type { Player } from '../PlayersService'; +import { BaseAction } from './BaseAction'; import type { - CostType, OrderType, AOEType, CustomMessage, BaseNext, Breaks, BreaksMessage, + CostType, OrderType, AOEType, CustomMessage, BaseNext, BreaksMessage, FailArgs, SuccessArgs, } from './types'; import { handleCastError } from './utils'; @@ -33,29 +35,16 @@ interface SkillArgs { export interface Skill extends SkillArgs, CustomMessage { } -export abstract class Skill { - params!: { - initiator: Player; - target: Player; - game: Game; - }; - - status = { - exp: 0, - }; - +export abstract class Skill extends BaseAction { /** * Создание скила */ constructor(params: SkillArgs) { + super(); + Object.assign(this, params); } - /** - * Пустая функция для потомка - */ - abstract run(): void - /** * Основная точка вхождения в выполнение скила * @param initiator инициатор @@ -69,11 +58,11 @@ export abstract class Skill { try { this.getCost(); this.checkChance(); - this.run(); - this.success(); - } catch (error) { - handleCastError(error, (error) => { - this.fail(error.message); + this.run(initiator, target, game); + this.next(); + } catch (e) { + handleCastError(e, (reason) => { + game.recordOrderResult(this.getFailResult(reason)); }); } } @@ -89,7 +78,7 @@ export abstract class Skill { if (remainingEnergy >= 0) { initiator.stats.set(this.costType, remainingEnergy); } else { - throw this.getFailResult('NO_ENERGY'); + throw new CastError('NO_ENERGY'); } } @@ -99,7 +88,7 @@ export abstract class Skill { checkChance(): void { if (MiscService.rndm('1d100') > this.getChance()) { // скил сфейлился - throw this.getFailResult('SKILL_FAIL'); + throw new CastError('SKILL_FAIL'); } } @@ -136,13 +125,17 @@ export abstract class Skill { return result; } - getFailResult(message: BreaksMessage, { initiator, target } = this.params): Breaks { - const result: Breaks = { + getFailResult( + reason: BreaksMessage | SuccessArgs | SuccessArgs[], + params = this.params, + ): FailArgs { + const result: FailArgs = { + actionType: 'phys', + reason, action: this.displayName, - initiator: initiator.nick, - target: target.nick, - actionType: 'skill', - message, + initiator: params.initiator.nick, + target: params.target.nick, + weapon: params.initiator.weapon.item, }; this.reset(); @@ -150,25 +143,13 @@ export abstract class Skill { return result; } - /** - * Успешное прохождение скила и отправка записи в BattleLog - */ - success({ initiator, target, game } = this.params): void { - const result = this.getSuccessResult({ initiator, target, game }); - - game.recordOrderResult(result); + reset() { + this.status.exp = 0; } - /** - * Обработка провала магии - */ - fail(message: BreaksMessage, { initiator, target, game } = this.params): void { - const result = this.getFailResult(message, { initiator, target, game }); + next({ initiator, target, game } = this.params): void { + const result = this.getSuccessResult({ initiator, target, game }); game.recordOrderResult(result); } - - reset() { - this.status.exp = 0; - } } diff --git a/src/arena/Constuructors/interfaces/PostAffect.ts b/src/arena/Constuructors/interfaces/PostAffect.ts new file mode 100644 index 00000000..d1552fe8 --- /dev/null +++ b/src/arena/Constuructors/interfaces/PostAffect.ts @@ -0,0 +1,10 @@ +import type GameService from '@/arena/GameService'; +import type { Player } from '@/arena/PlayersService'; +import type { SuccessArgs } from '../types'; + +export interface PostAffect { + postAffect( + params: { initiator: Player, target: Player, game: GameService}, + status: { effect: number, exp: number; } + ): SuccessArgs | SuccessArgs[] | void +} diff --git a/src/arena/Constuructors/interfaces/PreAffect.ts b/src/arena/Constuructors/interfaces/PreAffect.ts new file mode 100644 index 00000000..464b3ef5 --- /dev/null +++ b/src/arena/Constuructors/interfaces/PreAffect.ts @@ -0,0 +1,9 @@ +import type GameService from '@/arena/GameService'; +import type { Player } from '@/arena/PlayersService'; + +export interface PreAffect { + preAffect( + params: { initiator: Player, target: Player, game: GameService}, + status: { effect: number } + ): void +} diff --git a/src/arena/Constuructors/types.ts b/src/arena/Constuructors/types.ts index 73d5ac5c..40eb2a17 100644 --- a/src/arena/Constuructors/types.ts +++ b/src/arena/Constuructors/types.ts @@ -9,6 +9,7 @@ import type { HealMagicNext, HealNext } from './HealMagicConstructor'; import type { LongDmgMagicNext } from './LongDmgMagicConstructor'; import type { LongMagicNext } from './LongMagicConstructor'; import type { MagicNext } from './MagicConstructor'; +import type { PhysNext } from './PhysConstructor'; import type { SkillNext } from './SkillConstructor'; export type CostType = 'en' | 'mp'; @@ -20,18 +21,11 @@ export type BreaksMessage = 'NO_TARGET' | 'NO_MANA' | 'NO_ENERGY' | - 'SILENCED' | 'CHANCE_FAIL' | 'GOD_FAIL' | 'HEAL_FAIL' | 'SKILL_FAIL' | - 'DEF' | - 'DODGED' | - 'ECLIPSE' | - 'NO_WEAPON' | - 'PARRYED' | - 'DISARM' | - 'PARALYSED'; + 'NO_WEAPON'; export type ExpArr = { name: string; @@ -61,25 +55,10 @@ export type BaseNext = { exp: number; initiator: string; target: string; + affects?: SuccessArgs[]; msg?: CustomMessageFn; } -export type PhysNext = BaseNext & { - actionType: 'phys'; - dmg: number; - hp: number; - weapon: Item; - dmgType: DamageType, -} - -export type PhysBreak = Omit & { - actionType: 'phys'; - cause?: SuccessArgs; - message: BreaksMessage; - weapon: Item; - expArr: ExpArr; -} - export type SuccessArgs = MagicNext | DmgMagicNext | @@ -94,13 +73,11 @@ export type SuccessArgs = export type ActionType = SuccessArgs['actionType']; -export interface Breaks { +export interface FailArgs { actionType: ActionType; - message: BreaksMessage; - cause?: SuccessArgs; + reason: BreaksMessage | SuccessArgs | SuccessArgs[]; action: string; initiator: string; target: string; + weapon: Item | undefined; } - -export type FailArgs = Breaks | PhysBreak; diff --git a/src/arena/Constuructors/utils/index.ts b/src/arena/Constuructors/utils/index.ts index 80e07057..963ba75e 100644 --- a/src/arena/Constuructors/utils/index.ts +++ b/src/arena/Constuructors/utils/index.ts @@ -1,12 +1,13 @@ +import CastError from '@/arena/errors/CastError'; import type { - Breaks, FailArgs, SuccessArgs, + BreaksMessage, FailArgs, SuccessArgs, } from '../types'; type Result = SuccessArgs | FailArgs; type SuccessDamageResult = T extends { dmg: number } ? T : never; export const isSuccessResult = (result: Result): result is SuccessArgs => { - return !('message' in result); + return !('reason' in result); }; export const isSuccessDamageResult = (result: Result): result is SuccessDamageResult => { @@ -18,7 +19,6 @@ export const isSuccessDamageResult = (result: Result): result is SuccessDamageRe }; export const isPhysicalDamageResult = (result: Result): result is SuccessDamageResult => { - console.log(result); if (isSuccessDamageResult(result)) { return result.dmgType === 'physical'; } @@ -32,10 +32,13 @@ export const findByTarget = (target: string) => { }; }; -export const handleCastError = (error: unknown, onActionError: (error: Breaks) => void) => { - if (error instanceof Error) { - console.error(error); +export const handleCastError = ( + error: unknown, + onActionError: (error: BreaksMessage | SuccessArgs) => void, +) => { + if (error instanceof CastError) { + onActionError(error.reason); } else { - onActionError(error as Breaks); + console.error(error); } }; diff --git a/src/arena/LogService/LogService.test.ts b/src/arena/LogService/LogService.test.ts deleted file mode 100644 index 0df969bd..00000000 --- a/src/arena/LogService/LogService.test.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { LogService } from '@/arena/LogService'; -import TestUtils from '@/utils/testUtils'; - -// npm t src/arena/BattleLog/BattleLog.test.ts - -describe('BattleLog', () => { - let messages: string[] = []; - const writer = (data: string[]) => { - messages = data; - }; - - const logService = new LogService(writer); - - afterEach(() => { - messages = []; - }); - - it('should format magic messages', async () => { - await logService.sendBattleLog([{ - actionType: 'magic', - action: 'Magic', - initiator: 'Player 1', - target: 'Player 2', - exp: 25, - }, { - actionType: 'magic', - action: 'Magic', - initiator: 'Player 1', - target: 'Player 2', - exp: 25, - effect: 10, - }, - { - actionType: 'magic', - action: 'Magic', - initiator: 'Player 1', - target: 'Player 2', - message: 'GOD_FAIL', - }]); - - expect(messages).toMatchSnapshot(); - }); - - it('should format skill messages', async () => { - await logService.sendBattleLog([{ - actionType: 'skill', - action: 'Skill', - initiator: 'Player 1', - target: 'Player 2', - exp: 25, - }, { - actionType: 'skill', - action: 'Skill', - initiator: 'Player 1', - target: 'Player 2', - message: 'SKILL_FAIL', - }]); - - expect(messages).toMatchSnapshot(); - }); - - it('should format phys messages', async () => { - const weapon = await TestUtils.getWeapon(); - - await logService.sendBattleLog([{ - actionType: 'phys', - action: 'Phys', - initiator: 'Player 1', - target: 'Player 2', - weapon, - dmgType: 'physical', - exp: 25, - dmg: 10, - hp: 90, - }, { - actionType: 'phys', - action: 'Phys', - initiator: 'Player 1', - target: 'Player 2', - weapon, - message: 'DEF', - }]); - - expect(messages).toMatchSnapshot(); - }); - - it('should format dmg-magic messages', async () => { - await logService.sendBattleLog([{ - actionType: 'dmg-magic', - action: 'Dmg Magic', - initiator: 'Player 1', - target: 'Player 2', - dmgType: 'acid', - exp: 25, - dmg: 10, - hp: 90, - }, - { - actionType: 'dmg-magic', - action: 'Phys', - initiator: 'Player 1', - target: 'Player 2', - message: 'NO_MANA', - }]); - - expect(messages).toMatchSnapshot(); - }); - - it('should format heal-magic messages', async () => { - await logService.sendBattleLog([{ - actionType: 'heal-magic', - action: 'Heal Magic', - initiator: 'Player 1', - target: 'Player 2', - exp: 10, - hp: 110, - effect: 10, - }, { - actionType: 'heal-magic', - action: 'Heal Magic', - initiator: 'Player 1', - target: 'Player 2', - message: 'SILENCED', - }]); - - expect(messages).toMatchSnapshot(); - }); - - it('should format heal messages with rigth order', async () => { - await logService.sendBattleLog([{ - actionType: 'heal', - action: 'Heal', - initiator: 'Player 1', - target: 'Player 2', - expArr: [{ - id: '1', name: 'Player 1', exp: 10, val: 10, - }], - hp: 110, - effect: 10, - }, { - actionType: 'heal', - action: 'Heal', - initiator: 'Player 3', - target: 'Player 2', - expArr: [{ - id: '3', name: 'Player 3', exp: 10, val: 10, - }], - hp: 120, - effect: 10, - }, { - actionType: 'heal', - action: 'Heal', - initiator: 'Player 1', - target: 'Player 2', - message: 'SILENCED', - }]); - - expect(messages).toMatchSnapshot(); - }); - - it('should format long magic messages with rigth order', async () => { - await logService.sendBattleLog([{ - actionType: 'magic-long', - action: 'Magic 1', - initiator: 'Player 2', - target: 'Player 1', - exp: 25, - }, { - actionType: 'magic-long', - action: 'Magic 1', - initiator: 'Player 1', - target: 'Player 2', - exp: 25, - }, { - actionType: 'magic-long', - action: 'Magic 1', - initiator: 'Player 1', - target: 'Player 2', - exp: 25, - }, { - actionType: 'magic-long', - action: 'Magic 2', - initiator: 'Player 1', - target: 'Player 2', - exp: 25, - }, { - actionType: 'magic-long', - action: 'Magic 2', - initiator: 'Player 2', - target: 'Player 1', - exp: 25, - }]); - - expect(messages).toMatchSnapshot(); - }); - - it('should format long dmg magic messages with rigth order', async () => { - await logService.sendBattleLog([{ - actionType: 'dmg-magic-long', - action: 'Magic 1', - initiator: 'Player 2', - target: 'Player 1', - dmgType: 'clear', - exp: 25, - dmg: 25, - hp: 75, - }, { - actionType: 'dmg-magic-long', - action: 'Magic 1', - initiator: 'Player 1', - target: 'Player 2', - dmgType: 'clear', - exp: 25, - dmg: 25, - hp: 75, - }, { - actionType: 'dmg-magic-long', - action: 'Magic 1', - initiator: 'Player 1', - target: 'Player 2', - dmgType: 'clear', - exp: 25, - dmg: 25, - hp: 50, - }, { - actionType: 'dmg-magic-long', - action: 'Magic 2', - initiator: 'Player 1', - target: 'Player 2', - dmgType: 'clear', - exp: 25, - dmg: 25, - hp: 25, - }, { - actionType: 'dmg-magic-long', - action: 'Magic 2', - initiator: 'Player 2', - target: 'Player 1', - dmgType: 'clear', - exp: 25, - dmg: 25, - hp: 50, - }]); - - expect(messages).toMatchSnapshot(); - }); - - it('should use custom message', async () => { - 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(messages).toMatchSnapshot(); - }); -}); diff --git a/src/arena/LogService/__snapshots__/LogService.test.ts.snap b/src/arena/LogService/__snapshots__/LogService.test.ts.snap deleted file mode 100644 index 2ed6f6c1..00000000 --- a/src/arena/LogService/__snapshots__/LogService.test.ts.snap +++ /dev/null @@ -1,84 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BattleLog should format dmg-magic messages 1`] = ` -[ - "*Player 1* сотворил _Dmg Magic_ на *Player 2* нанеся 10 урона -\\[ ☣ 💔-10/90 📖25 ]", - "*Player 1* пытался использовать _Phys_ на *Player 2, но не хватило маны", -] -`; - -exports[`BattleLog should format heal messages with rigth order 1`] = ` -[ - "Игрок *Player 2* был вылечен 🤲 на *💖20* -\\[ Player 1: 💖10/110 📖10, Player 3: 💖10/110 📖10 ]", - "*Player 1* пытался сотворить _Heal_, но попытка провалилась (безмолвие)", -] -`; - -exports[`BattleLog should format heal-magic messages 1`] = ` -[ - "*Player 1* использовал _Heal Magic_ на *Player 2* -\\[ ❤️+10/110 📖10 ]", - "*Player 1* пытался сотворить _Heal Magic_, но попытка провалилась (безмолвие)", -] -`; - -exports[`BattleLog should format long dmg magic messages with rigth order 1`] = ` -[ - "*Player 2* сотворил _Magic 1_ на *Player 1* нанеся 25 урона -\\[ 💔-25/75 📖25 ]", - "*Player 1* сотворил _Magic 1_ на *Player 2* нанеся 50 урона -\\[ 💔-50/50 📖50 ]", - "*Player 1* сотворил _Magic 2_ на *Player 2* нанеся 25 урона -\\[ 💔-25/25 📖25 ]", - "*Player 2* сотворил _Magic 2_ на *Player 1* нанеся 25 урона -\\[ 💔-25/50 📖25 ]", -] -`; - -exports[`BattleLog should format long magic messages with rigth order 1`] = ` -[ - "*Player 2* использовал _Magic 1_ на *Player 1* -\\[ 📖25 ]", - "*Player 1* использовал _Magic 1_ на *Player 2* -\\[ 📖50 ]", - "*Player 1* использовал _Magic 2_ на *Player 2* -\\[ 📖25 ]", - "*Player 2* использовал _Magic 2_ на *Player 1* -\\[ 📖25 ]", -] -`; - -exports[`BattleLog should format magic messages 1`] = ` -[ - "*Player 1* использовав _Magic_ на *Player 2* -\\[ 📖25 ]", - "*Player 1* использовав _Magic_ на *Player 2* с эффектом 10 -\\[ 📖25 ]", - "Заклинание _Magic_ *Player 1* провалилось по воле богов", -] -`; - -exports[`BattleLog should format phys messages 1`] = ` -[ - "*Player 1* рубанул *Player 2* _Эпическим Мечом_ и нанёс *10* урона -\\[ 💔-10/90 📖25 ]", - "*Player 1* атаковал *Player 2* _Эпическим Мечом_, но тот смог защититься \\[]", -] -`; - -exports[`BattleLog should format skill messages 1`] = ` -[ - "*Player 1* использовал _Skill_ на *Player 2* -\\[ 📖25 ]", - "*Player 1* пытался использовать умение _Skill_, но у него не вышло", -] -`; - -exports[`BattleLog should use custom message 1`] = ` -[ - "custom Initiator|Target|Skill|skill -\\[ 📖10 ]", -] -`; diff --git a/src/arena/LogService/utils/format-action.ts b/src/arena/LogService/utils/format-action.ts index 731e6ad1..625e226d 100644 --- a/src/arena/LogService/utils/format-action.ts +++ b/src/arena/LogService/utils/format-action.ts @@ -1,6 +1,5 @@ -import { isUndefined } from 'lodash'; import type { SuccessArgs } from '@/arena/Constuructors/types'; -import { weaponTypes } from '@/arena/MiscService'; +import { getWeaponAction } from '@/arena/MiscService'; export function formatAction(msgObj: SuccessArgs): string { if (msgObj.msg) { @@ -11,17 +10,16 @@ export function formatAction(msgObj: SuccessArgs): string { case 'heal': return `Игрок *${msgObj.target}* был вылечен 🤲 на *💖${msgObj.effect}*`; case 'phys': { - const { action } = weaponTypes[msgObj.weapon.wtype]; - return `*${msgObj.initiator}* ${action(msgObj.target, msgObj.weapon)} и нанёс *${msgObj.dmg}* урона`; + return `*${msgObj.initiator}* ${getWeaponAction(msgObj.target, msgObj.weapon)} и нанёс *${msgObj.dmg}* урона`; } case 'dmg-magic': case 'dmg-magic-long': case 'aoe-dmg-magic': return `*${msgObj.initiator}* сотворил _${msgObj.action}_ на *${msgObj.target}* нанеся ${msgObj.dmg} урона`; case 'magic': - return isUndefined(msgObj.effect) - ? `*${msgObj.initiator}* использовав _${msgObj.action}_ на *${msgObj.target}*` - : `*${msgObj.initiator}* использовав _${msgObj.action}_ на *${msgObj.target}* с эффектом ${msgObj.effect}`; + return !msgObj.effect + ? `*${msgObj.initiator}* использовал _${msgObj.action}_ на *${msgObj.target}*` + : `*${msgObj.initiator}* использовал _${msgObj.action}_ на *${msgObj.target}* с эффектом ${msgObj.effect}`; default: return `*${msgObj.initiator}* использовал _${msgObj.action}_ на *${msgObj.target}*`; } diff --git a/src/arena/LogService/utils/format-cause.ts b/src/arena/LogService/utils/format-cause.ts index d42bf84d..c22a41c0 100644 --- a/src/arena/LogService/utils/format-cause.ts +++ b/src/arena/LogService/utils/format-cause.ts @@ -1,4 +1,5 @@ -import { SuccessArgs } from '@/arena/Constuructors/types'; +import type { SuccessArgs } from '@/arena/Constuructors/types'; +import * as icons from '@/utils/icons'; export function formatCause(cause: SuccessArgs) { switch (cause.actionType) { @@ -6,6 +7,8 @@ export function formatCause(cause: SuccessArgs) { return `_${cause.action}_ *${cause.initiator}*: 📖${cause.exp}`; case 'protect': return `_${cause.action}_ ${cause.expArr.map(({ name, exp }) => `*${name}*: 📖${exp}`)}`; + case 'dmg-magic': + return `_${cause.action}_ *${cause.initiator}* >> *${cause.target}* ${`${icons.damageType[cause.dmgType]} 💔-${cause.dmg}/${cause.hp}`}`; default: return `_${cause.action}_ *${cause.initiator}*`; } diff --git a/src/arena/LogService/utils/format-error.ts b/src/arena/LogService/utils/format-error.ts index 1a1da433..a47d94c0 100644 --- a/src/arena/LogService/utils/format-error.ts +++ b/src/arena/LogService/utils/format-error.ts @@ -9,99 +9,71 @@ import { formatCause } from './format-cause'; */ export function formatError(msgObj: FailArgs): string { const { - action, actionType, message, target, initiator, cause, + action, actionType, reason, target, initiator, } = msgObj; - const expString = 'expArr' in msgObj ? msgObj.expArr.map(({ name, exp }) => `${name}: 📖${exp}`).join(', ') : ''; - const weapon = 'weapon' in msgObj ? msgObj.weapon.case : ''; + if (typeof reason === 'string') { + const TEXT: Record> = { + NO_INITIATOR: { + ru: `Некто хотел использовать _${action}_ на игрока *${target}*, но исчез`, + en: '', + }, + NO_TARGET: { + ru: `Цель для заклинания _${action}_ игрока *${initiator}* не была найдена`, + en: '', + }, + NO_MANA: { + ru: `*${initiator}* пытался использовать _${action}_ на *${target}, но не хватило маны`, + en: '', + }, + NO_ENERGY: { + ru: `*${initiator}* пытался применить _${action}_, но не хватило энергии`, + en: '', + }, + CHANCE_FAIL: { + ru: `*${initiator}* пытался сотворить _${action}_, но у него не вышло`, + en: '', + }, + GOD_FAIL: { + ru: `Заклинание _${action}_ *${initiator}* провалилось по воле богов`, + en: '', + }, + HEAL_FAIL: { + ru: `*${initiator}* пытался _вылечить_ *${target}*, но тот был атакован`, + en: '', + }, + SKILL_FAIL: { + ru: `*${initiator}* пытался использовать умение _${action}_, но у него не вышло`, + en: '', + }, + NO_WEAPON: { + ru: `*${initiator}* пытался атаковать *${target}*, но у него не оказалось оружия в руках`, + en: '', + }, + default: { + ru: 'Ошибка парсинга строки магии', + en: '', + }, + }; - if (cause) { - switch (actionType) { - case 'phys': - return `*${initiator} пытался атаковать ${target}, но у него не получилось\n${formatCause(cause)}`; - default: - return `*${initiator} пытался использовать _${action}_ ${target}, но у него не получилось\n${formatCause(cause)}`; + const text = TEXT[reason]; + + if (!text) { + console.log(reason); + return TEXT.default.ru; } + // @todo сейчас battleLog на стороне клиента не понимает типы магий, и + // просто отображает полученную строку + return text.ru; } - const TEXT: Record> = { - NO_INITIATOR: { - ru: `Некто хотел использовать _${action}_ на игрока *${target}*, но исчез`, - en: '', - }, - NO_TARGET: { - ru: `Цель для заклинания _${action}_ игрока *${initiator}* не была найдена`, - en: '', - }, - NO_MANA: { - ru: `*${initiator}* пытался использовать _${action}_ на *${target}, но не хватило маны`, - en: '', - }, - NO_ENERGY: { - ru: `*${initiator}* пытался применить _${action}_, но не хватило энергии`, - en: '', - }, - SILENCED: { - ru: `*${initiator}* пытался сотворить _${action}_, но попытка провалилась (безмолвие)`, - en: '', - }, - CHANCE_FAIL: { - ru: `*${initiator}* пытался сотворить _${action}_, но у него не вышло`, - en: '', - }, - GOD_FAIL: { - ru: `Заклинание _${action}_ *${initiator}* провалилось по воле богов`, - en: '', - }, - HEAL_FAIL: { - ru: `*${initiator}* пытался _вылечить_ *${target}*, но тот был атакован`, - en: '', - }, - SKILL_FAIL: { - ru: `*${initiator}* пытался использовать умение _${action}_, но у него не вышло`, - en: '', - }, - NO_WEAPON: { - ru: `*${initiator}* пытался атаковать *${target}*, но у него не оказалось оружия в руках`, - en: '', - }, - DEF: { - ru: `*${initiator}* атаковал *${target}* _${weapon}_, но тот смог защититься \\[${expString}]`, - en: '', - }, - DODGED: { - ru: `*${initiator}* атаковал *${target}* _${weapon}_, но тот уклонился от атаки`, - en: '', - }, - ECLIPSE: { - ru: `*${initiator}* попытался атаковал *${target}* но ничего не увидел во тьме`, - en: '', - }, - PARALYSED: { - ru: `*${initiator}* попытался атаковал но был парализован 🗿`, - en: '', - }, - PARRYED: { - ru: `*${initiator}* пытался атаковать *${target}*, но атака была \\[_Парированна_]`, - en: '', - }, - DISARM: { - ru: `*${initiator}* пытался атаковать *${target}*, но оказался \\[_Обезаружен_]`, - en: '', - }, - default: { - ru: 'Ошибка парсинга строки магии', - en: '', - }, - }; - - const text = TEXT[message]; + const normalizedReason = Array.isArray(reason) ? reason : [reason]; + const formattedReason = normalizedReason.map(formatCause).join('\n'); - if (!text) { - console.log(message); - return TEXT.default.ru; + switch (actionType) { + case 'phys': + return `*${initiator}* пытался атаковать *${target}*, но у него не получилось\n${formattedReason}`; + default: + return `*${initiator}* пытался использовать _${action}_ на *${target}*, но у него не получилось\n${formattedReason}`; } - // @todo сейчас battleLog на стороне клиента не понимает типы магий, и - // просто отображает полученную строку - return text.ru; } diff --git a/src/arena/LogService/utils/format-exp.ts b/src/arena/LogService/utils/format-exp.ts index 9e64831d..c5fac2ff 100644 --- a/src/arena/LogService/utils/format-exp.ts +++ b/src/arena/LogService/utils/format-exp.ts @@ -1,7 +1,7 @@ import type { SuccessArgs } from '@/arena/Constuructors/types'; import * as icons from '@/utils/icons'; -const expBrackets = (str: string) => `\n\\[ ${str} ]`; +const expBrackets = (str: string) => `\\[ ${str} ]`; export function formatExp(args: SuccessArgs): string { switch (args.actionType) { @@ -16,7 +16,7 @@ export function formatExp(args: SuccessArgs): string { `${args.target} ${damageType} 💔-${args.dmg}/${args.hp} 📖${args.exp}`, ...args.expArr.map(({ name, val, hp, exp, - }) => `${name} ${damageType} 💔-${val}/${hp} 📖${exp}`), + }) => `${name} ${damageType} 💔-${val}/${hp} 📖${exp}`), ].join('\n')); } case 'heal-magic': { diff --git a/src/arena/LogService/utils/format-message.ts b/src/arena/LogService/utils/format-message.ts index 8473806b..e6c8a815 100644 --- a/src/arena/LogService/utils/format-message.ts +++ b/src/arena/LogService/utils/format-message.ts @@ -4,9 +4,17 @@ import { formatAction } from './format-action'; import { formatError } from './format-error'; import { formatExp } from './format-exp'; -export function formatMessage(msgObj: SuccessArgs | FailArgs) { +export function formatMessage(msgObj: SuccessArgs | FailArgs, depth = 0): string { + const indent = '\t'.repeat(depth); + if (isSuccessResult(msgObj)) { - return `${formatAction(msgObj)}${formatExp(msgObj)}`; + if (!msgObj.affects?.length) { + return `${indent}${formatAction(msgObj)}\n${indent}${formatExp(msgObj)}`; + } + + const affects = msgObj.affects.map((msgObj) => formatMessage(msgObj, depth + 1)); + + return `${indent}${formatAction(msgObj)}\n${indent}${formatExp(msgObj)}\n${affects.join('\n')}`; } return formatError(msgObj); diff --git a/src/arena/MiscService.js b/src/arena/MiscService.js index 6e0a9594..676235dd 100644 --- a/src/arena/MiscService.js +++ b/src/arena/MiscService.js @@ -95,6 +95,9 @@ function randInt(min, max) { module.exports = { weaponTypes: WEAPON_TYPES, stores: STORES, + getWeaponAction(target, weapon) { + return WEAPON_TYPES[weapon.wtype].action(target, weapon); + }, /** * Функция рандома по формату 1d100+10; * @param {String} diceStr параметры рандома в формате 1d100 diff --git a/src/arena/PlayersService/Player.ts b/src/arena/PlayersService/Player.ts index c9416aac..828c4875 100644 --- a/src/arena/PlayersService/Player.ts +++ b/src/arena/PlayersService/Player.ts @@ -179,4 +179,12 @@ export default class Player { getSkillLevel(skill: string) { return this.skills[skill] ?? 0; } + + isAlly(player: Player) { + if (!this.clan || !player.clan) { + return false; + } + + return this.clan.id === player.clan.id; + } } diff --git a/src/arena/actions/__snapshots__/handsHeal.test.ts.snap b/src/arena/actions/__snapshots__/handsHeal.test.ts.snap new file mode 100644 index 00000000..b083f7a7 --- /dev/null +++ b/src/arena/actions/__snapshots__/handsHeal.test.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`protect should be stopped by attack 1`] = ` +"*quisquam* рубанул *corporis* _Эпическим Мечом_ и нанёс *11.7* урона +\\[ 💔-11.7/-3.7 📖94 ] +*corporis* пытался использовать _Лечение руками_ на *corporis*, но у него не получилось +_Атака_ *corporis*" +`; diff --git a/src/arena/actions/__snapshots__/protect.test.ts.snap b/src/arena/actions/__snapshots__/protect.test.ts.snap new file mode 100644 index 00000000..c51409eb --- /dev/null +++ b/src/arena/actions/__snapshots__/protect.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`protect should not get exp if protect enemy 1`] = ` +"*corporis* пытался атаковать *quisquam*, но у него не получилось +_Защита_ *quisquam*: 📖43,*corporis*: 📖0" +`; + +exports[`protect should protect player 1`] = ` +"*corporis* пытался атаковать *quisquam*, но у него не получилось +_Защита_ *quisquam*: 📖44" +`; diff --git a/src/arena/actions/attack.ts b/src/arena/actions/attack.ts index edf241be..1b089ecb 100644 --- a/src/arena/actions/attack.ts +++ b/src/arena/actions/attack.ts @@ -1,8 +1,10 @@ +import type { PreAffect } from '../Constuructors/interfaces/PreAffect'; import PhysConstructor from '../Constuructors/PhysConstructor'; +import CastError from '../errors/CastError'; /** * Физическая атака */ -class Attack extends PhysConstructor { +class Attack extends PhysConstructor implements PreAffect { constructor() { super({ name: 'attack', @@ -20,11 +22,19 @@ class Attack extends PhysConstructor { if (!this.params) { return; } + const { initiator, target } = this.params; - const { target } = this.params; - target.stats.down('hp', this.status.hit); - // getExp вынесен сюда, для возможности "сбросить" атаку, если она была заблокирована protect - this.getExp(); + target.flags.isHited = { + initiator: initiator.nick, val: this.status.effect, + }; + + target.stats.down('hp', this.status.effect); + } + + preAffect({ initiator, target, game } = this.params): void { + if (target.flags.isHited) { + throw new CastError(this.getSuccessResult({ initiator: target, target: initiator, game })); + } } } export default new Attack(); diff --git a/src/arena/actions/handsHeal.test.ts b/src/arena/actions/handsHeal.test.ts new file mode 100644 index 00000000..617289e4 --- /dev/null +++ b/src/arena/actions/handsHeal.test.ts @@ -0,0 +1,44 @@ +import casual from 'casual'; +import GameService from '@/arena/GameService'; +import { type Char } from '@/models/character'; +import TestUtils from '@/utils/testUtils'; +import attack from './attack'; +import handsHeal from './handsHeal'; + +// npm t src/arena/actions/handsHeal.test.ts + +describe('protect', () => { + let game: GameService; + let initiator: Char; + let target: Char; + + beforeAll(async () => { + casual.seed(1); + handsHeal.registerPreAffects([attack]); + + initiator = await TestUtils.createCharacter({}, { withWeapon: true }); + target = await TestUtils.createCharacter(); + }); + + beforeEach(async () => { + game = new GameService([initiator.id, target.id]); + }); + + beforeEach(() => { + jest.spyOn(global.Math, 'random').mockReturnValue(0.5); + }); + + afterEach(() => { + jest.spyOn(global.Math, 'random').mockRestore(); + }); + + it('should be stopped by attack', () => { + game.players.players[0].proc = 1; + game.players.players[1].proc = 1; + + attack.cast(game.players.players[0], game.players.players[1], game); + handsHeal.cast(game.players.players[1], game.players.players[1], game); + + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); + }); +}); diff --git a/src/arena/actions/handsHeal.ts b/src/arena/actions/handsHeal.ts index 40901bac..c2f036d7 100644 --- a/src/arena/actions/handsHeal.ts +++ b/src/arena/actions/handsHeal.ts @@ -18,15 +18,11 @@ class HandsHeal extends Heal { } run(initiator: Player, target: Player, _game: Game): void { - if (target.flags.isHited) { - throw this.breaks('HEAL_FAIL'); - } else { - this.status.val = this.effectVal(); - target.stats.up('hp', this.status.val); - target.flags.isHealed.push({ - initiator: initiator.id, val: this.status.val, - }); - } + this.status.effect = this.effectVal(); + target.stats.up('hp', this.status.effect); + target.flags.isHealed.push({ + initiator: initiator.id, val: this.status.effect, + }); } } diff --git a/src/arena/actions/protect.test.ts b/src/arena/actions/protect.test.ts new file mode 100644 index 00000000..f82cc95e --- /dev/null +++ b/src/arena/actions/protect.test.ts @@ -0,0 +1,57 @@ +import casual from 'casual'; +import GameService from '@/arena/GameService'; +import { type Char } from '@/models/character'; +import TestUtils from '@/utils/testUtils'; +import attack from './attack'; +import protect from './protect'; + +// npm t src/arena/actions/protect.test.ts + +describe('protect', () => { + let game: GameService; + let initiator: Char; + let target: Char; + + beforeAll(async () => { + casual.seed(1); + attack.registerPreAffects([protect]); + + initiator = await TestUtils.createCharacter(); + target = await TestUtils.createCharacter({}, { withWeapon: true }); + }); + + beforeEach(async () => { + game = new GameService([initiator.id, target.id]); + }); + + beforeEach(() => { + jest.spyOn(global.Math, 'random').mockReturnValue(0.5); + }); + + afterEach(() => { + jest.spyOn(global.Math, 'random').mockRestore(); + }); + + it('should protect player', () => { + game.players.players[0].proc = 1; + game.players.players[0].stats.set('pdef', 100); + game.players.players[1].proc = 0.1; + + protect.cast(game.players.players[0], game.players.players[0], game); + attack.cast(game.players.players[1], game.players.players[0], game); + + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); + }); + + it('should not get exp if protect enemy', () => { + game.players.players[0].proc = 1; + game.players.players[0].stats.set('pdef', 10); + game.players.players[1].proc = 0.1; + + protect.cast(game.players.players[0], game.players.players[0], game); + protect.cast(game.players.players[1], game.players.players[0], game); + attack.cast(game.players.players[1], game.players.players[0], game); + + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); + }); +}); diff --git a/src/arena/actions/protect.ts b/src/arena/actions/protect.ts index 8b2e7025..95cbe717 100644 --- a/src/arena/actions/protect.ts +++ b/src/arena/actions/protect.ts @@ -1,9 +1,12 @@ -import { PreAffect } from '@/arena/Constuructors/PreAffect'; -import type { BaseNext, ExpArr, OrderType } from '@/arena/Constuructors/types'; +import type { + BaseNext, ExpArr, OrderType, SuccessArgs, +} from '@/arena/Constuructors/types'; import type Game from '@/arena/GameService'; import MiscService from '@/arena/MiscService'; import type { Player } from '@/arena/PlayersService'; import { floatNumber } from '@/utils/floatNumber'; +import type { PreAffect } from '../Constuructors/interfaces/PreAffect'; +import CastError from '../errors/CastError'; export type ProtectNext = Omit & { actionType: 'protect' @@ -35,7 +38,7 @@ class Protect implements PreAffect { }); } - check(...[params, status]: Parameters): ReturnType { + preAffect(...[params, status]: Parameters): SuccessArgs | void { const { initiator, target, game } = params; const attackValue = initiator.stats.val('patk') * initiator.proc; const protectValue = target.flags.isProtected.length > 0 ? target.stats.val('pdef') : 0.1; @@ -48,7 +51,7 @@ class Protect implements PreAffect { console.log('chance', chance, 'random', randomValue, 'result', result); if (!result) { - return this.getSuccessResult({ initiator, target, game }, status.value); + throw new CastError(this.getSuccessResult({ initiator, target, game }, status.effect)); } } diff --git a/src/arena/errors/CastError.ts b/src/arena/errors/CastError.ts new file mode 100644 index 00000000..8cd29f38 --- /dev/null +++ b/src/arena/errors/CastError.ts @@ -0,0 +1,7 @@ +import { BreaksMessage, SuccessArgs } from '../Constuructors/types'; + +export default class CastError extends Error { + constructor(public reason: BreaksMessage | SuccessArgs) { + super(); + } +} diff --git a/src/arena/magics/__snapshots__/blight.test.ts.snap b/src/arena/magics/__snapshots__/blight.test.ts.snap index c3d8853c..6cd9ec9d 100644 --- a/src/arena/magics/__snapshots__/blight.test.ts.snap +++ b/src/arena/magics/__snapshots__/blight.test.ts.snap @@ -1,20 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`blight should hit percentage damage 1`] = `5.95`; +exports[`blight should hit percentage damage 1`] = `6.54`; exports[`blight should hit percentage damage 2`] = ` -[ - { - "action": "Истощение", - "actionType": "dmg-magic", - "dmg": 2.05, - "dmgType": "physical", - "effect": undefined, - "exp": 24, - "hp": 5.95, - "initiator": "asperiores", - "msg": undefined, - "target": "magni", - }, -] +"*asperiores* сотворил _Истощение_ на *magni* нанеся 1.46 урона +\\[ 👊 💔-1.46/6.54 📖20 ]" `; diff --git a/src/arena/magics/__snapshots__/bodySpirit.test.ts.snap b/src/arena/magics/__snapshots__/bodySpirit.test.ts.snap index 950ecd8c..4b797dcd 100644 --- a/src/arena/magics/__snapshots__/bodySpirit.test.ts.snap +++ b/src/arena/magics/__snapshots__/bodySpirit.test.ts.snap @@ -10,20 +10,8 @@ exports[`bodySpirit should hit target and get mp 1`] = ` exports[`bodySpirit should hit target and get mp 2`] = `2.44`; exports[`bodySpirit should hit target and get mp 3`] = ` -[ - { - "action": "Дух тела", - "actionType": "dmg-magic", - "dmg": 4.88, - "dmgType": "physical", - "effect": undefined, - "exp": 47, - "hp": 3.12, - "initiator": "asperiores", - "msg": undefined, - "target": "magni", - }, -] +"*asperiores* сотворил _Дух тела_ на *magni* нанеся 4.88 урона +\\[ 👊 💔-4.88/3.12 📖47 ]" `; exports[`bodySpirit should not kill target 1`] = ` @@ -36,18 +24,20 @@ exports[`bodySpirit should not kill target 1`] = ` exports[`bodySpirit should not kill target 2`] = `0`; exports[`bodySpirit should not kill target 3`] = ` +"*asperiores* сотворил _Дух тела_ на *magni* нанеся 0 урона +\\[ 👊 💔-0/0.1 📖8 ]" +`; + +exports[`bodySpirit should take 0 damage if target hp less than 0.5 1`] = ` [ - { - "action": "Дух тела", - "actionType": "dmg-magic", - "dmg": 0, - "dmgType": "physical", - "effect": undefined, - "exp": 8, - "hp": 0.1, - "initiator": "asperiores", - "msg": undefined, - "target": "magni", - }, + 8, + 0.1, ] `; + +exports[`bodySpirit should take 0 damage if target hp less than 0.5 2`] = `0`; + +exports[`bodySpirit should take 0 damage if target hp less than 0.5 3`] = ` +"*asperiores* сотворил _Дух тела_ на *magni* нанеся 0 урона +\\[ 👊 💔-0/0.1 📖8 ]" +`; diff --git a/src/arena/magics/__snapshots__/chainLightning.test.ts.snap b/src/arena/magics/__snapshots__/chainLightning.test.ts.snap index 09540239..631d4706 100644 --- a/src/arena/magics/__snapshots__/chainLightning.test.ts.snap +++ b/src/arena/magics/__snapshots__/chainLightning.test.ts.snap @@ -4,7 +4,7 @@ exports[`chainLightning should hit 3 targets 1`] = ` [ 5.89, 5.63, - 6.1, + 6.11, 8, 8, 8, @@ -19,36 +19,10 @@ exports[`chainLightning should hit 3 targets 1`] = ` exports[`chainLightning should hit 3 targets 2`] = `63`; exports[`chainLightning should hit 3 targets 3`] = ` -[ - { - "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", - }, -] +"*architecto* сотворил _Цепь молний_ на *aspernatur* нанеся 2.36 урона +\\[ aspernatur ⚡ 💔-2.36/5.63 📖31 +architecto ⚡ 💔-2.11/5.89 📖17 +quia ⚡ 💔-1.89/6.11 📖15 ]" `; exports[`chainLightning should hit 3 targets without clan 1`] = ` @@ -61,7 +35,7 @@ exports[`chainLightning should hit 3 targets without clan 1`] = ` 8, 8, 8, - 5.63, + 5.66, 8, 8, ] @@ -70,36 +44,10 @@ exports[`chainLightning should hit 3 targets without clan 1`] = ` exports[`chainLightning should hit 3 targets without clan 2`] = `63`; exports[`chainLightning should hit 3 targets without clan 3`] = ` -[ - { - "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", - }, -] +"*qui* сотворил _Цепь молний_ на *ex* нанеся 2.34 урона +\\[ ex ⚡ 💔-2.34/5.66 📖31 +qui ⚡ 💔-2.11/5.89 📖17 +doloremque ⚡ 💔-1.87/6.13 📖15 ]" `; exports[`chainLightning should hit 4 targets 1`] = ` @@ -107,7 +55,7 @@ exports[`chainLightning should hit 4 targets 1`] = ` 4.98, 4.64, 5.31, - 5.65, + 5.62, 8, 8, 8, @@ -121,43 +69,11 @@ exports[`chainLightning should hit 4 targets 1`] = ` exports[`chainLightning should hit 4 targets 2`] = `104`; exports[`chainLightning should hit 4 targets 3`] = ` -[ - { - "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", - }, -] +"*explicabo* сотворил _Цепь молний_ на *et* нанеся 3.4 урона +\\[ et ⚡ 💔-3.4/4.64 📖39 +explicabo ⚡ 💔-3.02/4.98 📖24 +eum ⚡ 💔-2.69/5.31 📖22 +veritatis ⚡ 💔-2.38/5.62 📖19 ]" `; exports[`chainLightning should hit 5 targets 1`] = ` @@ -179,48 +95,10 @@ exports[`chainLightning should hit 5 targets 1`] = ` exports[`chainLightning should hit 5 targets 2`] = `153`; exports[`chainLightning should hit 5 targets 3`] = ` -[ - { - "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", - }, -] +"*asperiores* сотворил _Цепь молний_ на *magni* нанеся 4.38 урона +\\[ magni ⚡ 💔-4.38/3.59 📖47 +asperiores ⚡ 💔-3.94/4.06 📖32 +labore ⚡ 💔-3.53/4.47 📖28 +iure ⚡ 💔-3.1/4.9 📖25 +recusandae ⚡ 💔-2.63/5.37 📖21 ]" `; diff --git a/src/arena/magics/__snapshots__/eclipse.test.ts.snap b/src/arena/magics/__snapshots__/eclipse.test.ts.snap new file mode 100644 index 00000000..c41d1b41 --- /dev/null +++ b/src/arena/magics/__snapshots__/eclipse.test.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`eclipse initiator should be blinded by eclipse 1`] = ` +"*asperiores* использовал _Затмение_ на *asperiores* +\\[ 📖80 ] +*alias* пытался атаковать *asperiores*, но у него не получилось +_Затмение_ *asperiores*" +`; diff --git a/src/arena/magics/__snapshots__/fireBall.test.ts.snap b/src/arena/magics/__snapshots__/fireBall.test.ts.snap index 46827be4..5205d361 100644 --- a/src/arena/magics/__snapshots__/fireBall.test.ts.snap +++ b/src/arena/magics/__snapshots__/fireBall.test.ts.snap @@ -10,59 +10,21 @@ exports[`fireBall should hit 1 target 1`] = ` 8, 8, 8, - -5.44, + -5.59, 8, 8, ] `; -exports[`fireBall should hit 1 target 2`] = `119`; +exports[`fireBall should hit 1 target 2`] = `120`; exports[`fireBall should hit 1 target 3`] = ` -[ - { - "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", - }, -] +"*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 ]" `; exports[`fireBall should hit 6 targets 1`] = ` @@ -84,48 +46,10 @@ exports[`fireBall should hit 6 targets 1`] = ` exports[`fireBall should hit 6 targets 2`] = `120`; exports[`fireBall should hit 6 targets 3`] = ` -[ - { - "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", - }, -] +"*asperiores* сотворил _Огненный шар_ на *magni* нанеся 8.72 урона +\\[ magni 🔥 💔-8.72/-5.56 📖80 +magni 🔥 💔-1.21/-1.93 📖10 +magni 🔥 💔-1.21/-3.14 📖10 +magni 🔥 💔-1.21/-4.35 📖10 +magni 🔥 💔-1.21/-5.56 📖10 ]" `; diff --git a/src/arena/magics/__snapshots__/lightShield.test.ts.snap b/src/arena/magics/__snapshots__/lightShield.test.ts.snap new file mode 100644 index 00000000..0b331788 --- /dev/null +++ b/src/arena/magics/__snapshots__/lightShield.test.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lightShield initiator should be hit by light shield 1`] = ` +"*asperiores* использовал _Световой щит_ на *asperiores* +\\[ 📖6 ] +*asperiores* использовал _Световой щит_ на *asperiores* +\\[ 📖6 ] +*alias* рубанул *asperiores* _Эпическим Мечом_ и нанёс *11* урона +\\[ 💔-11/-3 📖88 ] + *asperiores* сотворил _Световой щит_ на *alias* нанеся 3 урона + \\[ 💔-3/5 📖0 ] + *asperiores* сотворил _Световой щит_ на *alias* нанеся 3 урона + \\[ 💔-3/2 📖0 ]" +`; diff --git a/src/arena/magics/__snapshots__/paralysis.test.ts.snap b/src/arena/magics/__snapshots__/paralysis.test.ts.snap new file mode 100644 index 00000000..97aa609f --- /dev/null +++ b/src/arena/magics/__snapshots__/paralysis.test.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`paralysis initiator should be blinded by eclipse 1`] = ` +"*asperiores* использовал _Паралич_ на *asperiores* +\\[ 📖80 ] +*alias* пытался атаковать *asperiores*, но у него не получилось +_Паралич_ *asperiores*" +`; diff --git a/src/arena/magics/__snapshots__/physicalSadness.test.ts.snap b/src/arena/magics/__snapshots__/physicalSadness.test.ts.snap index 81f89c1f..6556159e 100644 --- a/src/arena/magics/__snapshots__/physicalSadness.test.ts.snap +++ b/src/arena/magics/__snapshots__/physicalSadness.test.ts.snap @@ -1,108 +1,29 @@ // 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 1`] = `-4.92`; -exports[`physicalSadness should hit target with single hit 2`] = `97`; +exports[`physicalSadness should hit target with single hit 2`] = `111`; 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", - }, -] +"*magni* рубанул *asperiores* _Эпическим Мечом_ и нанёс *10.9* урона +\\[ 💔-10.9/-2.9 📖87 ] +*asperiores* сотворил _Вселенская скорбь_ на *magni* нанеся 12.92 урона +\\[ 👊 💔-12.92/-4.92 📖111 ]" `; -exports[`physicalSadness should hit targets with multiple hit 1`] = `5.22`; +exports[`physicalSadness should hit targets with multiple hit 1`] = `3.25`; -exports[`physicalSadness should hit targets with multiple hit 2`] = `30`; +exports[`physicalSadness should hit targets with multiple hit 2`] = `46`; 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", - }, -] +"*magni* рубанул *asperiores* _Эпическим Мечом_ и нанёс *2.73* урона +\\[ 💔-2.73/5.27 📖22 ] +*magni* рубанул *asperiores* _Эпическим Мечом_ и нанёс *2.73* урона +\\[ 💔-2.73/2.54 📖22 ] +*magni* рубанул *asperiores* _Эпическим Мечом_ и нанёс *2.73* урона +\\[ 💔-2.73/-0.19 📖22 ] +*magni* рубанул *asperiores* _Эпическим Мечом_ и нанёс *2.73* урона +\\[ 💔-2.73/-2.92 📖22 ] +*asperiores* сотворил _Вселенская скорбь_ на *magni* нанеся 4.75 урона +\\[ 👊 💔-4.75/3.25 📖46 ]" `; diff --git a/src/arena/magics/blight.ts b/src/arena/magics/blight.ts index f077a0ad..e853f539 100644 --- a/src/arena/magics/blight.ts +++ b/src/arena/magics/blight.ts @@ -30,7 +30,7 @@ class Blight extends LongDmgMagic { const effectVal = this.effectVal(); const hit = hp * (effectVal / 100); - this.status.hit = hit; + this.status.effect = hit; target.stats.down('hp', hit); } @@ -40,7 +40,7 @@ class Blight extends LongDmgMagic { const effectVal = this.effectVal(); const hit = hp * (effectVal / 100); - this.status.hit = hit; + this.status.effect = hit; target.stats.down('hp', hit); } } diff --git a/src/arena/magics/bodySpirit.ts b/src/arena/magics/bodySpirit.ts index ff51664d..ba75de37 100644 --- a/src/arena/magics/bodySpirit.ts +++ b/src/arena/magics/bodySpirit.ts @@ -30,7 +30,7 @@ class Blessing extends DmgMagic { const maxDamage = Math.max(targetHp - minimumTargetHeath, 0); const effectVal = Math.min(this.effectVal(), maxDamage); - this.status.hit = effectVal; + this.status.effect = effectVal; target.stats.down('hp', effectVal); initiator.stats.up('mp', effectVal * damageToManaMultiplier); diff --git a/src/arena/magics/eclipse.test.ts b/src/arena/magics/eclipse.test.ts new file mode 100644 index 00000000..3357a211 --- /dev/null +++ b/src/arena/magics/eclipse.test.ts @@ -0,0 +1,48 @@ +import casual from 'casual'; +import CharacterService from '@/arena/CharacterService'; +import GameService from '@/arena/GameService'; +import TestUtils from '@/utils/testUtils'; +import attack from '../actions/attack'; +import eclipse from './eclipse'; + +// npm t src/arena/magics/eclipse.test.ts + +describe('eclipse', () => { + let game: GameService; + + beforeAll(() => { + casual.seed(1); + + attack.registerPreAffects([eclipse]); + }); + + beforeEach(async () => { + const initiator = await TestUtils.createCharacter({ prof: 'm', magics: { eclipse: 1 } }); + const target = await TestUtils.createCharacter({ prof: 'w' }, { withWeapon: true }); + + await Promise.all([initiator.id, target.id].map(CharacterService.getCharacterById)); + + game = new GameService([initiator.id, target.id]); + }); + + beforeEach(() => { + jest.spyOn(global.Math, 'random').mockReturnValue(0.1); + }); + + afterEach(() => { + jest.spyOn(global.Math, 'random').mockRestore(); + }); + + it('initiator should be blinded by eclipse', async () => { + game.players.players[0].proc = 1; + game.players.players[0].stats.set('maxMp', 99); + game.players.players[0].stats.set('mp', 99); + game.players.players[1].proc = 1; + + eclipse.cast(game.players.players[0], game.players.players[0], game); + + attack.cast(game.players.players[1], game.players.players[0], game); + + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); + }); +}); diff --git a/src/arena/magics/eclipse.ts b/src/arena/magics/eclipse.ts index e60c815a..405f919d 100644 --- a/src/arena/magics/eclipse.ts +++ b/src/arena/magics/eclipse.ts @@ -1,10 +1,12 @@ import { CommonMagic } from '../Constuructors/CommonMagicConstructor'; +import type { PreAffect } from '../Constuructors/interfaces/PreAffect'; +import CastError from '../errors/CastError'; /** * Затмение * Основное описание магии общее требовани есть в конструкторе */ -class Eclipse extends CommonMagic { +class Eclipse extends CommonMagic implements PreAffect { constructor() { super({ name: 'eclipse', @@ -28,6 +30,12 @@ class Eclipse extends CommonMagic { // выставляем глобальный флаг затмения game.flags.global.isEclipsed = true; } + + preAffect({ initiator, target, game } = this.params) { + if (game.flags.global.isEclipsed) { + throw new CastError(this.getSuccessResult({ initiator: target, target: initiator, game })); + } + } } export default new Eclipse(); diff --git a/src/arena/magics/index.ts b/src/arena/magics/index.ts index 05ad0053..86506a83 100644 --- a/src/arena/magics/index.ts +++ b/src/arena/magics/index.ts @@ -28,3 +28,4 @@ export { default as physicalSadness } from './physicalSadness'; export { default as bodySpirit } from './bodySpirit'; export { default as blight } from './blight'; export { default as dustShield } from './dustShield'; +export { default as lightShield } from './lightShield'; diff --git a/src/arena/magics/lightShield.test.ts b/src/arena/magics/lightShield.test.ts new file mode 100644 index 00000000..dd885a8b --- /dev/null +++ b/src/arena/magics/lightShield.test.ts @@ -0,0 +1,46 @@ +import casual from 'casual'; +import CharacterService from '@/arena/CharacterService'; +import GameService from '@/arena/GameService'; +import TestUtils from '@/utils/testUtils'; +import attack from '../actions/attack'; +import lightShield from './lightShield'; + +// npm t src/arena/magics/lightShield.test.ts + +describe('lightShield', () => { + let game: GameService; + + beforeAll(() => { + casual.seed(1); + + attack.registerPostAffects([lightShield]); + }); + + beforeEach(async () => { + const initiator = await TestUtils.createCharacter({ prof: 'm', magics: { lightShield: 2 } }); + const target = await TestUtils.createCharacter({ prof: 'w' }, { withWeapon: true }); + + await Promise.all([initiator.id, target.id].map(CharacterService.getCharacterById)); + + game = new GameService([initiator.id, target.id]); + }); + + beforeEach(() => { + jest.spyOn(global.Math, 'random').mockReturnValue(0.15); + }); + + afterEach(() => { + jest.spyOn(global.Math, 'random').mockRestore(); + }); + + it('initiator should be hit by light shield', async () => { + game.players.players[0].proc = 1; + game.players.players[1].proc = 1; + + lightShield.cast(game.players.players[0], game.players.players[0], game); + lightShield.cast(game.players.players[0], game.players.players[0], game); + attack.cast(game.players.players[1], game.players.players[0], game); + + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); + }); +}); diff --git a/src/arena/magics/lightShield.ts b/src/arena/magics/lightShield.ts new file mode 100644 index 00000000..b74927b4 --- /dev/null +++ b/src/arena/magics/lightShield.ts @@ -0,0 +1,67 @@ +import type { PostAffect } from '../Constuructors/interfaces/PostAffect'; +import { LongDmgMagic } from '../Constuructors/LongDmgMagicConstructor'; +import type { MagicNext } from '../Constuructors/MagicConstructor'; +import type { SuccessArgs } from '../Constuructors/types'; +/** + * Магический доспех + * Основное описание магии общее требование есть в конструкторе + */ +class LightShield extends LongDmgMagic implements PostAffect { + constructor() { + super({ + name: 'lightShield', + displayName: 'Световой щит', + desc: 'Создает магический доспех на маге', + cost: 3, + baseExp: 6, + costType: 'mp', + lvl: 1, + orderType: 'team', + aoeType: 'target', + magType: 'good', + chance: [100, 100, 100], + effect: ['1d1', '3d3', '5d5'], + profList: ['m'], + dmgType: 'clear', + }); + } + + run() { + const { target, initiator } = this.params; + target.flags.isLightShielded.push({ initiator: initiator.nick, val: initiator.proc }); + } + + runLong(): void { + const { target, initiator } = this.params; + target.flags.isLightShielded.push({ initiator: initiator.nick, val: initiator.proc }); + } + + postAffect( + { initiator, target, game } = this.params, + ): void | SuccessArgs | SuccessArgs[] { + return target.flags.isLightShielded.map(() => { + const effect = this.effectVal({ initiator: target, target: initiator, game }); + + initiator.stats.down('hp', effect); + + return super.getSuccessResult({ initiator: target, target: initiator, game }); + }); + } + + getSuccessResult({ initiator, target } = this.params): SuccessArgs { + const result: MagicNext = { + exp: this.status.exp, + action: this.displayName, + actionType: 'magic', + target: target.nick, + initiator: initiator.nick, + msg: this.customMessage?.bind(this), + }; + + this.reset(); + + return result; + } +} + +export default new LightShield(); diff --git a/src/arena/magics/paralysis.test.ts b/src/arena/magics/paralysis.test.ts new file mode 100644 index 00000000..e4d47a7a --- /dev/null +++ b/src/arena/magics/paralysis.test.ts @@ -0,0 +1,48 @@ +import casual from 'casual'; +import CharacterService from '@/arena/CharacterService'; +import GameService from '@/arena/GameService'; +import TestUtils from '@/utils/testUtils'; +import attack from '../actions/attack'; +import paralysis from './paralysis'; + +// npm t src/arena/magics/paralysis.test.ts + +describe('paralysis', () => { + let game: GameService; + + beforeAll(() => { + casual.seed(1); + + attack.registerPreAffects([paralysis]); + }); + + beforeEach(async () => { + const initiator = await TestUtils.createCharacter({ prof: 'm', magics: { paralysis: 1 } }); + const target = await TestUtils.createCharacter({ prof: 'w' }, { withWeapon: true }); + + await Promise.all([initiator.id, target.id].map(CharacterService.getCharacterById)); + + game = new GameService([initiator.id, target.id]); + }); + + beforeEach(() => { + jest.spyOn(global.Math, 'random').mockReturnValue(0.1); + }); + + afterEach(() => { + jest.spyOn(global.Math, 'random').mockRestore(); + }); + + it('initiator should be blinded by eclipse', async () => { + game.players.players[0].proc = 1; + game.players.players[0].stats.set('maxMp', 99); + game.players.players[0].stats.set('mp', 99); + game.players.players[1].proc = 1; + + paralysis.cast(game.players.players[0], game.players.players[0], game); + + attack.cast(game.players.players[1], game.players.players[0], game); + + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); + }); +}); diff --git a/src/arena/magics/paralysis.ts b/src/arena/magics/paralysis.ts index 9cc6a460..066025f6 100644 --- a/src/arena/magics/paralysis.ts +++ b/src/arena/magics/paralysis.ts @@ -1,10 +1,12 @@ import { CommonMagic } from '../Constuructors/CommonMagicConstructor'; +import { PreAffect } from '../Constuructors/interfaces/PreAffect'; +import CastError from '../errors/CastError'; /** * Паралич * Основное описание магии общее требовани есть в конструкторе */ -class Paralysis extends CommonMagic { +class Paralysis extends CommonMagic implements PreAffect { constructor() { super({ name: 'paralysis', @@ -27,6 +29,12 @@ class Paralysis extends CommonMagic { const { target } = this.params; target.flags.isParalysed = true; } + + preAffect({ initiator, target, game } = this.params) { + if (target.flags.isParalysed) { + throw new CastError(this.getSuccessResult({ initiator: target, target: initiator, game })); + } + } } export default new Paralysis(); diff --git a/src/arena/magics/physicalSadness.ts b/src/arena/magics/physicalSadness.ts index 9747f9b6..571456db 100644 --- a/src/arena/magics/physicalSadness.ts +++ b/src/arena/magics/physicalSadness.ts @@ -1,14 +1,5 @@ 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.dmgType === 'physical'; - } - - return false; -}; +import { isPhysicalDamageResult } from '../Constuructors/utils'; class PhysicalSadness extends DmgMagic { constructor() { @@ -33,7 +24,7 @@ class PhysicalSadness extends DmgMagic { run(): void { const { target, game } = this.params; const results = game.getRoundResults(); - const physicalDamageResults = results.filter(isPhysicalDamage); + const physicalDamageResults = results.filter(isPhysicalDamageResult); if (!physicalDamageResults.length) { return; @@ -44,7 +35,7 @@ class PhysicalSadness extends DmgMagic { const totalHit = physicalDamageResults.reduce((sum, result) => sum + result.dmg, 0); const hit = effect + (totalHit / physicalDamageResults.length + 1); - this.status.hit = hit; + this.status.effect = hit; target.stats.down('hp', hit); } diff --git a/src/arena/magics/silence.ts b/src/arena/magics/silence.ts index 80525065..5e6b4ce7 100644 --- a/src/arena/magics/silence.ts +++ b/src/arena/magics/silence.ts @@ -1,10 +1,12 @@ import { CommonMagic } from '../Constuructors/CommonMagicConstructor'; +import type { PreAffect } from '../Constuructors/interfaces/PreAffect'; +import CastError from '../errors/CastError'; /** * Безмолвие * Основное описание магии общее требовани есть в конструкторе */ -class Silence extends CommonMagic { +class Silence extends CommonMagic implements PreAffect { constructor() { super({ name: 'silence', @@ -30,6 +32,14 @@ class Silence extends CommonMagic { val: 0, }); } + + preAffect({ initiator, target, game } = this.params): void { + const { isSilenced } = initiator.flags; + if (isSilenced.some((e) => e.initiator !== this.name)) { + // если кастер находится под безмолвием/бунтом богов + throw new CastError(this.getSuccessResult({ initiator: target, target: initiator, game })); + } + } } export default new Silence(); diff --git a/src/arena/magics/vampirism.ts b/src/arena/magics/vampirism.ts index a2ff7866..ff64691c 100644 --- a/src/arena/magics/vampirism.ts +++ b/src/arena/magics/vampirism.ts @@ -31,7 +31,7 @@ class Vampirism extends DmgMagic { run(): void { const { target, initiator } = this.params; target.stats.mode('down', 'hp', this.effectVal()); - initiator.stats.mode('up', 'hp', this.status.hit); + initiator.stats.mode('up', 'hp', this.status.effect); } } diff --git a/src/arena/skills/__snapshots__/disarm.test.ts.snap b/src/arena/skills/__snapshots__/disarm.test.ts.snap index 86fa20a9..44c738a4 100644 --- a/src/arena/skills/__snapshots__/disarm.test.ts.snap +++ b/src/arena/skills/__snapshots__/disarm.test.ts.snap @@ -1,57 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`disarm target should be disarmed if initiator has more dex 1`] = ` -[ - { - "action": "🥊 Обезоруживание", - "actionType": "skill", - "exp": 20, - "initiator": "alias", - "msg": undefined, - "target": "asperiores", - }, - { - "action": "attack", - "actionType": "phys", - "cause": { - "action": "🥊 Обезоруживание", - "actionType": "skill", - "exp": 0, - "initiator": "alias", - "msg": undefined, - "target": "asperiores", - }, - "initiator": "asperiores", - "message": undefined, - "target": "alias", - "weapon": { - "code": "a100", - }, - }, -] +"*alias* использовал _🥊 Обезоруживание_ на *asperiores* +\\[ 📖20 ] +*asperiores* пытался атаковать *alias*, но у него не получилось +_🥊 Обезоруживание_ *alias*: 📖0" `; exports[`disarm target should not be disarmed if initiator has less dex 1`] = ` -[ - { - "action": "🥊 Обезоруживание", - "actionType": "skill", - "initiator": "explicabo", - "message": "SKILL_FAIL", - "target": "repellat", - }, - { - "action": "attack", - "actionType": "phys", - "dmg": 11, - "dmgType": "physical", - "exp": 88, - "hp": -3, - "initiator": "repellat", - "target": "explicabo", - "weapon": { - "code": "a100", - }, - }, -] +"*explicabo* пытался использовать умение _🥊 Обезоруживание_, но у него не вышло +*repellat* рубанул *explicabo* _Эпическим Мечом_ и нанёс *11* урона +\\[ 💔-11/-3 📖88 ]" `; diff --git a/src/arena/skills/__snapshots__/dodge.test.ts.snap b/src/arena/skills/__snapshots__/dodge.test.ts.snap index 32ac8cd9..ab30645c 100644 --- a/src/arena/skills/__snapshots__/dodge.test.ts.snap +++ b/src/arena/skills/__snapshots__/dodge.test.ts.snap @@ -1,58 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`dodge target should dodge attack if has initiator more dex 1`] = ` -[ - { - "action": "🐍 Увертка", - "actionType": "skill", - "exp": 0, - "initiator": "alias", - "msg": [Function], - "target": "alias", - }, - { - "action": "attack", - "actionType": "phys", - "cause": { - "action": "🐍 Увертка", - "actionType": "skill", - "exp": 50, - "initiator": "alias", - "msg": [Function], - "target": "asperiores", - }, - "initiator": "asperiores", - "message": undefined, - "target": "alias", - "weapon": { - "code": "a100", - }, - }, -] +"*alias* использовал _🐍 Увертка_ + +*asperiores* пытался атаковать *alias*, но у него не получилось +_🐍 Увертка_ *alias*: 📖50" `; exports[`dodge target should not dodge attack if target has more dex 1`] = ` -[ - { - "action": "🐍 Увертка", - "actionType": "skill", - "exp": 0, - "initiator": "explicabo", - "msg": [Function], - "target": "explicabo", - }, - { - "action": "attack", - "actionType": "phys", - "dmg": 11, - "dmgType": "physical", - "exp": 88, - "hp": -3, - "initiator": "repellat", - "target": "explicabo", - "weapon": { - "code": "a100", - }, - }, -] +"*explicabo* использовал _🐍 Увертка_ + +*repellat* рубанул *explicabo* _Эпическим Мечом_ и нанёс *11* урона +\\[ 💔-11/-3 📖88 ]" `; diff --git a/src/arena/skills/__snapshots__/parry.test.ts.snap b/src/arena/skills/__snapshots__/parry.test.ts.snap index 100bafaf..e68b145c 100644 --- a/src/arena/skills/__snapshots__/parry.test.ts.snap +++ b/src/arena/skills/__snapshots__/parry.test.ts.snap @@ -1,58 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`parry target should not parry attack if target has more dex 1`] = ` -[ - { - "action": "🤺 Парирование", - "actionType": "skill", - "exp": 0, - "initiator": "explicabo", - "msg": [Function], - "target": "explicabo", - }, - { - "action": "attack", - "actionType": "phys", - "dmg": 11, - "dmgType": "physical", - "exp": 88, - "hp": -3, - "initiator": "repellat", - "target": "explicabo", - "weapon": { - "code": "a100", - }, - }, -] +"*explicabo* использовал _🤺 Парирование_ + +*repellat* рубанул *explicabo* _Эпическим Мечом_ и нанёс *11* урона +\\[ 💔-11/-3 📖88 ]" `; exports[`parry target should parry attack if initiator has more dex 1`] = ` -[ - { - "action": "🤺 Парирование", - "actionType": "skill", - "exp": 0, - "initiator": "alias", - "msg": [Function], - "target": "alias", - }, - { - "action": "attack", - "actionType": "phys", - "cause": { - "action": "🤺 Парирование", - "actionType": "skill", - "exp": 8, - "initiator": "alias", - "msg": [Function], - "target": "asperiores", - }, - "initiator": "asperiores", - "message": undefined, - "target": "alias", - "weapon": { - "code": "a100", - }, - }, -] +"*alias* использовал _🤺 Парирование_ + +*asperiores* пытался атаковать *alias*, но у него не получилось +_🤺 Парирование_ *alias*: 📖8" `; diff --git a/src/arena/skills/disarm.test.ts b/src/arena/skills/disarm.test.ts index b7003f52..117d50be 100644 --- a/src/arena/skills/disarm.test.ts +++ b/src/arena/skills/disarm.test.ts @@ -12,6 +12,7 @@ describe('disarm', () => { beforeAll(() => { casual.seed(1); + attack.registerPreAffects([disarm]); }); beforeEach(async () => { diff --git a/src/arena/skills/disarm.ts b/src/arena/skills/disarm.ts index 20b54eb5..143e2eef 100644 --- a/src/arena/skills/disarm.ts +++ b/src/arena/skills/disarm.ts @@ -1,8 +1,9 @@ -import { PreAffect } from '../Constuructors/PreAffect'; +import type { PreAffect } from '../Constuructors/interfaces/PreAffect'; import { Skill } from '../Constuructors/SkillConstructor'; +import CastError from '../errors/CastError'; /** - * Обезаруживание + * Обезоруживание */ class Disarm extends Skill implements PreAffect { constructor() { @@ -35,13 +36,13 @@ class Disarm extends Skill implements PreAffect { this.getExp(target); } else { - throw this.getFailResult('SKILL_FAIL'); + throw new CastError('SKILL_FAIL'); } } - check({ initiator, target, game } = this.params) { + preAffect({ initiator, target, game } = this.params) { if (initiator.flags.isDisarmed) { - return this.getSuccessResult({ initiator: target, target: initiator, game }); + throw new CastError(this.getSuccessResult({ initiator: target, target: initiator, game })); } } } diff --git a/src/arena/skills/dodge.test.ts b/src/arena/skills/dodge.test.ts index ffc7cbbc..3e6b6700 100644 --- a/src/arena/skills/dodge.test.ts +++ b/src/arena/skills/dodge.test.ts @@ -12,6 +12,7 @@ describe('dodge', () => { beforeAll(() => { casual.seed(1); + attack.registerPreAffects([dodge]); }); beforeEach(async () => { diff --git a/src/arena/skills/dodge.ts b/src/arena/skills/dodge.ts index 4f6c96a5..4288e249 100644 --- a/src/arena/skills/dodge.ts +++ b/src/arena/skills/dodge.ts @@ -1,7 +1,8 @@ import { bold, italic } from '@/utils/formatString'; -import type { PreAffect } from '../Constuructors/PreAffect'; +import type { PreAffect } from '../Constuructors/interfaces/PreAffect'; import { Skill } from '../Constuructors/SkillConstructor'; -import { SuccessArgs } from '../Constuructors/types'; +import type { SuccessArgs } from '../Constuructors/types'; +import CastError from '../errors/CastError'; import MiscService from '../MiscService'; const dodgeableWeaponTypes = [ @@ -40,7 +41,7 @@ class Dodge extends Skill implements PreAffect { initiator.flags.isDodging = this.effect[initiatorSkillLvl - 1] * initiator.stats.val('dex'); } - check({ initiator, target, game } = this.params) { + preAffect({ initiator, target, game } = this.params) { const isDodgeable = initiator.weapon.isOfType(dodgeableWeaponTypes); if (target.flags.isDodging && isDodgeable) { @@ -51,7 +52,7 @@ class Dodge extends Skill implements PreAffect { if (chance > MiscService.rndm('1d100')) { this.getExp(target); - return this.getSuccessResult({ initiator: target, target: initiator, game }); + throw new CastError(this.getSuccessResult({ initiator: target, target: initiator, game })); } } } diff --git a/src/arena/skills/parry.test.ts b/src/arena/skills/parry.test.ts index e40a9314..492e4ffa 100644 --- a/src/arena/skills/parry.test.ts +++ b/src/arena/skills/parry.test.ts @@ -11,6 +11,7 @@ describe('parry', () => { let game: GameService; beforeAll(() => { + attack.registerPreAffects([parry]); casual.seed(1); }); diff --git a/src/arena/skills/parry.ts b/src/arena/skills/parry.ts index fae1c9fc..6debc9de 100644 --- a/src/arena/skills/parry.ts +++ b/src/arena/skills/parry.ts @@ -1,7 +1,8 @@ import { bold, italic } from '@/utils/formatString'; -import { PreAffect } from '../Constuructors/PreAffect'; +import type { PreAffect } from '../Constuructors/interfaces/PreAffect'; import { Skill } from '../Constuructors/SkillConstructor'; -import { SuccessArgs } from '../Constuructors/types'; +import type { SuccessArgs } from '../Constuructors/types'; +import CastError from '../errors/CastError'; const parryableWeaponTypes = [ 's', // колющее @@ -39,7 +40,7 @@ class Parry extends Skill implements PreAffect { initiator.flags.isParry = initiator.stats.val('dex') * effect; } - check({ initiator, target, game } = this.params) { + preAffect({ initiator, target, game } = this.params) { const isParryable = initiator.weapon.isOfType(parryableWeaponTypes); if (target.flags.isParry && isParryable) { @@ -48,7 +49,7 @@ class Parry extends Skill implements PreAffect { if ((target.flags.isParry - initiatorDex) > 0) { this.getExp(target); - return this.getSuccessResult({ initiator: target, target: initiator, game }); + throw new CastError(this.getSuccessResult({ initiator: target, target: initiator, game })); } target.flags.isParry -= initiatorDex; diff --git a/src/utils/registerAffects.ts b/src/utils/registerAffects.ts index c65c8b2f..0148f547 100644 --- a/src/utils/registerAffects.ts +++ b/src/utils/registerAffects.ts @@ -1,14 +1,34 @@ import arena from '@/arena'; const registerAttackAffects = () => { - arena.actions.attack.preAffects = [ + arena.actions.attack.registerPreAffects([ arena.actions.protect, arena.skills.dodge, arena.skills.parry, arena.skills.disarm, - ]; + arena.magics.paralysis, + arena.magics.eclipse, + ]); + + arena.actions.attack.registerPostAffects([ + arena.magics.lightShield, + ]); +}; + +const registerMagicAffects = () => { + Object.values(arena.magics).forEach((magic) => { + magic.registerPreAffects([ + arena.magics.silence, + ]); + }); +}; + +const registerHealAffects = () => { + arena.actions.handsHeal.registerPreAffects([arena.actions.attack]); }; export const registerAffects = () => { registerAttackAffects(); + registerMagicAffects(); + registerHealAffects(); }; diff --git a/src/utils/testUtils.ts b/src/utils/testUtils.ts index 4126370a..d85ce5d1 100644 --- a/src/utils/testUtils.ts +++ b/src/utils/testUtils.ts @@ -3,6 +3,7 @@ import type { AnyKeys } from 'mongoose'; import CharacterService from '@/arena/CharacterService'; import { ClanService } from '@/arena/ClanService'; import type { HistoryItem } from '@/arena/HistoryService'; +import { formatMessage } from '@/arena/LogService/utils'; import { profsList, profsData, type Prof } from '@/data/profs'; import { type Char, CharModel } from '@/models/character'; import { type Clan, ClanModel } from '@/models/clan'; @@ -75,18 +76,7 @@ export default class TestUtils { 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; - }); + return formatMessage(item); + }).join('\n'); } }