Skip to content

Commit

Permalink
#278 Skill fail (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyvg authored Nov 4, 2023
1 parent 1db6639 commit 2b79c46
Show file tree
Hide file tree
Showing 18 changed files with 515 additions and 75 deletions.
57 changes: 25 additions & 32 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 { isSuccessResult } = 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.params.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 && isSuccessResult(result)) {
throw this.breaks(result.message, result);
}
}
});
}

/**
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 Expand Up @@ -204,12 +195,14 @@ class PhysConstructor {
}

/**
* @param {String} msg строка остановки атаки (причина)
* @param {string} message строка остановки атаки (причина)
* @param {import('./types').SuccessArgs} cause строка остановки атаки (причина)
*/
breaks(msg) {
breaks(message, cause) {
return {
actionType: 'phys',
message: msg,
message,
cause,
action: this.name,
initiator: this.params.initiator.nick,
target: this.params.target.nick,
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 { SuccessArgs } from './types';

export interface PreAffect {
check(params: { initiator: Player, target: Player, game: GameService}): SuccessArgs | void
}
76 changes: 52 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 @@ -39,6 +40,10 @@ export abstract class Skill {
game: Game;
};

status = {
exp: 0,
};

/**
* Создание скила
*/
Expand All @@ -65,10 +70,11 @@ export abstract class Skill {
this.getCost();
this.checkChance();
this.run();
this.next();
this.getExp(initiator);
} catch (failMsg) {
game.recordOrderResult(failMsg);
this.success();
} catch (error) {
handleCastError(error, (error) => {
this.fail(error.message);
});
}
}

Expand All @@ -83,7 +89,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 +99,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 +114,61 @@ export abstract class Skill {
}

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

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

game.recordOrderResult(args);
this.reset();

return result;
}

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

this.reset();

return result;
}

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

reset() {
this.status.exp = 0;
}
}
2 changes: 2 additions & 0 deletions src/arena/Constuructors/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export type PhysNext = BaseNext & {

export type PhysBreak = Omit<BaseNext, 'exp'> & {
actionType: 'phys';
cause?: SuccessArgs;
message: BreaksMessage;
weapon: Item;
expArr: ExpArr;
Expand All @@ -94,6 +95,7 @@ export type ActionType = SuccessArgs['actionType'];
export interface Breaks {
actionType: ActionType;
message: BreaksMessage;
cause?: SuccessArgs;
action: string;
initiator: string;
target: string;
Expand Down
10 changes: 9 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,11 @@ export const isSuccessDamageResult = (result: SuccessArgs | FailArgs) => {

return false;
};

export const handleCastError = (error: unknown, onActionError: (error: Breaks) => void) => {
if (error instanceof Error) {
console.error(error);
} else {
onActionError(error as Breaks);
}
};
10 changes: 10 additions & 0 deletions src/arena/LogService/utils/format-cause.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SuccessArgs } from '@/arena/Constuructors/types';

export function formatCause(cause: SuccessArgs) {
switch (cause.actionType) {
case 'skill':
return `_${cause.action}_ *${cause.initiator}*: 📖${cause.exp}`;
default:
return `_${cause.action}_ *${cause.initiator}*`;
}
}
13 changes: 11 additions & 2 deletions src/arena/LogService/utils/format-error.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { BreaksMessage, FailArgs } from '@/arena/Constuructors/types';

import { formatCause } from './format-cause';
/**
* msg
* @todo WIP, функция должна будет принимать как значения урона т.п так и
Expand All @@ -9,12 +9,21 @@ import type { BreaksMessage, FailArgs } from '@/arena/Constuructors/types';
*/
export function formatError(msgObj: FailArgs): string {
const {
action, message, target, initiator,
action, actionType, message, target, initiator, cause,
} = msgObj;

const expString = 'expArr' in msgObj ? msgObj.expArr.map(({ name, exp }) => `${name}: 📖${exp}`).join(', ') : '';
const weapon = 'weapon' in msgObj ? msgObj.weapon.case : '';

if (cause) {
switch (actionType) {
case 'phys':
return `*${initiator} пытался атаковать ${target}, но у него не получилось\n${formatCause(cause)}`;
default:
return `*${initiator} пытался использовать _${action}_ ${target}, но у него не получилось\n${formatCause(cause)}`;
}
}

const TEXT: Record<BreaksMessage | 'default', Record<'en' | 'ru', string>> = {
NO_INITIATOR: {
ru: `Некто хотел использовать _${action}_ на игрока *${target}*, но исчез`,
Expand Down
2 changes: 2 additions & 0 deletions src/arena/LogService/utils/format-exp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export function formatExp(args: SuccessArgs): string {
return expBrackets(args.expArr.map(({ name, exp, val }) => `${name}: 💖${val}/${args.hp} 📖${exp}`).join(', '));
case 'phys':
return expBrackets(`💔-${args.dmg}/${args.hp} 📖${args.exp}`);
case 'skill':
return args.exp ? expBrackets(`📖${args.exp}`) : '';
default:
return expBrackets(`📖${args.exp}`);
}
Expand Down
57 changes: 57 additions & 0 deletions src/arena/skills/__snapshots__/disarm.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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",
},
},
]
`;

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",
},
},
]
`;
Loading

0 comments on commit 2b79c46

Please sign in to comment.