Skip to content
Merged
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
9 changes: 8 additions & 1 deletion src/core/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { BASE_FPS, BASE_MAX_COMMAND_EXECUTING_ON_TICK_LIMIT, isServer } from "@c
import { BluePrintsFactory, EffectFactory, IteractionsFactory, QuestsFactory, SoundsFactory } from "@factories";
import { GlobalStore } from "@store";
import { baseChecksMiddleware, DropItemGuard, EntityInteractGuard, EquipItemGuard, MovementGuard, OpenChestGuard, PickUpGuard, ShootGuard, UseItemGuard } from "@middlewares";
import { createPluginProto, extractMethodFromPlugin, extractPropertyFromPlugin } from "@utils";
import { createPluginProto, extractMethodFromPlugin, extractPropertyFromPlugin, useLink, useValidation, useVisibility, useAttack } from "@utils";
import type { Entity, GameObject } from "@world";
import { ConflictResolverPlugin } from "@plugins";

Expand Down Expand Up @@ -378,6 +378,13 @@ export class Game implements IGame {
if (options?.usingEntityMiddlewares) this.use([DropItemGuard, EntityInteractGuard, EquipItemGuard, MovementGuard, OpenChestGuard, PickUpGuard, UseItemGuard])
if (options?.usingObjectMiddlewares) this.use([ShootGuard])
if (!(options?.disableConflictResolver)) this.registerPlugin(new ConflictResolverPlugin())

if (!(options?.disableHooksHydration)) {
useLink.prototype.game = this
useValidation.prototype.game = this
useVisibility.prototype.game = this
useAttack.prototype.game = this
}
}

