Add entity info

This commit is contained in:
Aslan 2026-01-23 08:47:15 -05:00
parent 0958a8c272
commit 58df618956
4 changed files with 250 additions and 39 deletions

View file

@ -1,8 +1,19 @@
import { MatrixClient } from "matrix-js-sdk"; import { MatrixClient } from "matrix-js-sdk";
import type { ICallbackStore } from "../types.js"; import type { ICallbackStore } from "../types.js";
import { config } from "../../config.js"; import { config } from "../../config.js";
import { getPlayerById, getRace } from "../../services/game/entity.js";
import { import {
existsEntity,
getEntitiesAtLocation,
getEntityByName,
getHealthPercentage,
getMaxHealth,
getNpcsAtLocation,
getPlayer,
getPlayersAtLocation,
getRace,
} from "../../services/game/entity.js";
import {
entitiesShareLocation,
existsLocation, existsLocation,
getLocation, getLocation,
getLocationByName, getLocationByName,
@ -16,6 +27,7 @@ import { getLevel } from "../../services/game/entity.js";
import { import {
getFullItemName, getFullItemName,
getTotalStats, getTotalStats,
type IEntity,
type IItem, type IItem,
type IStat, type IStat,
type Location, type Location,
@ -54,6 +66,14 @@ const registerModuleGame = (
startConditions: [`${gamePrefix} travel `], startConditions: [`${gamePrefix} travel `],
callbackFunc: onTravel, callbackFunc: onTravel,
}); });
callbackStore.messageCallbacks.push({
startConditions: [`${gamePrefix} entities`],
callbackFunc: onEntities,
});
callbackStore.messageCallbacks.push({
startConditions: [`${gamePrefix} entity `],
callbackFunc: onEntity,
});
}; };
const onHelp = (_text: string, roomId: string) => { const onHelp = (_text: string, roomId: string) => {
@ -68,6 +88,8 @@ const onHelp = (_text: string, roomId: string) => {
<li><b>!game location</b> - Shows information about your current location</li> <li><b>!game location</b> - Shows information about your current location</li>
<li><b>!game nearby</b> - Shows nearby locations</li> <li><b>!game nearby</b> - Shows nearby locations</li>
<li><b>!game travel {location}</b> - Travel to a location</li> <li><b>!game travel {location}</b> - Travel to a location</li>
<li><b>!game entities</b> - Shows entities at your location</li>
<li><b>!game entity {entity}</b> - Shows information about an entity</li>
<li><b>(WIP) !game talk {entity}</b> - Talk to an entity</li> <li><b>(WIP) !game talk {entity}</b> - Talk to an entity</li>
<li><b>(WIP) !game fight {entity}</b> - Fight an entity</li> <li><b>(WIP) !game fight {entity}</b> - Fight an entity</li>
<li><b>(WIP) !game work {action}</b> - Start work</li> <li><b>(WIP) !game work {action}</b> - Start work</li>
@ -76,37 +98,38 @@ const onHelp = (_text: string, roomId: string) => {
}; };
const onStatus = (_text: string, roomId: string, sender: string) => { const onStatus = (_text: string, roomId: string, sender: string) => {
const player = getPlayerById(sender); const entity = getEntityByName(sender);
const race = getRace(player.race); const race = getRace(entity.race);
const location = getLocation(player.location); const location = getLocation(entity.location);
const level = getLevel(player.experience); const level = getLevel(entity.experience);
client.sendHtmlMessage( client.sendHtmlMessage(
roomId, roomId,
"", "",
`<ul> `<ul>
<li><b>Name:</b> ${player.name}</li> <li><b>Name:</b> ${entity.name}</li>
<li><b>Description:</b> ${player.description}</li> <li><b>Description:</b> ${entity.description}</li>
<li><b>Race:</b> ${race.name}</li> <li><b>Race:</b> ${race.name}</li>
<li><b>Location:</b> ${location.name}</li> <li><b>Location:</b> ${location.name}</li>
<li><b>Level:</b> ${level.level} - ${level.experienceInLevel}/${level.experienceToNextLevel} exp</li> <li><b>Level:</b> ${level.level} - ${level.experienceInLevel}/${level.experienceToNextLevel} exp</li>
<li><b>Vitality:</b> ${player.vitality}</li> <li><b>Health:</b> ${entity.health}/${getMaxHealth(entity)} (${getHealthPercentage(entity)}%)</li>
<li><b>Strength:</b> ${player.strength}</li> <li><b>Vitality:</b> ${entity.vitality}</li>
<li><b>Endurance:</b> ${player.endurance}</li> <li><b>Strength:</b> ${entity.strength}</li>
<li><b>Agility:</b> ${player.agility}</li> <li><b>Endurance:</b> ${entity.endurance}</li>
<li><b>Dexterity:</b> ${player.dexterity}</li> <li><b>Agility:</b> ${entity.agility}</li>
<li><b>Intelligence:</b> ${player.intelligence}</li> <li><b>Dexterity:</b> ${entity.dexterity}</li>
<li><b>Wisdom:</b> ${player.wisdom}</li> <li><b>Intelligence:</b> ${entity.intelligence}</li>
<li><b>Stealth:</b> ${player.stealth}</li> <li><b>Wisdom:</b> ${entity.wisdom}</li>
<li><b>Charisma:</b> ${player.charisma}</li> <li><b>Stealth:</b> ${entity.stealth}</li>
<li><b>Lockpicking:</b> ${player.lockpicking}</li> <li><b>Charisma:</b> ${entity.charisma}</li>
<li><b>Lockpicking:</b> ${entity.lockpicking}</li>
</ul>`, </ul>`,
); );
}; };
const onInventory = (text: string, roomId: string, sender: string) => { const onInventory = (text: string, roomId: string, sender: string) => {
const player = getPlayerById(sender); const player = getPlayer(sender);
const itemIndex = text.replace(`${gamePrefix} inventory `, "").trim(); const itemIndex = text.replace(`${gamePrefix} inventory `, "").trim();
const itemIndexInt = Number(itemIndex); 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 onLocation = (_text: string, roomId: string, sender: string) => {
const player = getPlayerById(sender); const player = getPlayer(sender);
const location = getLocation(player.location); const location = getLocation(player.location);
client.sendHtmlMessage( client.sendHtmlMessage(
roomId, roomId,
"", "",
`<ul> `<p>Location info:</p>
<li><b>Player:</b> ${player.name}</li> <ul>
<li><b>X:</b> ${location.X}</li> <li><b>X:</b> ${location.X}</li>
<li><b>Y:</b> ${location.Y}</li> <li><b>Y:</b> ${location.Y}</li>
<li><b>Location Name:</b> ${location.name}</li> <li><b>Location Name:</b> ${location.name}</li>
</ul> <li><b>Location Description:</b> ${location.description}</li>
<p>${location.description}</p>`, </ul>`,
); );
}; };
const onNearby = (_text: string, roomId: string, sender: string) => { const onNearby = (_text: string, roomId: string, sender: string) => {
const player = getPlayerById(sender); const player = getPlayer(sender);
const location = getLocation(player.location); const location = getLocation(player.location);
const parentLocation = getParentLocation(player.location);
const mapLocation = (locId: Location): string => { const mapLocation = (locId: Location): string => {
const locData = getLocation(locId); const locData = getLocation(locId);
const distance = getLocationDistance(location, locData); const distance = getLocationDistance(location, locData);
return `<li><b>${locData.name}</b> - ${distance.toFixed(1)}km - <i>${locData.description}</i></li>`; let distanceText = `${distance.toFixed(1)}km`;
if (locId === player.location) {
distanceText = `you are here`;
}
return `<li><b>${locData.name}</b> - ${distanceText} - <i>${locData.description}</i></li>`;
}; };
client.sendHtmlMessage( client.sendHtmlMessage(
roomId, roomId,
"", "",
`<p>There are ${location.childLocations.length} locations around you (${player.name})</p> `<p>There are ${location.childLocations.length + parentLocation.childLocations.length} locations around you (${player.name})</p>
<ul> <ul>
${location.childLocations.map(mapLocation).join("\n")} ${location.childLocations.map(mapLocation).join("\n")}
${getParentLocation(player.location).childLocations.map(mapLocation).join("\n")} ${parentLocation.childLocations.map(mapLocation).join("\n")}
</ul>`, </ul>`,
); );
}; };
const onTravel = (text: string, roomId: string, sender: string) => { const onTravel = (text: string, roomId: string, sender: string) => {
const player = getPlayerById(sender); const player = getPlayer(sender);
const locationName = text.replace(`${gamePrefix} travel `, "").trim(); const locationName = text.replace(`${gamePrefix} travel `, "").trim();
if (!existsLocation(locationName)) { if (!existsLocation(locationName)) {
@ -246,4 +275,44 @@ const onTravel = (text: string, roomId: string, sender: string) => {
startTravel(player, travelLocation.id, client, roomId); 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 `<li><b>${entity.name}</b> - ${getRace(entity.race).name}</li>`;
};
client.sendHtmlMessage(
roomId,
"",
`<p>Entities in ${location.name}:</p>
<ul>
${getNpcsAtLocation(player.location).map(mapEntity)}
</ul>
<p>Players in ${location.name}:</p>
<ul>
${getPlayersAtLocation(player.location).map(mapEntity)}
</ul>`,
);
};
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 }; export { registerModuleGame };

View file

@ -1,6 +1,15 @@
import { getUserNameById } from "../../helpers.js"; import { getUserNameById } from "../../helpers.js";
import { state } from "../../store/store.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 { itemShortsword } from "./structures/items.js";
import { Location } from "./structures/locations.js"; import { Location } from "./structures/locations.js";
import { Race, raceHuman, races, type IRace } from "./structures/races.js"; import { Race, raceHuman, races, type IRace } from "./structures/races.js";
@ -28,11 +37,36 @@ const createPlayer = (name: string): IPlayer => ({
lockpicking: 0, lockpicking: 0,
}); });
const getPlayerById = (userId: string): IPlayer => { const createNpcData = (npc: INPC): INPCData => ({
return getPlayer(getUserNameById(userId)); 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); const player = state.game.players.find((player) => player.name === name);
if (player) { if (player) {
return player; return player;
@ -44,6 +78,55 @@ const getPlayer = (name: string): IPlayer => {
return newPlayer; 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 getRace = (id: Race): IRace => {
const race = races.find((race) => race.id === id); const race = races.find((race) => race.id === id);
@ -69,8 +152,41 @@ const getLevel = (experience: number): ILevel => {
}; };
}; };
const getSpeed = (entity: IEntity) => { const getMaxHealth = (entity: IEntity): number => {
return 1 + Math.sqrt(entity.agility + entity.endurance) / 3; 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,
};

View file

@ -5,7 +5,7 @@ import {
locations, locations,
type ILocation, type ILocation,
} from "./structures/locations.js"; } from "./structures/locations.js";
import type { IPlayer } from "./structures/entities.js"; import type { IEntity, IPlayer } from "./structures/entities.js";
import { getSpeed } from "./entity.js"; import { getSpeed } from "./entity.js";
const existsLocation = (name: string): boolean => { const existsLocation = (name: string): boolean => {
@ -34,8 +34,24 @@ const getLocationDistance = (
locationA: ILocation, locationA: ILocation,
locationB: ILocation, locationB: ILocation,
): number => { ): number => {
const deltaX = Math.abs(locationA.X - locationB.X); const parentOfA = getParentLocation(locationA.id);
const deltaY = Math.abs(locationA.Y - locationB.Y); 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); const deltaPow = Math.pow(deltaX, 2) + Math.pow(deltaY, 2);
@ -59,6 +75,13 @@ const hasLocation = (
return childLocations.includes(locationChild); 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 = ( const getTravelTimeInHours = (
player: IPlayer, player: IPlayer,
locationA: ILocation, locationA: ILocation,
@ -115,6 +138,7 @@ export {
getLocationDistance, getLocationDistance,
getParentLocation, getParentLocation,
hasLocation, hasLocation,
entitiesShareLocation,
getTravelTimeInHours, getTravelTimeInHours,
getTravelTimeInMilliseconds, getTravelTimeInMilliseconds,
startTravel, startTravel,

View file

@ -36,6 +36,8 @@ export interface INPCData {
dead: boolean; dead: boolean;
} }
export type TFullNPC = INPC & INPCData;
export enum NPC { export enum NPC {
BECKY = "BECKY", BECKY = "BECKY",
} }