diff --git a/src/arena/BattleService.ts b/src/arena/BattleService.ts index 0aba453..5ed1c62 100644 --- a/src/arena/BattleService.ts +++ b/src/arena/BattleService.ts @@ -58,6 +58,9 @@ function getTargetKeyboard(charId: string, game: Game, action: string) { if (orderType === 'team') { return game.isPlayersAlly(player, target); } + if (orderType === 'teamExceptSelf') { + return game.isPlayersAlly(player, target) && player.id !== target.id; + } return !orders.some((order) => target.id === order.target && action === order.action); }) diff --git a/src/arena/Constuructors/FlagsConstructor.ts b/src/arena/Constuructors/FlagsConstructor.ts index ef6971b..ef8722c 100644 --- a/src/arena/Constuructors/FlagsConstructor.ts +++ b/src/arena/Constuructors/FlagsConstructor.ts @@ -21,6 +21,7 @@ export default class FlagsConstructor { isDisarmed = false; isSleeping = false; isLightShielded: Flag[] = []; + isBehindWall?: Flag; /** * Обнуление флагов @@ -39,5 +40,6 @@ export default class FlagsConstructor { this.isDisarmed = false; this.isShielded = 0; this.isLightShielded = []; + this.isBehindWall = undefined; } } diff --git a/src/arena/Constuructors/types.ts b/src/arena/Constuructors/types.ts index 82d5467..e048843 100644 --- a/src/arena/Constuructors/types.ts +++ b/src/arena/Constuructors/types.ts @@ -4,7 +4,7 @@ import type GameService from '../GameService'; import type { Player } from '../PlayersService'; export type CostType = 'en' | 'mp'; -export type OrderType = 'all' | 'any' | 'enemy' | 'self' | 'team'; +export type OrderType = 'all' | 'any' | 'enemy' | 'self' | 'team' | 'teamExceptSelf'; export type AOEType = 'target' | 'team'; export type DamageType = 'acid' | 'fire' | 'lighting' | 'frost' | 'physical' | 'clear'; export type BreaksMessage = diff --git a/src/arena/LogService/utils/format-action.ts b/src/arena/LogService/utils/format-action.ts index c8392ea..d2453ea 100644 --- a/src/arena/LogService/utils/format-action.ts +++ b/src/arena/LogService/utils/format-action.ts @@ -17,6 +17,7 @@ export function formatAction(msgObj: SuccessArgs): string { case 'aoe-dmg-magic': return `*${msgObj.initiator}* сотворил _${msgObj.action}_ на *${msgObj.target}* нанеся ${msgObj.effect} урона`; case 'magic': + case 'magic-long': return !msgObj.effect ? `*${msgObj.initiator}* использовал _${msgObj.action}_ на *${msgObj.target}*` : `*${msgObj.initiator}* использовал _${msgObj.action}_ на *${msgObj.target}* с эффектом ${msgObj.effect}`; diff --git a/src/arena/config.ts b/src/arena/config.ts index a269222..047fc3c 100644 --- a/src/arena/config.ts +++ b/src/arena/config.ts @@ -98,7 +98,7 @@ export default { 'magicArmor', 'strongAura', 'dustShield', 'mediumAura', 'stoneSkin', - 'magic_wall'], // ??? + 'magicWall'], // ??? 'cats_claw', 'vampiric_aura', 'glitch', diff --git a/src/arena/magics/__snapshots__/magicWall.test.ts.snap b/src/arena/magics/__snapshots__/magicWall.test.ts.snap new file mode 100644 index 0000000..5395293 --- /dev/null +++ b/src/arena/magics/__snapshots__/magicWall.test.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`magicWall target should be behind wall 1`] = `147.01`; + +exports[`magicWall target should be behind wall 2`] = ` +"*asperiores* использовал _Магическая стена_ на *alias* с эффектом 140.21 +\\[ 📖14 ] +*alias* пытался атаковать *asperiores*, но у него не получилось +_Магическая стена_ *asperiores*" +`; diff --git a/src/arena/magics/index.ts b/src/arena/magics/index.ts index 07df1e0..31d56e3 100644 --- a/src/arena/magics/index.ts +++ b/src/arena/magics/index.ts @@ -31,3 +31,4 @@ export { default as blight } from './blight'; export { default as dustShield } from './dustShield'; export { default as lightShield } from './lightShield'; export { default as sleep } from './sleep'; +export { default as magicWall } from './magicWall'; diff --git a/src/arena/magics/magicWall.test.ts b/src/arena/magics/magicWall.test.ts new file mode 100644 index 0000000..bbeccfa --- /dev/null +++ b/src/arena/magics/magicWall.test.ts @@ -0,0 +1,47 @@ +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 magicWall from './magicWall'; + +// npm t src/arena/magics/magicWall.test.ts + +describe('magicWall', () => { + let game: GameService; + + beforeAll(() => { + casual.seed(1); + + attack.registerPreAffects([magicWall]); + }); + + beforeEach(async () => { + const initiator = await TestUtils.createCharacter({ prof: 'm', magics: { magicWall: 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('target should be behind wall', async () => { + game.players.players[0].proc = 1; + game.players.players[0].stats.set('mp', magicWall.cost); + game.players.players[1].proc = 1; + + magicWall.cast(game.players.players[0], game.players.players[1], game); + attack.cast(game.players.players[1], game.players.players[0], game); + + expect(game.players.players[1].stats.val('pdef')).toMatchSnapshot(); + expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot(); + }); +}); diff --git a/src/arena/magics/magicWall.ts b/src/arena/magics/magicWall.ts new file mode 100644 index 0000000..9f0ed7a --- /dev/null +++ b/src/arena/magics/magicWall.ts @@ -0,0 +1,55 @@ +import type { PreAffect } from '@/arena/Constuructors/interfaces/PreAffect'; +import CastError from '@/arena/errors/CastError'; +import { LongMagic } from '../Constuructors/LongMagicConstructor'; + +/** + * Магическая стена + * Основное описание магии общее требовани есть в конструкторе + */ +class MagicWall extends LongMagic implements PreAffect { + constructor() { + super({ + name: 'magicWall', + displayName: 'Магическая стена', + desc: 'Защищает цель, цель не может атаковать, нельзя использовать на себя', + cost: 30, + baseExp: 0.1, + costType: 'mp', + lvl: 4, + orderType: 'teamExceptSelf', + aoeType: 'target', + magType: 'good', + chance: [100, 100, 100], + profList: ['p'], + effect: ['1d25+125', '1d50+150', '1d100+200'], + }); + } + + calculateExp(effect: number, baseExp = 0) { + return Math.round(effect * baseExp * this.params.initiator.proc); + } + + run() { + const { target, initiator } = this.params; + target.stats.up('pdef', this.effectVal()); + target.flags.isBehindWall = { initiator: initiator.id, val: this.status.effect }; + } + + runLong() { + const { target, initiator } = this.params; + target.stats.up('pdef', this.effectVal()); + target.flags.isBehindWall = { initiator: initiator.id, val: this.status.effect }; + } + + preAffect({ initiator, game } = this.params) { + if (initiator.flags.isBehindWall) { + const wallCaster = game.players.getById(initiator.flags.isBehindWall.initiator); + if (wallCaster) { + // eslint-disable-next-line max-len + throw new CastError(this.getSuccessResult({ initiator: wallCaster, target: initiator, game })); + } + } + } +} + +export default new MagicWall(); diff --git a/src/utils/registerAffects.ts b/src/utils/registerAffects.ts index 6e6e5ab..9dc172d 100644 --- a/src/utils/registerAffects.ts +++ b/src/utils/registerAffects.ts @@ -9,6 +9,7 @@ const registerAttackAffects = () => { arena.magics.paralysis, arena.magics.eclipse, arena.magics.sleep, + arena.magics.magicWall, ]); arena.actions.attack.registerPostAffects([