public on<T>(event: keyof typeof GameEvent, cb: EventCallback<T>) {
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/core/engine/engine-options.intefaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export interface IGameOptions {
*/
readonly disableConflictResolver?: boolean;

/**
* Disable inject game into hooks for them functionallity.
* If true, you need inject game into hooks options manually,
* but this can be multiply perfomance
*/
readonly disableHooksHydration?: boolean;

/**
* Optional command bus options
*/
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./use-visibility.hook.interface.js"
export * from "./use-validation.hook.interface.js"
export * from "./use-validation.hook.interface.js"
export * from "./use-link.hook.interface.js"
58 changes: 58 additions & 0 deletions src/interfaces/hooks/use-link.hook.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Game } from "@core";
import type { Linkable, UnlinkWhen } from "@types";

export interface ILink {
/**
* Parent of link
*/
readonly from: Linkable;

/**
* Child of link
*/
readonly to: Linkable;

/**
* Link destroy function
*/
readonly unLink: VoidFunction;

/**
* Flag indicated link active status
*/
isActive: boolean;

/**
* You can reactivate link, if then deleted
* @param options - Optional new options to link
* @returns { ILink | false } - ILink if relink success, false if link already exists
*/
readonly link: (options?: ILinkOptions) => ILink | false;
}

export interface ILinkOptions {
/**
* Game reference, if you disabled
*/
readonly game?: Game;

/**
* Max distance between Child and Parent
*/
readonly maxDistance?: number;

/**
* Kill child of link, if parent was killed
*/
readonly killChild?: boolean;

/**
* Delete child of link, if parent was deleted
*/
readonly deleteChild?: boolean;

/**
* Auto delete link when
*/
readonly autoUnlinkOn?: UnlinkWhen[];
}
18 changes: 10 additions & 8 deletions src/interfaces/hooks/use-validation.hook.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
export interface IUseValidationResult {
export interface IUseValidationResult extends IUseValidationContext {
/**
* Callback executing (dispatch) action, returns true if success, else false
* @param data - Data to executing command
* @returns { boolean } - True if correct executing, else false
*/
readonly confirm: <T>(data: T) => boolean;
}

export interface IUseValidationContext {
/**
* Indicates, can this action will do
*/
Expand All @@ -8,11 +17,4 @@ export interface IUseValidationResult {
* Array of errors, with reason, why action cant be executed
*/
readonly errors: string[];

/**
* Callback executing (dispatch) action, returns true if success, else false
* @param data - Data to executing command
* @returns { boolean } - True if correct executing, else false
*/
readonly confirm: <T>(data: T) => boolean;
}
2 changes: 2 additions & 0 deletions src/types/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./linkable.type.js"
export * from "./unlink-when.type.js"
6 changes: 6 additions & 0 deletions src/types/hooks/linkable.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Entity, GameObject } from "@world";

/**
* Linkable (Entity or GameObject)
*/
export type Linkable = (Entity | GameObject)
4 changes: 4 additions & 0 deletions src/types/hooks/unlink-when.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Link will be auto removed, when only of this events occured
*/
export type UnlinkWhen = "parentKilled" | "parentDeleted" | "childOutOfRange"
3 changes: 2 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from "./callbacks/index.js"
export * from "./items/index.js"
export * from "./factories/index.js"
export * from "./decorators/index.js"
export * from "./global/index.js"
export * from "./global/index.js"
export * from "./hooks/index.js"
3 changes: 2 additions & 1 deletion src/utils/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./use-attack.hook.js"
export * from "./use-visibility.hook.js"
export * from "./use-validation.hook.js"
export * from "./use-validation.hook.js"
export * from "./use-link.hook.js"
6 changes: 4 additions & 2 deletions src/utils/hooks/use-attack.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import type { IDeadData } from "@interfaces";

/**
* Calc attack, emit DeadEvent, return victim dead state
* @param game - Game reference to emit event
* @param dmg - Total damage (full calculated)
* @param attacker - Attacker reference (another Entity, tower, etc.)
* @param victim - Victim reference
* @param core - Game reference to emit event (if hydration disabled)
* @returns { { isDead: boolean } } - GameObject with dead info
*/
export function useAttack(game: Game, dmg: number, attacker: Entity | GameObject, victim: Entity): { isDead: boolean; } {
export function useAttack(dmg: number, attacker: Entity | GameObject, victim: Entity, core?: Game): { isDead: boolean; } {
const game = useAttack.prototype.game as Game || core

victim.health = victim.health - (dmg >= 0 ? dmg : 0)

if (victim.health <= 0) {
Expand Down
67 changes: 67 additions & 0 deletions src/utils/hooks/use-link.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { anyWorldObjectIsGameObject, convertAnyPositionToPosition } from "@utils";
import type { Linkable } from "@types";
import type { IDeadData, ILink, ILinkOptions, IMovedData, IUseValidationContext } from "@interfaces";
import { USE_VALIDATION_EVENT_PREFIX } from "@const";
import { CommandType } from "@enums";
import type { Game } from "@core";

/**
* Creates Link between two entities
* @param from - Linkable WO from
* @param to - Linkable WO to
* @returns { ILink }
*/
export function useLink(from: Linkable, to: Linkable, options?: ILinkOptions): ILink {
const game = useLink.prototype.game as Game

const parentIsEntity = !anyWorldObjectIsGameObject(from)
const childIsEntity = !anyWorldObjectIsGameObject(to)

let move: VoidFunction;
let kill: VoidFunction;
let deleting: VoidFunction;

const link = {
from,
to,
isActive: true,
unLink: () => {
if (move) move()
if (kill) kill()
if (deleting) deleting()

link.isActive = false
},
link: (options?: ILinkOptions) => link.isActive ? false : useLink(from, to, options)
} as ILink

if (childIsEntity && options?.maxDistance) move = game.registerCustomEvent<IMovedData & IUseValidationContext>(`${USE_VALIDATION_EVENT_PREFIX}:${CommandType.MOVE}`, (opt, event, data) => {
if (
data.entity
&& !anyWorldObjectIsGameObject(data.entity)
&& data.entity.id === to.id
) {

const [x1, y1] = from.position
const [x2, y2] = convertAnyPositionToPosition(data.eventData.newPosition || (data.eventData as any).position)

if (x2-x1 > options!.maxDistance! || y2-y1 > options!.maxDistance!) {
data.eventData.isAllowed = false
data.eventData.errors.push(`[useLink]: Max distance occured (${options!.maxDistance})`)
}
if (options.autoUnlinkOn?.includes("childOutOfRange")) link.unLink()
}
})
if (parentIsEntity && childIsEntity) {
if (options?.killChild) kill = game.on<IDeadData>("entityDead", (opt, event, data) => {
if (data.eventData.entity.id === from.id) game.options.manager.kill(to.id)
if (options.autoUnlinkOn?.includes("parentKilled")) link.unLink()
})
if (options?.deleteChild) deleting = game.on<{}>("entityDeleted", (opt, event, data) => {
if (data.entity!.id === from.id) game.options.manager.delete(to.id)
if (options.autoUnlinkOn?.includes("parentDeleted")) link.unLink()
})
}

return link
}
6 changes: 4 additions & 2 deletions src/utils/hooks/use-validation.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { anyWorldObjectIsGameObject } from "@utils";

/**
* Throw validation event, all plugins can listen and block/unblock it
* @param game - Game reference
* @param subject - Subject, who make action
* @param action - Command to execute
* @param ctx - Context in this hook
* @param core - Game reference (if hydration disabled)
* @returns { IUseValidationResult } - Hook result
*/
export function useValidation<T = any>(game: Game, subject: Entity | GameObject, action: CommandType, ctx: CommandContext): IUseValidationResult {
export function useValidation<T = any>(subject: Entity | GameObject, action: CommandType, ctx: CommandContext, core?: Game): IUseValidationResult {
const game = useValidation.prototype.game as Game || core

const resultContext = {
...ctx,
isAllowed: true,
Expand Down
6 changes: 4 additions & 2 deletions src/utils/hooks/use-visibility.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { USE_VISIBILITY_EVENT } from "@const";

/**
* Util calculate how well observer can see target. Throwing useVisibility:calcVisibility custom event
* @param game - Game reference
* @param observer - Who see
* @param target - Target
* @param core - Game reference (if hydration disabled)
* @returns { IUseVisibiltyResult } - Result of check
*/
export function useVisibility(game: Game, observer: Entity | GameObject, target: Entity | GameObject): IUseVisibiltyResult {
export function useVisibility(observer: Entity | GameObject, target: Entity | GameObject, core?: Game): IUseVisibiltyResult {
const game = useVisibility.prototype.game as Game || core

if (canSee(observer.position, target.position, game.options.map)) {
const context = {
isVisible: true,
Expand Down
2 changes: 1 addition & 1 deletion src/world/entities/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class Entity implements ITarget {
for (const entity of entities) {
const totalDamage = this.fullDamage - entity.armorHealth

const { isDead } = useAttack(this.manager.game, totalDamage, this, entity)
const { isDead } = useAttack(totalDamage, this, entity, this.manager.game)

if (isDead) counter++
}
Expand Down
2 changes: 1 addition & 1 deletion src/world/entities/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class GameObject implements IGameObject {
let counter = 0;

for (const victim of entities) {
const { isDead } = useAttack(this.map.game, this.metadata.damage, this, victim)
const { isDead } = useAttack(this.metadata.damage, this, victim, this.map.game)

if (isDead) counter++
}
Expand Down
12 changes: 9 additions & 3 deletions test/gametest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BASE_SEARCH_RADIUS, USE_VALIDATION_EVENT_PREFIX, USE_VISIBILITY_EVENT }
import { BluePrintsFactory, EffectFactory, IteractionsFactory, QuestsFactory, SoundsFactory } from "@factories";
import { loggerMiddleware } from "@middlewares";
import { RegenerationPlugin, NetworkPlguin, AsyncPlugin, GraphicPlugin } from "@plugins";
import { useVisibility, checkTwoPositions, useValidation } from "@utils";
import { useVisibility, checkTwoPositions, useValidation, useLink } from "@utils";

const [game, manager, map] = createGame({
usingEntityMiddlewares: true,
Expand Down Expand Up @@ -314,6 +314,12 @@ game.dispatch({
}
})

useLink(player, player_second, { maxDistance: 1, autoUnlinkOn: ["childOutOfRange", "parentDeleted", "parentKilled"], killChild: false, deleteChild: false })

console.log(useValidation(player_second, CommandType.MOVE, {
newPosition: [7, 7]
}), 'VALIDATION RES (BLOCKED!)')

const events = new Map<CommandType, NetworkCallback>()

events.set(CommandType.SET_STATE, (ev, data) => {
Expand Down Expand Up @@ -343,8 +349,8 @@ game.registerCustomEvent<IUseValidationResult>(`${USE_VALIDATION_EVENT_PREFIX}:$
d.eventData.errors.push('OUT OF REACH')
})

console.log(useVisibility(game, player, player_second))
console.log(useValidation(game, player, CommandType.USE_ITEM, {
console.log(useVisibility(player, player_second))
console.log(useValidation(player, CommandType.USE_ITEM, {
forTest: true
}))

Expand Down
Loading