Add game
This commit is contained in:
parent
d36e98ad0b
commit
c071b286af
23 changed files with 713 additions and 11 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "aslobot-matrix",
|
"name": "aslobot-matrix",
|
||||||
"version": "1.2.3",
|
"version": "1.3.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "aslobot-matrix",
|
"name": "aslobot-matrix",
|
||||||
"version": "1.2.3",
|
"version": "1.3.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/genai": "^1.34.0",
|
"@google/genai": "^1.34.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "aslobot-matrix",
|
"name": "aslobot-matrix",
|
||||||
"version": "1.2.3",
|
"version": "1.3.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,18 @@ const getUserName = (user: IUser): string => {
|
||||||
return username;
|
return username;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getUserNameById = (userId: string): string => {
|
||||||
|
const userPattern = /@[a-zA-Z0-9]*/;
|
||||||
|
const match = userId.match(userPattern)?.at(0);
|
||||||
|
if (!match) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let username = match.replaceAll("@", "");
|
||||||
|
username = username.charAt(0).toUpperCase() + username.slice(1);
|
||||||
|
return username;
|
||||||
|
};
|
||||||
|
|
||||||
const getUserFromMention = (mention: string | undefined): IUser | undefined => {
|
const getUserFromMention = (mention: string | undefined): IUser | undefined => {
|
||||||
if (!mention) {
|
if (!mention) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
@ -139,6 +151,7 @@ export {
|
||||||
checkRoles,
|
checkRoles,
|
||||||
getLevel,
|
getLevel,
|
||||||
getUserName,
|
getUserName,
|
||||||
|
getUserNameById,
|
||||||
getUserFromMention,
|
getUserFromMention,
|
||||||
changePersonality,
|
changePersonality,
|
||||||
log,
|
log,
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ const onAI = async (
|
||||||
repliedMessage?: string,
|
repliedMessage?: string,
|
||||||
repliedSender?: string,
|
repliedSender?: string,
|
||||||
image?: Buffer<ArrayBuffer>,
|
image?: Buffer<ArrayBuffer>,
|
||||||
|
repliedImage?: Buffer<ArrayBuffer>,
|
||||||
) => {
|
) => {
|
||||||
if (text.startsWith(`${config.app.triggerPrefix}aileaderboard`)) {
|
if (text.startsWith(`${config.app.triggerPrefix}aileaderboard`)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -75,6 +76,7 @@ const onAI = async (
|
||||||
`${username}: ${textMod}`,
|
`${username}: ${textMod}`,
|
||||||
`${repliedUsername}: ${repliedMessage}`,
|
`${repliedUsername}: ${repliedMessage}`,
|
||||||
image,
|
image,
|
||||||
|
repliedImage,
|
||||||
);
|
);
|
||||||
|
|
||||||
user.aiCost += responseAI.tokens * prices.text;
|
user.aiCost += responseAI.tokens * prices.text;
|
||||||
|
|
|
||||||
153
src/modules/game/game.ts
Normal file
153
src/modules/game/game.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
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 {
|
||||||
|
getLocation,
|
||||||
|
getLocationDistance,
|
||||||
|
} from "../../services/game/location.js";
|
||||||
|
import { getLevel } from "../../services/game/game.js";
|
||||||
|
import {
|
||||||
|
getFullItemName,
|
||||||
|
type IItem,
|
||||||
|
type Location,
|
||||||
|
} from "../../services/game/index.js";
|
||||||
|
let client: MatrixClient;
|
||||||
|
|
||||||
|
const gamePrefix = `${config.app.triggerPrefix}game`;
|
||||||
|
|
||||||
|
const registerModuleGame = (
|
||||||
|
matrixClient: MatrixClient,
|
||||||
|
callbackStore: ICallbackStore,
|
||||||
|
) => {
|
||||||
|
client = matrixClient;
|
||||||
|
|
||||||
|
callbackStore.messageCallbacks.push({
|
||||||
|
startConditions: [`${gamePrefix} help`],
|
||||||
|
callbackFunc: onHelp,
|
||||||
|
});
|
||||||
|
callbackStore.messageCallbacks.push({
|
||||||
|
startConditions: [`${gamePrefix} status`],
|
||||||
|
callbackFunc: onStatus,
|
||||||
|
});
|
||||||
|
callbackStore.messageCallbacks.push({
|
||||||
|
startConditions: [`${gamePrefix} inventory`],
|
||||||
|
callbackFunc: onInventory,
|
||||||
|
});
|
||||||
|
callbackStore.messageCallbacks.push({
|
||||||
|
startConditions: [`${gamePrefix} location`],
|
||||||
|
callbackFunc: onLocation,
|
||||||
|
});
|
||||||
|
callbackStore.messageCallbacks.push({
|
||||||
|
startConditions: [`${gamePrefix} locations`],
|
||||||
|
callbackFunc: onLocations,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onHelp = (_text: string, roomId: string) => {
|
||||||
|
client.sendHtmlMessage(
|
||||||
|
roomId,
|
||||||
|
"",
|
||||||
|
`<ul>
|
||||||
|
<li><b>!game help</b> - Prints this help message</li>
|
||||||
|
<li><b>!game status</b> - Prints information about your character</li>
|
||||||
|
<li><b>!game inventory</b> - Shows your inventory</li>
|
||||||
|
<li><b>(WIP) !game inventory {index}</b> - Shows information about an item in your inventory</li>
|
||||||
|
<li><b>!game location</b> - Shows information about your current location</li>
|
||||||
|
<li><b>!game locations</b> - Shows nearby locations</li>
|
||||||
|
<li><b>(WIP) !game travel {location}</b> - Travel to a location</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 work {action}</b> - Start work</li>
|
||||||
|
</ul>`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onStatus = (_text: string, roomId: string, sender: string) => {
|
||||||
|
const player = getPlayerById(sender);
|
||||||
|
const race = getRace(player.race);
|
||||||
|
const location = getLocation(player.location);
|
||||||
|
|
||||||
|
const level = getLevel(player.experience);
|
||||||
|
|
||||||
|
client.sendHtmlMessage(
|
||||||
|
roomId,
|
||||||
|
"",
|
||||||
|
`<ul>
|
||||||
|
<li><b>Name:</b> ${player.name}</li>
|
||||||
|
<li><b>Description:</b> ${player.description}</li>
|
||||||
|
<li><b>Race:</b> ${race.name}</li>
|
||||||
|
<li><b>Location:</b> ${location.name}</li>
|
||||||
|
<li><b>Level:</b> ${level.level} - ${level.experienceInLevel}/${level.experienceToNextLevel}</li>
|
||||||
|
<li><b>Vitality:</b> ${player.vitality}</li>
|
||||||
|
<li><b>Strength:</b> ${player.strength}</li>
|
||||||
|
<li><b>Endurance:</b> ${player.endurance}</li>
|
||||||
|
<li><b>Agility:</b> ${player.agility}</li>
|
||||||
|
<li><b>Dexterity:</b> ${player.dexterity}</li>
|
||||||
|
<li><b>Intelligence:</b> ${player.intelligence}</li>
|
||||||
|
<li><b>Wisdom:</b> ${player.wisdom}</li>
|
||||||
|
<li><b>Stealth:</b> ${player.stealth}</li>
|
||||||
|
<li><b>Charisma:</b> ${player.charisma}</li>
|
||||||
|
<li><b>Lockpicking:</b> ${player.lockpicking}</li>
|
||||||
|
</ul>`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onInventory = (_text: string, roomId: string, sender: string) => {
|
||||||
|
const player = getPlayerById(sender);
|
||||||
|
|
||||||
|
const mapItem = (item: IItem, index: number): string => {
|
||||||
|
const fullName = getFullItemName(item);
|
||||||
|
|
||||||
|
return `<b>(${index})</b> ${fullName}, `;
|
||||||
|
};
|
||||||
|
|
||||||
|
client.sendHtmlMessage(
|
||||||
|
roomId,
|
||||||
|
"",
|
||||||
|
`<p>Your inventory (${player.name})</p>
|
||||||
|
<ul>
|
||||||
|
${player.inventory.items.map(mapItem)}
|
||||||
|
</ul>`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLocation = (_text: string, roomId: string, sender: string) => {
|
||||||
|
const player = getPlayerById(sender);
|
||||||
|
const location = getLocation(player.location);
|
||||||
|
|
||||||
|
client.sendHtmlMessage(
|
||||||
|
roomId,
|
||||||
|
"",
|
||||||
|
`<ul>
|
||||||
|
<li><b>Player:</b> ${player.name}</li>
|
||||||
|
<li><b>X:</b> ${location.X}</li>
|
||||||
|
<li><b>Y:</b> ${location.Y}</li>
|
||||||
|
<li><b>Location Name:</b> ${location.name}</li>
|
||||||
|
</ul>
|
||||||
|
<p>${location.description}</p>`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLocations = (_text: string, roomId: string, sender: string) => {
|
||||||
|
const player = getPlayerById(sender);
|
||||||
|
const location = getLocation(player.location);
|
||||||
|
|
||||||
|
const mapLocation = (locId: Location): string => {
|
||||||
|
const locData = getLocation(locId);
|
||||||
|
const distance = getLocationDistance(location, locData);
|
||||||
|
|
||||||
|
return `<li><b>${locData.name}</b> - ${distance.toFixed(1)}km - <i>${locData.description}</i></li>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
client.sendHtmlMessage(
|
||||||
|
roomId,
|
||||||
|
"",
|
||||||
|
`<p>There are ${location.childLocations.length} locations around you (${player.name})</p>
|
||||||
|
<ul>
|
||||||
|
${location.childLocations.map(mapLocation)}
|
||||||
|
</ul>`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { registerModuleGame };
|
||||||
1
src/modules/game/index.ts
Normal file
1
src/modules/game/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./game.js";
|
||||||
|
|
@ -10,6 +10,7 @@ import { registerModuleBase } from "./base/base.js";
|
||||||
import { registerModuleAdmin } from "./admin/admin.js";
|
import { registerModuleAdmin } from "./admin/admin.js";
|
||||||
import { registerModuleUser } from "./user/user.js";
|
import { registerModuleUser } from "./user/user.js";
|
||||||
import { registerModuleAI } from "./ai/ai.js";
|
import { registerModuleAI } from "./ai/ai.js";
|
||||||
|
import { registerModuleGame } from "./game/game.js";
|
||||||
import { checkRoles, getUserById, log } from "../helpers.js";
|
import { checkRoles, getUserById, log } from "../helpers.js";
|
||||||
import { onAnyMessage, onMissingRole } from "./global.js";
|
import { onAnyMessage, onMissingRole } from "./global.js";
|
||||||
import { config } from "../config.js";
|
import { config } from "../config.js";
|
||||||
|
|
@ -113,6 +114,7 @@ const registerModules = (client: MatrixClient) => {
|
||||||
const replyToId = relatesTo?.["m.in_reply_to"]?.event_id;
|
const replyToId = relatesTo?.["m.in_reply_to"]?.event_id;
|
||||||
let repliedMessage: string | undefined;
|
let repliedMessage: string | undefined;
|
||||||
let repliedSender: string | undefined;
|
let repliedSender: string | undefined;
|
||||||
|
let repliedImage: Buffer<ArrayBuffer> | undefined;
|
||||||
|
|
||||||
if (replyToId) {
|
if (replyToId) {
|
||||||
const repliedEvent = await client.fetchRoomEvent(roomId, replyToId);
|
const repliedEvent = await client.fetchRoomEvent(roomId, replyToId);
|
||||||
|
|
@ -125,6 +127,32 @@ const registerModules = (client: MatrixClient) => {
|
||||||
} else if (repliedContent.formatted_body) {
|
} else if (repliedContent.formatted_body) {
|
||||||
repliedMessage = repliedContent.formatted_body.toString();
|
repliedMessage = repliedContent.formatted_body.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (repliedContent.msgtype === MsgType.Image) {
|
||||||
|
if (typeof content.url === "string") {
|
||||||
|
const httpUrl = client.mxcUrlToHttp(
|
||||||
|
content.url,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
if (httpUrl) {
|
||||||
|
const imageResponse = await fetch(httpUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${client.getAccessToken()}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const arrayBuffer =
|
||||||
|
await imageResponse.arrayBuffer();
|
||||||
|
repliedImage = Buffer.from(
|
||||||
|
new Uint8Array(arrayBuffer),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,6 +179,7 @@ const registerModules = (client: MatrixClient) => {
|
||||||
repliedMessage,
|
repliedMessage,
|
||||||
repliedSender,
|
repliedSender,
|
||||||
image,
|
image,
|
||||||
|
repliedImage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -160,6 +189,7 @@ const registerModules = (client: MatrixClient) => {
|
||||||
registerModuleAdmin(client, callbacks);
|
registerModuleAdmin(client, callbacks);
|
||||||
registerModuleUser(client, callbacks);
|
registerModuleUser(client, callbacks);
|
||||||
registerModuleAI(client, callbacks);
|
registerModuleAI(client, callbacks);
|
||||||
|
registerModuleGame(client, callbacks);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { registerModules };
|
export { registerModules };
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ interface ICallback {
|
||||||
repliedMessage?: string,
|
repliedMessage?: string,
|
||||||
repliedSender?: string,
|
repliedSender?: string,
|
||||||
image?: Buffer<ArrayBuffer>,
|
image?: Buffer<ArrayBuffer>,
|
||||||
|
repliedImage?: Buffer<ArrayBuffer>,
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,26 @@ const getTextGemini = async (
|
||||||
input: string,
|
input: string,
|
||||||
oldInput?: string,
|
oldInput?: string,
|
||||||
inputImage?: Buffer<ArrayBuffer>,
|
inputImage?: Buffer<ArrayBuffer>,
|
||||||
|
oldInputImage?: Buffer<ArrayBuffer>,
|
||||||
): Promise<AIResponseText> => {
|
): Promise<AIResponseText> => {
|
||||||
log(`AI Text Request: ${input}`);
|
log(`AI Text Request: ${input}`);
|
||||||
|
|
||||||
const oldInputContent: Content = {
|
const oldInputContent: Content = oldInputImage
|
||||||
|
? {
|
||||||
|
role: "user",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: oldInput ?? "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inlineData: {
|
||||||
|
mimeType: "image/png",
|
||||||
|
data: Buffer.from(oldInputImage).toString("base64"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {
|
||||||
role: "user",
|
role: "user",
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
|
|
@ -34,6 +50,7 @@ const getTextGemini = async (
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const inputContent: Content = inputImage
|
const inputContent: Content = inputImage
|
||||||
? {
|
? {
|
||||||
role: "user",
|
role: "user",
|
||||||
|
|
|
||||||
52
src/services/game/entity.ts
Normal file
52
src/services/game/entity.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { getUserNameById } from "../../helpers.js";
|
||||||
|
import { state } from "../../store/store.js";
|
||||||
|
import type { IPlayer } 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";
|
||||||
|
|
||||||
|
const createPlayer = (name: string): IPlayer => ({
|
||||||
|
name: name,
|
||||||
|
description: "",
|
||||||
|
race: Race.HUMAN,
|
||||||
|
location: Location.FARLANDS,
|
||||||
|
inventory: {
|
||||||
|
items: [itemShortsword],
|
||||||
|
},
|
||||||
|
experience: 0,
|
||||||
|
health: 100,
|
||||||
|
vitality: 0,
|
||||||
|
strength: 0,
|
||||||
|
endurance: 0,
|
||||||
|
agility: 0,
|
||||||
|
dexterity: 0,
|
||||||
|
intelligence: 0,
|
||||||
|
wisdom: 0,
|
||||||
|
stealth: 0,
|
||||||
|
charisma: 0,
|
||||||
|
lockpicking: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPlayerById = (userId: string): IPlayer => {
|
||||||
|
return getPlayer(getUserNameById(userId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPlayer = (name: string): IPlayer => {
|
||||||
|
const player = state.game.players.find((player) => player.name === name);
|
||||||
|
if (player) {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPlayer = createPlayer(name);
|
||||||
|
state.game.players.push(newPlayer);
|
||||||
|
|
||||||
|
return newPlayer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRace = (id: Race): IRace => {
|
||||||
|
const race = races.find((race) => race.id === id);
|
||||||
|
|
||||||
|
return race ? race : raceHuman;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { createPlayer, getPlayerById, getPlayer, getRace };
|
||||||
22
src/services/game/game.ts
Normal file
22
src/services/game/game.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import type { ILevel } from "./types.js";
|
||||||
|
|
||||||
|
const getLevel = (experience: number): ILevel => {
|
||||||
|
let tmpExperience = experience;
|
||||||
|
let experienceToNextLevel = 50;
|
||||||
|
let level = 0;
|
||||||
|
|
||||||
|
while (tmpExperience >= experienceToNextLevel) {
|
||||||
|
level++;
|
||||||
|
tmpExperience -= experienceToNextLevel;
|
||||||
|
experienceToNextLevel = experienceToNextLevel *= 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
level: level,
|
||||||
|
totalExperience: Math.floor(experience),
|
||||||
|
experienceInLevel: Math.floor(tmpExperience),
|
||||||
|
experienceToNextLevel: Math.floor(experienceToNextLevel),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getLevel };
|
||||||
11
src/services/game/index.ts
Normal file
11
src/services/game/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export * from "./game.js";
|
||||||
|
export * from "./entity.js";
|
||||||
|
export * from "./location.js";
|
||||||
|
export * from "./item.js";
|
||||||
|
export * from "./types.js";
|
||||||
|
export * from "./structures/locations.js";
|
||||||
|
export * from "./structures/entities.js";
|
||||||
|
export * from "./structures/races.js";
|
||||||
|
export * from "./structures/items.js";
|
||||||
|
export * from "./structures/rarities.js";
|
||||||
|
export * from "./structures/quests.js";
|
||||||
23
src/services/game/item.ts
Normal file
23
src/services/game/item.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import type { IItem } from "./structures/items.js";
|
||||||
|
import {
|
||||||
|
rarities,
|
||||||
|
Rarity,
|
||||||
|
rarityCommon,
|
||||||
|
type IRarity,
|
||||||
|
} from "./structures/rarities.js";
|
||||||
|
|
||||||
|
const getFullItemName = (item: IItem): string => {
|
||||||
|
return `${getRarityName(item.rarity)} ${item.baseName}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRarityName = (id: Rarity): string => {
|
||||||
|
return getRarity(id).name;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRarity = (id: Rarity): IRarity => {
|
||||||
|
const rarity = rarities.find((rarity) => rarity.id === id);
|
||||||
|
|
||||||
|
return rarity ? rarity : rarityCommon;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getFullItemName, getRarityName, getRarity };
|
||||||
26
src/services/game/location.ts
Normal file
26
src/services/game/location.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import {
|
||||||
|
locationFarlands,
|
||||||
|
locations,
|
||||||
|
type ILocation,
|
||||||
|
type Location,
|
||||||
|
} from "./structures/locations.js";
|
||||||
|
|
||||||
|
const getLocation = (id: Location): ILocation => {
|
||||||
|
const location = locations.find((location) => location.id === id);
|
||||||
|
|
||||||
|
return location ? location : locationFarlands;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLocationDistance = (
|
||||||
|
locationA: ILocation,
|
||||||
|
locationB: ILocation,
|
||||||
|
): number => {
|
||||||
|
const deltaX = Math.abs(locationA.X - locationB.X);
|
||||||
|
const deltaY = Math.abs(locationA.Y - locationB.Y);
|
||||||
|
|
||||||
|
const deltaPow = Math.pow(deltaX, 2) + Math.pow(deltaY, 2);
|
||||||
|
|
||||||
|
return Math.sqrt(deltaPow);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getLocation, getLocationDistance };
|
||||||
72
src/services/game/structures/entities.ts
Normal file
72
src/services/game/structures/entities.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Location } from "./locations.js";
|
||||||
|
import { Race } from "./races.js";
|
||||||
|
import type { IInventory } from "../types.js";
|
||||||
|
|
||||||
|
export interface IEntity {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
race: Race;
|
||||||
|
location: Location;
|
||||||
|
inventory: IInventory;
|
||||||
|
experience: number;
|
||||||
|
vitality: number;
|
||||||
|
strength: number;
|
||||||
|
endurance: number;
|
||||||
|
agility: number;
|
||||||
|
dexterity: number;
|
||||||
|
intelligence: number;
|
||||||
|
wisdom: number;
|
||||||
|
stealth: number;
|
||||||
|
charisma: number;
|
||||||
|
lockpicking: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPlayer extends IEntity {
|
||||||
|
health: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INPC extends IEntity {
|
||||||
|
id: NPC;
|
||||||
|
type: NpcType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INPCData {
|
||||||
|
id: NPC;
|
||||||
|
health: number;
|
||||||
|
dead: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum NPC {
|
||||||
|
BECKY = "BECKY",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum NpcType {
|
||||||
|
QUEST_GIVER = "QUEST_GIVER",
|
||||||
|
PASSIVE = "PASSIVE",
|
||||||
|
AGGRESIVE = "AGGRESIVE",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const npcBecky: INPC = {
|
||||||
|
id: NPC.BECKY,
|
||||||
|
name: "Becky",
|
||||||
|
description: "A 50 meter tall giantess. Might be a bad idea to attack",
|
||||||
|
race: Race.GIANT,
|
||||||
|
location: Location.NIGHTROOT_FOREST,
|
||||||
|
inventory: {
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
experience: 10000,
|
||||||
|
vitality: 250,
|
||||||
|
strength: 250,
|
||||||
|
endurance: 50,
|
||||||
|
agility: 5,
|
||||||
|
dexterity: 10,
|
||||||
|
intelligence: 20,
|
||||||
|
wisdom: 30,
|
||||||
|
stealth: 0,
|
||||||
|
charisma: 5,
|
||||||
|
lockpicking: 50,
|
||||||
|
type: NpcType.QUEST_GIVER,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const npcs = [npcBecky];
|
||||||
59
src/services/game/structures/items.ts
Normal file
59
src/services/game/structures/items.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { Rarity } from "./rarities.js";
|
||||||
|
|
||||||
|
export interface IItem {
|
||||||
|
baseName: string;
|
||||||
|
description: string;
|
||||||
|
value: number;
|
||||||
|
durability: number;
|
||||||
|
type: ItemType;
|
||||||
|
rarity: Rarity;
|
||||||
|
stats: IStat[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStat {
|
||||||
|
abilityType?: Ability;
|
||||||
|
damageType?: DamageType;
|
||||||
|
damage: number;
|
||||||
|
defense: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ItemType {
|
||||||
|
ITEM = "ITEM",
|
||||||
|
CURRENCY = "CURRENCY",
|
||||||
|
WEAPON_1H = "WEAPON_1H",
|
||||||
|
WEAPON_2H = "WEAPON_2H",
|
||||||
|
SHIELD = "SHIELD",
|
||||||
|
CHEST = "CHEST",
|
||||||
|
LEGS = "LEGS",
|
||||||
|
HEAD = "HEAD",
|
||||||
|
BOOTS = "BOOTS",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DamageType {
|
||||||
|
PHYSICAL = "PHYSICAL",
|
||||||
|
ELEMENTAL = "ELEMENTAL",
|
||||||
|
ARCANE = "ARCANE",
|
||||||
|
PSYCHIC = "PSYCHIC",
|
||||||
|
POISON = "POISON",
|
||||||
|
RADIATION = "RADIATION",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Ability {
|
||||||
|
TELEPORT = "TELEPORT",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const itemShortsword: IItem = {
|
||||||
|
baseName: "Shortsword",
|
||||||
|
description: "A common sword",
|
||||||
|
value: 10,
|
||||||
|
durability: 100,
|
||||||
|
type: ItemType.WEAPON_1H,
|
||||||
|
rarity: Rarity.COMMON,
|
||||||
|
stats: [
|
||||||
|
{
|
||||||
|
damageType: DamageType.PHYSICAL,
|
||||||
|
damage: 10,
|
||||||
|
defense: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
97
src/services/game/structures/locations.ts
Normal file
97
src/services/game/structures/locations.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
export interface ILocation {
|
||||||
|
id: Location;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
X: number;
|
||||||
|
Y: number;
|
||||||
|
childLocations: Location[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILocationData {
|
||||||
|
id: Location;
|
||||||
|
destroyed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Location {
|
||||||
|
UNIVERSE = "UNIVERSE",
|
||||||
|
SOL = "SOL",
|
||||||
|
EARTH = "EARTH",
|
||||||
|
FARLANDS = "FARLANDS",
|
||||||
|
HIGHMERE = "HIGHMERE",
|
||||||
|
HIGHMERE_TAVERN = "HIGHMERE_TAVERN",
|
||||||
|
NIGHTROOT_FOREST = "NIGHTROOT_FOREST",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const locationUniverse: ILocation = {
|
||||||
|
id: Location.UNIVERSE,
|
||||||
|
name: "Universe",
|
||||||
|
description: "Everything",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
childLocations: [Location.SOL],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locationSol: ILocation = {
|
||||||
|
id: Location.SOL,
|
||||||
|
name: "Sol",
|
||||||
|
description: "Home system",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
childLocations: [Location.EARTH],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locationEarth: ILocation = {
|
||||||
|
id: Location.EARTH,
|
||||||
|
name: "Earth",
|
||||||
|
description: "Home planet",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
childLocations: [Location.FARLANDS],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locationFarlands: ILocation = {
|
||||||
|
id: Location.FARLANDS,
|
||||||
|
name: "Farlands",
|
||||||
|
description: "Large plains",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
childLocations: [Location.HIGHMERE, Location.NIGHTROOT_FOREST],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locationHighmere: ILocation = {
|
||||||
|
id: Location.HIGHMERE,
|
||||||
|
name: "Highmere",
|
||||||
|
description: "A large capital city of Farlands",
|
||||||
|
X: 5,
|
||||||
|
Y: 5,
|
||||||
|
childLocations: [Location.HIGHMERE_TAVERN],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locationHighmereTavern: ILocation = {
|
||||||
|
id: Location.HIGHMERE_TAVERN,
|
||||||
|
name: "Highmere Tavern",
|
||||||
|
description: "",
|
||||||
|
X: 0.3,
|
||||||
|
Y: 0.5,
|
||||||
|
childLocations: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locationNightrootForest: ILocation = {
|
||||||
|
id: Location.NIGHTROOT_FOREST,
|
||||||
|
name: "Nightroot Forest",
|
||||||
|
description:
|
||||||
|
"Forest full of tall trees through which barely any light shines",
|
||||||
|
X: 3,
|
||||||
|
Y: 6,
|
||||||
|
childLocations: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locations = [
|
||||||
|
locationUniverse,
|
||||||
|
locationSol,
|
||||||
|
locationEarth,
|
||||||
|
locationFarlands,
|
||||||
|
locationHighmere,
|
||||||
|
locationHighmereTavern,
|
||||||
|
locationNightrootForest,
|
||||||
|
];
|
||||||
10
src/services/game/structures/quests.ts
Normal file
10
src/services/game/structures/quests.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export interface IQuest {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
type: QuestType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum QuestType {
|
||||||
|
FETCH = "FETCH",
|
||||||
|
KILL = "KILL",
|
||||||
|
}
|
||||||
31
src/services/game/structures/races.ts
Normal file
31
src/services/game/structures/races.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
export interface IRace {
|
||||||
|
id: Race;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Race {
|
||||||
|
HUMAN = "HUMAN",
|
||||||
|
GIANT = "GIANT",
|
||||||
|
ELF = "ELF",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const raceHuman: IRace = {
|
||||||
|
id: Race.HUMAN,
|
||||||
|
name: "Human",
|
||||||
|
description: "Just a normal human",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const raceGiant: IRace = {
|
||||||
|
id: Race.GIANT,
|
||||||
|
name: "Giant",
|
||||||
|
description: "Basically a human, but of an extreme size and strength",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const raceElf: IRace = {
|
||||||
|
id: Race.ELF,
|
||||||
|
name: "Elf",
|
||||||
|
description: "A weird creature with pointy ears",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const races = [raceHuman, raceGiant, raceElf];
|
||||||
52
src/services/game/structures/rarities.ts
Normal file
52
src/services/game/structures/rarities.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
export interface IRarity {
|
||||||
|
id: Rarity;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Rarity {
|
||||||
|
COMMON = "COMMON",
|
||||||
|
UNCOMMON = "UNCOMMON",
|
||||||
|
RARE = "RARE",
|
||||||
|
VERY_RARE = "VERY_RARE",
|
||||||
|
LEGENDARY = "LEGENDARY",
|
||||||
|
MAGICAL = "MAGICAL",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const rarityCommon: IRarity = {
|
||||||
|
id: Rarity.COMMON,
|
||||||
|
name: "Common",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rarityUncommon: IRarity = {
|
||||||
|
id: Rarity.UNCOMMON,
|
||||||
|
name: "Uncommon",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rarityRare: IRarity = {
|
||||||
|
id: Rarity.RARE,
|
||||||
|
name: "Rare",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rarityVeryRare: IRarity = {
|
||||||
|
id: Rarity.VERY_RARE,
|
||||||
|
name: "Very Rare",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rarityLegendary: IRarity = {
|
||||||
|
id: Rarity.LEGENDARY,
|
||||||
|
name: "Legendary",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rarityMagical: IRarity = {
|
||||||
|
id: Rarity.MAGICAL,
|
||||||
|
name: "Magical",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rarities = [
|
||||||
|
rarityCommon,
|
||||||
|
rarityUncommon,
|
||||||
|
rarityRare,
|
||||||
|
rarityVeryRare,
|
||||||
|
rarityLegendary,
|
||||||
|
rarityMagical,
|
||||||
|
];
|
||||||
22
src/services/game/types.ts
Normal file
22
src/services/game/types.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import type { IItem } from "./structures/items.js";
|
||||||
|
import type { ILocationData } from "./structures/locations.js";
|
||||||
|
import type { INPCData, IPlayer } from "./structures/entities.js";
|
||||||
|
|
||||||
|
interface IGame {
|
||||||
|
players: IPlayer[];
|
||||||
|
npcs: INPCData[];
|
||||||
|
locations: ILocationData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IInventory {
|
||||||
|
items: IItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ILevel {
|
||||||
|
level: number;
|
||||||
|
totalExperience: number;
|
||||||
|
experienceInLevel: number;
|
||||||
|
experienceToNextLevel: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { type IGame, type IInventory, type ILevel };
|
||||||
|
|
@ -11,6 +11,11 @@ let state: IState = {
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
},
|
},
|
||||||
aiMemory: [],
|
aiMemory: [],
|
||||||
|
game: {
|
||||||
|
players: [],
|
||||||
|
npcs: [],
|
||||||
|
locations: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const load = () => {
|
const load = () => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
import type { IGame } from "../services/game/types.js";
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
users: IUser[];
|
users: IUser[];
|
||||||
personality: IPersonality;
|
personality: IPersonality;
|
||||||
aiMemory: string[];
|
aiMemory: string[];
|
||||||
|
game: IGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUser {
|
interface IUser {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue