diff --git a/src/modules/game/game.ts b/src/modules/game/game.ts index 371dd8f..30202b6 100644 --- a/src/modules/game/game.ts +++ b/src/modules/game/game.ts @@ -1,8 +1,19 @@ import { MatrixClient } from "matrix-js-sdk"; import type { ICallbackStore } from "../types.js"; import { config } from "../../config.js"; -import { getPlayerById, getRace } from "../../services/game/entity.js"; import { + existsEntity, + getEntitiesAtLocation, + getEntityByName, + getHealthPercentage, + getMaxHealth, + getNpcsAtLocation, + getPlayer, + getPlayersAtLocation, + getRace, +} from "../../services/game/entity.js"; +import { + entitiesShareLocation, existsLocation, getLocation, getLocationByName, @@ -16,6 +27,7 @@ import { getLevel } from "../../services/game/entity.js"; import { getFullItemName, getTotalStats, + type IEntity, type IItem, type IStat, type Location, @@ -51,9 +63,17 @@ const registerModuleGame = ( callbackFunc: onNearby, }); callbackStore.messageCallbacks.push({ - startConditions: [`${gamePrefix} travel`], + startConditions: [`${gamePrefix} travel `], callbackFunc: onTravel, }); + callbackStore.messageCallbacks.push({ + startConditions: [`${gamePrefix} entities`], + callbackFunc: onEntities, + }); + callbackStore.messageCallbacks.push({ + startConditions: [`${gamePrefix} entity `], + callbackFunc: onEntity, + }); }; const onHelp = (_text: string, roomId: string) => { @@ -68,6 +88,8 @@ const onHelp = (_text: string, roomId: string) => {
  • !game location - Shows information about your current location
  • !game nearby - Shows nearby locations
  • !game travel {location} - Travel to a location
  • +
  • !game entities - Shows entities at your location
  • +
  • !game entity {entity} - Shows information about an entity
  • (WIP) !game talk {entity} - Talk to an entity
  • (WIP) !game fight {entity} - Fight an entity
  • (WIP) !game work {action} - Start work
  • @@ -76,37 +98,38 @@ const onHelp = (_text: string, roomId: string) => { }; const onStatus = (_text: string, roomId: string, sender: string) => { - const player = getPlayerById(sender); - const race = getRace(player.race); - const location = getLocation(player.location); + const entity = getEntityByName(sender); + const race = getRace(entity.race); + const location = getLocation(entity.location); - const level = getLevel(player.experience); + const level = getLevel(entity.experience); client.sendHtmlMessage( roomId, "", ``, ); }; const onInventory = (text: string, roomId: string, sender: string) => { - const player = getPlayerById(sender); + const player = getPlayer(sender); const itemIndex = text.replace(`${gamePrefix} inventory `, "").trim(); const itemIndexInt = Number(itemIndex); @@ -170,46 +193,52 @@ const onInventory = (text: string, roomId: string, sender: string) => { }; const onLocation = (_text: string, roomId: string, sender: string) => { - const player = getPlayerById(sender); + const player = getPlayer(sender); const location = getLocation(player.location); client.sendHtmlMessage( roomId, "", - ``, ); }; const onNearby = (_text: string, roomId: string, sender: string) => { - const player = getPlayerById(sender); + const player = getPlayer(sender); const location = getLocation(player.location); + const parentLocation = getParentLocation(player.location); const mapLocation = (locId: Location): string => { const locData = getLocation(locId); const distance = getLocationDistance(location, locData); - return `
  • ${locData.name} - ${distance.toFixed(1)}km - ${locData.description}
  • `; + let distanceText = `${distance.toFixed(1)}km`; + if (locId === player.location) { + distanceText = `you are here`; + } + + return `
  • ${locData.name} - ${distanceText} - ${locData.description}
  • `; }; client.sendHtmlMessage( roomId, "", - `

    There are ${location.childLocations.length} locations around you (${player.name})

    + `

    There are ${location.childLocations.length + parentLocation.childLocations.length} locations around you (${player.name})

    `, ); }; const onTravel = (text: string, roomId: string, sender: string) => { - const player = getPlayerById(sender); + const player = getPlayer(sender); const locationName = text.replace(`${gamePrefix} travel `, "").trim(); if (!existsLocation(locationName)) { @@ -246,4 +275,44 @@ const onTravel = (text: string, roomId: string, sender: string) => { startTravel(player, travelLocation.id, client, roomId); }; +const onEntities = (_text: string, roomId: string, sender: string) => { + const player = getPlayer(sender); + const location = getLocation(player.location); + + const mapEntity = (entity: IEntity): string => { + return `
  • ${entity.name} - ${getRace(entity.race).name}
  • `; + }; + + client.sendHtmlMessage( + roomId, + "", + `

    Entities in ${location.name}:

    + +

    Players in ${location.name}:

    + `, + ); +}; + +const onEntity = (text: string, roomId: string, sender: string) => { + const player = getPlayer(sender); + + const entityName = text.replace(`${gamePrefix} entity `, "").trim(); + if (!existsEntity(entityName)) { + client.sendTextMessage(roomId, "No such entity exists"); + return; + } + + const entity = getEntityByName(entityName); + if (!entitiesShareLocation(player, entity)) { + client.sendTextMessage(roomId, "No such entity in your vicinity"); + return; + } + + onStatus("", roomId, sender); +}; + export { registerModuleGame }; diff --git a/src/services/game/entity.ts b/src/services/game/entity.ts index 185cd00..49fa84c 100644 --- a/src/services/game/entity.ts +++ b/src/services/game/entity.ts @@ -1,6 +1,15 @@ import { getUserNameById } from "../../helpers.js"; import { state } from "../../store/store.js"; -import type { IEntity, IPlayer } from "./structures/entities.js"; +import { + npcBecky, + npcs, + type IEntity, + type INPC, + type INPCData, + type IPlayer, + type NPC, + type TFullNPC, +} from "./structures/entities.js"; import { itemShortsword } from "./structures/items.js"; import { Location } from "./structures/locations.js"; import { Race, raceHuman, races, type IRace } from "./structures/races.js"; @@ -28,11 +37,36 @@ const createPlayer = (name: string): IPlayer => ({ lockpicking: 0, }); -const getPlayerById = (userId: string): IPlayer => { - return getPlayer(getUserNameById(userId)); +const createNpcData = (npc: INPC): INPCData => ({ + id: npc.id, + health: getMaxHealth(npc), + dead: false, +}); + +const existsPlayer = (name: string): boolean => { + return ( + state.game.players.find( + (player) => player.name.toLowerCase() === name.toLowerCase(), + ) !== undefined + ); }; -const getPlayer = (name: string): IPlayer => { +const existsNpc = (name: string): boolean => { + return ( + npcs.find((npc) => npc.name.toLowerCase() === name.toLowerCase()) !== + undefined + ); +}; + +const existsEntity = (name: string): boolean => { + return existsPlayer(name) || existsNpc(name); +}; + +const getPlayer = (id: string): IPlayer => { + return getPlayer(getUserNameById(id)); +}; + +const getPlayerByName = (name: string): IPlayer => { const player = state.game.players.find((player) => player.name === name); if (player) { return player; @@ -44,6 +78,55 @@ const getPlayer = (name: string): IPlayer => { return newPlayer; }; +const getEntityByName = (name: string): IPlayer | TFullNPC => { + if (existsPlayer(name)) { + return getPlayer(name); + } + + const npc = getNpcByName(name); + + return { ...npc, ...getNpcData(npc.id) }; +}; + +const getNpc = (id: NPC): INPC => { + const npc = npcs.find((npc) => npc.id === id); + + return npc ? npc : npcBecky; +}; + +const getNpcByName = (name: string): INPC => { + const npc = npcs.find((npc) => npc.id === name); + + return npc ? npc : npcBecky; +}; + +const getNpcData = (id: NPC): INPCData => { + const npcData = state.game.npcs.find((npcData) => npcData.id === id); + if (npcData) { + return npcData; + } + + const newNpcData = createNpcData(getNpc(id)); + state.game.npcs.push(newNpcData); + + return newNpcData; +}; + +const getNpcsAtLocation = (id: Location): INPC[] => { + return npcs.filter((npc) => npc.location === id); +}; + +const getPlayersAtLocation = (id: Location): IPlayer[] => { + return state.game.players.filter((player) => player.location === id); +}; + +const getEntitiesAtLocation = (id: Location): IEntity[] => { + const npcsAtLocation = getNpcsAtLocation(id); + const playersAtLocation = getPlayersAtLocation(id); + + return [...npcsAtLocation, ...playersAtLocation]; +}; + const getRace = (id: Race): IRace => { const race = races.find((race) => race.id === id); @@ -69,8 +152,41 @@ const getLevel = (experience: number): ILevel => { }; }; -const getSpeed = (entity: IEntity) => { - return 1 + Math.sqrt(entity.agility + entity.endurance) / 3; +const getMaxHealth = (entity: IEntity): number => { + return entity.vitality * 10; }; -export { createPlayer, getPlayerById, getPlayer, getRace, getLevel, getSpeed }; +const getHealthPercentage = (entity: IPlayer | INPC): number => { + if ("health" in entity) { + return entity.health / getMaxHealth(entity); + } + + const npcData = getNpcData(entity.id); + return npcData.health / getMaxHealth(entity); +}; + +const getSpeed = (entity: IEntity) => { + return 60 + 3 + Math.sqrt(entity.agility + entity.endurance) / 2; +}; + +export { + createPlayer, + createNpcData, + existsPlayer, + existsNpc, + existsEntity, + getPlayer, + getPlayerByName, + getEntityByName, + getNpc, + getNpcByName, + getNpcData, + getNpcsAtLocation, + getPlayersAtLocation, + getEntitiesAtLocation, + getRace, + getLevel, + getMaxHealth, + getHealthPercentage, + getSpeed, +}; diff --git a/src/services/game/location.ts b/src/services/game/location.ts index f0881cc..dbb7939 100644 --- a/src/services/game/location.ts +++ b/src/services/game/location.ts @@ -5,7 +5,7 @@ import { locations, type ILocation, } from "./structures/locations.js"; -import type { IPlayer } from "./structures/entities.js"; +import type { IEntity, IPlayer } from "./structures/entities.js"; import { getSpeed } from "./entity.js"; const existsLocation = (name: string): boolean => { @@ -34,8 +34,24 @@ const getLocationDistance = ( locationA: ILocation, locationB: ILocation, ): number => { - const deltaX = Math.abs(locationA.X - locationB.X); - const deltaY = Math.abs(locationA.Y - locationB.Y); + const parentOfA = getParentLocation(locationA.id); + const parentOfB = getParentLocation(locationB.id); + + let locAX = locationA.X; + let locAY = locationA.Y; + let locBX = locationB.X; + let locBY = locationB.Y; + + if (locationA.id === parentOfB.id) { + locBX += locAX; + locBY += locAY; + } else if (locationB.id === parentOfA.id) { + locAX += locBX; + locAY += locBY; + } + + const deltaX = Math.abs(locAX - locBX); + const deltaY = Math.abs(locAY - locBY); const deltaPow = Math.pow(deltaX, 2) + Math.pow(deltaY, 2); @@ -59,6 +75,13 @@ const hasLocation = ( return childLocations.includes(locationChild); }; +const entitiesShareLocation = (entityA: IEntity, entityB: IEntity): boolean => { + const locationA = getLocation(entityA.location); + const locationB = getLocation(entityB.location); + + return locationA.id === locationB.id; +}; + const getTravelTimeInHours = ( player: IPlayer, locationA: ILocation, @@ -115,6 +138,7 @@ export { getLocationDistance, getParentLocation, hasLocation, + entitiesShareLocation, getTravelTimeInHours, getTravelTimeInMilliseconds, startTravel, diff --git a/src/services/game/structures/entities.ts b/src/services/game/structures/entities.ts index d753dd2..c9d63b7 100644 --- a/src/services/game/structures/entities.ts +++ b/src/services/game/structures/entities.ts @@ -36,6 +36,8 @@ export interface INPCData { dead: boolean; } +export type TFullNPC = INPC & INPCData; + export enum NPC { BECKY = "BECKY", }