Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#278 Skill fail #300

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 20 additions & 29 deletions src/arena/Constuructors/PhysConstructor.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const { floatNumber } = require('../../utils/floatNumber');
const { default: arena } = require('../index');
const MiscService = require('../MiscService');
const { dodge, parry, disarm } = require('../skills');
const { isFailResult } = require('./utils');

/**
* @typedef {import ('../GameService').default} game
Expand All @@ -13,7 +15,10 @@ const MiscService = require('../MiscService');
* @todo Сейчас при отсутствие защиты на цели, не учитывается статик протект(
* ???) Т.е если цель не защищается атака по ней на 95% удачна
* */
class PhysConstructor {
class PhysConstructor { /**
* @type {import('./PreAffect').PreAffect[]}
* */
preAffects;
/**
* Конструктор атаки
* @param {atkAct} atkAct имя actions
Expand All @@ -23,14 +28,20 @@ class PhysConstructor {
* @property {String} desc
* @property {Number} lvl
* @property {String} orderType
*
* @param {import('./PreAffect').PreAffect[]} preAffects
*/
constructor(atkAct) {
constructor(atkAct, preAffects = [dodge, parry, disarm]) {
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 = preAffects;
}

/**
Expand Down Expand Up @@ -63,34 +74,15 @@ class PhysConstructor {
* Проверка флагов влияющих на физический урон
*/
checkPreAffects() {
const { initiator, target, game } = this.params;
const iDex = initiator.stats.val('dex');
// Глобальная проверка не весит ли затмение на арене
if (game.flags.global.isEclipsed) throw this.breaks('ECLIPSE');
const weapon = arena.items[initiator.weapon.code];
const hasDodgeableItems = MiscService.weaponTypes[weapon.wtype].dodge;
// Проверяем увёртку
if (target.flags.isDodging && hasDodgeableItems) {
/** @todo возможно следует состряпать static функцию tryDodge внутри скила
* уворота которая будет выполнять весь расчет а возвращать только bool
* значение. Сейчас эти проверки сильно раздувают PhysConstructor
*/
// проверяем имеет ли цель достаточно dex для того что бы уклониться
if (this.game.flags.global.isEclipsed) throw this.breaks('ECLIPSE');

const at = floatNumber(Math.round(target.flags.isDodging / iDex));
console.log('Dodging: ', at);
const r = MiscService.rndm('1d100');
const c = Math.round(Math.sqrt(at) + (10 * at) + 5);
console.log('left:', c, ' right:', r, ' result:', c > r);
if (c > r) throw this.breaks('DODGED');
}
if (target.flags.isParry) {
if (+(target.flags.isParry - iDex) > 0) {
throw this.breaks('PARRYED');
} else {
target.flags.isParry -= +iDex;
this.preAffects.forEach((preAffect) => {
const result = preAffect.check(this.params);

if (result && isFailResult(result)) {
throw this.breaks(result.message);
}
}
});
}

/**
Expand All @@ -99,7 +91,6 @@ class PhysConstructor {
fitsCheck() {
const { initiator } = this.params;
if (!initiator.weapon) throw this.breaks('NO_WEAPON');
if (initiator.flags.isDisarmed) throw this.breaks('DISARM');
}

/**
Expand Down
7 changes: 7 additions & 0 deletions src/arena/Constuructors/PreAffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type GameService from '../GameService';
import type { Player } from '../PlayersService';
import type { Breaks } from './types';

export interface PreAffect {
check(params: { initiator: Player, target: Player, game: GameService}): Breaks | void
}
60 changes: 36 additions & 24 deletions src/arena/Constuructors/SkillConstructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import type Game from '../GameService';
import MiscService from '../MiscService';
import type { Player } from '../PlayersService';
import type {
CostType, OrderType, AOEType, Breaks, BreaksMessage, CustomMessage, BaseNext,
CostType, OrderType, AOEType, CustomMessage, BaseNext, Breaks, BreaksMessage,
} from './types';
import { handleCastError } from './utils';

export type SkillNext = BaseNext & {
actionType: 'skill';
Expand Down Expand Up @@ -65,10 +66,10 @@ export abstract class Skill {
this.getCost();
this.checkChance();
this.run();
this.next();
this.getExp(initiator);
} catch (failMsg) {
game.recordOrderResult(failMsg);
} catch (error) {
handleCastError(error, (error) => {
this.fail(error.message);
});
}
}

Expand All @@ -83,7 +84,7 @@ export abstract class Skill {
if (remainingEnergy >= 0) {
initiator.stats.set(this.costType, remainingEnergy);
} else {
throw this.breaks('NO_ENERGY');
throw this.getFailResult('NO_ENERGY');
}
}

Expand All @@ -93,7 +94,7 @@ export abstract class Skill {
checkChance(): void {
if (MiscService.rndm('1d100') > this.getChance()) {
// скил сфейлился
throw this.breaks('SKILL_FAIL');
throw this.getFailResult('SKILL_FAIL');
}
}

Expand All @@ -108,39 +109,50 @@ export abstract class Skill {
}

/**
* Успешное прохождение скила и отправка записи в BattleLog
* Рассчитываем полученный exp
*/
next(): void {
const { initiator, target, game } = this.params;
const args: SkillNext = {
getExp(initiator: Player): void {
initiator.stats.up('exp', this.baseExp);
}

getFailResult(message: BreaksMessage, { initiator, target } = this.params): Breaks {
return {
action: this.displayName,
initiator: initiator.nick,
target: target.nick,
actionType: 'skill',
message,
};
}

getSuccessResult({ initiator, target } = this.params): SkillNext {
return {
exp: this.baseExp,
action: this.displayName,
actionType: 'skill',
target: target.nick,
initiator: initiator.nick,
msg: this.customMessage?.bind(this),
};

game.recordOrderResult(args);
}

/**
* Расчитываем полученный exp
* Успешное прохождение скила и отправка записи в BattleLog
*/
getExp(initiator: Player): void {
initiator.stats.mode('up', 'exp', this.baseExp);
success({ initiator, target, game } = this.params): void {
this.getExp(initiator);

const result = this.getSuccessResult({ initiator, target, game });

game.recordOrderResult(result);
}

/**
* Обработка провала магии
*/
breaks(e: BreaksMessage): Breaks {
return {
action: this.displayName,
initiator: this.params.initiator.nick,
target: this.params.target.nick,
actionType: 'skill',
message: e,
};
fail(message: BreaksMessage, { initiator, target, game } = this.params): void {
const result = this.getFailResult(message, { initiator, target, game });

game.recordOrderResult(result);
}
}
14 changes: 13 additions & 1 deletion src/arena/Constuructors/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FailArgs, SuccessArgs } from '../types';
import type { Breaks, FailArgs, SuccessArgs } from '../types';

export const isSuccessResult = (result: SuccessArgs | FailArgs): result is SuccessArgs => {
return !('message' in result);
Expand All @@ -11,3 +11,15 @@ export const isSuccessDamageResult = (result: SuccessArgs | FailArgs) => {

return false;
};

export const isFailResult = (result: SuccessArgs | FailArgs): result is FailArgs => {
return 'message' in result;
};

export const handleCastError = (error: unknown, onActionError: (error: Breaks) => void) => {
if (error instanceof Error) {
console.error(error);
} else {
onActionError(error as Breaks);
}
};
67 changes: 67 additions & 0 deletions src/arena/actions/__snapshots__/attack.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`attack should dodge 1`] = `
[
8,
8,
]
`;

exports[`attack should dodge 2`] = `0`;

exports[`attack should dodge 3`] = `
[
{
"action": "🐍 Увертка",
"actionType": "skill",
"exp": 50,
"initiator": "magni",
"msg": [Function],
"target": "asperiores",
},
{
"action": "attack",
"actionType": "phys",
"initiator": "asperiores",
"message": "DODGED",
"target": "magni",
"weapon": {
"code": "a100",
},
},
]
`;

exports[`attack should not dodge 1`] = `
[
8,
-2.3,
]
`;

exports[`attack should not dodge 2`] = `82`;

exports[`attack should not dodge 3`] = `
[
{
"action": "🐍 Увертка",
"actionType": "skill",
"initiator": "amet",
"message": "SKILL_FAIL",
"target": "ratione",
},
{
"action": "attack",
"actionType": "phys",
"dmg": 10.3,
"dmgType": "physical",
"exp": 82,
"hp": -2.3,
"initiator": "ratione",
"target": "amet",
"weapon": {
"code": "a100",
},
},
]
`;
62 changes: 62 additions & 0 deletions src/arena/actions/attack.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import casual from 'casual';
import CharacterService from '@/arena/CharacterService';
import GameService from '@/arena/GameService';
import { profsData } from '@/data/profs';
import TestUtils from '@/utils/testUtils';
import attack from './attack';

// npm t src/arena/actions/attack.test.ts

describe('attack', () => {
let game: GameService;

beforeAll(() => {
casual.seed(1);
});

beforeEach(async () => {
const harks = { ...profsData.m.hark, wis: 20 };
const initiator = await TestUtils.createCharacter({ prof: 'm', magics: { fireBall: 3 }, harks }, { withWeapon: true });
const target = await TestUtils.createCharacter({ skills: { dodge: 3 } });

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('should dodge', () => {
game.players.players[0].proc = 1;

game.players.players[1].flags.isDodging = Infinity;
attack.cast(game.players.players[0], game.players.players[1], game);

expect(
game.players.players.map((player) => player.stats.val('hp')),
).toMatchSnapshot();
expect(game.players.players[0].stats.val('exp')).toMatchSnapshot();
expect( TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot();
});



it('should not dodge', () => {
game.players.players[0].proc = 1;

game.players.players[1].flags.isDodging = -Infinity;
attack.cast(game.players.players[0], game.players.players[1], game);

expect(
game.players.players.map((player) => player.stats.val('hp')),
).toMatchSnapshot();
expect(game.players.players[0].stats.val('exp')).toMatchSnapshot();
expect(TestUtils.normalizeRoundHistory(game.getRoundResults())).toMatchSnapshot();
});
});
Loading
Loading