Rework behaviors

This commit is contained in:
Hri7566 2024-10-15 05:01:21 -04:00
parent f6ca90329c
commit c73a4d2abf
26 changed files with 810 additions and 422 deletions

View File

@ -17450,7 +17450,10 @@ var TalkomaticBot = class extends import_node_events.EventEmitter {
async start() { async start() {
this.logger.info("Starting"); this.logger.info("Starting");
this.client.connect(); this.client.connect();
let data = await this.findChannel(this.config.channel.name) || await this.createChannel(this.config.channel.name, "private"); let data = await this.findChannel(this.config.channel.name) || await this.createChannel(
this.config.channel.name,
this.config.channel.type
);
this.logger.debug(data); this.logger.debug(data);
if (typeof data !== "undefined") { if (typeof data !== "undefined") {
try { try {

View File

@ -4,6 +4,7 @@
- lake - lake
- river - river
- sea - sea
- shop
hasSand: true hasSand: true
objects: [] objects: []
- id: lake - id: lake
@ -30,3 +31,11 @@
- river - river
hasSand: true hasSand: true
objects: [] objects: []
- id: shop
name: Shop
nearby:
- pond
- id: bed # /go sleep?
name: Bed
nearby:
- pond

View File

@ -1,2 +1,3 @@
- channel: - channel:
name: test/fishing name: test/fishing
type: public

62
src/api/behavior/bhv.d.ts vendored Normal file
View File

@ -0,0 +1,62 @@
interface IBehaviorValidResponse<State> {
success: true;
// shouldRemove: boolean;
// and?: string;
state: State;
}
interface IBehaviorErrorResponse {
success: false;
err: string;
}
type TBehaviorResponse<State> =
| IBehaviorValidResponse<State>
| IBehaviorErrorResponse;
type TBehaviorCallback<Context, State> = (
context: Context
) => Promise<TBehaviorResponse<State>>;
type TBehavior<Context, State> = Record<
string,
TBehaviorCallback<Context, State>
>;
type TBehaviorMap<Context, State> = Map<string, TBehavior<Context, State>>; //Record<string, TBehavior<C>>;
type TBehaviorNamespace = string;
type TBehaviorAction = keyof IBehaviorContextStateDefinitions;
type TBehaviorID = `${TBehaviorNamespace}:${TBehaviorAction}`;
interface IBehaviorDefinition<Context> {
id: string;
bhv: TBehavior<Context>;
}
declare interface IBehaviorContextStateDefinitions
extends Record<Record<{ context: unknown; state: unknown }>> {
eat: {
context: {
id: string;
part: IPart;
object: IObject;
user: User;
};
state: {
shouldRemove: boolean;
and?: string;
};
};
yeet: {
context: {
part: IPart;
};
state: {
shouldRemove: boolean;
text: string;
};
};
}

72
src/api/behavior/index.ts Normal file
View File

@ -0,0 +1,72 @@
import { logger } from "@server/commands/handler";
export const behaviorMap = new Map<string, TBehavior<unknown, unknown>>();
class BehaviorError extends Error {
constructor(...args: string[]) {
super(...args);
}
}
function splitBehaviorID(id: TBehaviorID) {
const args = id.split(":");
if (typeof args[0] == "undefined" || typeof args[1] == "undefined")
throw new BehaviorError(
"Incomplete behavior ID (should have exactly two segments)"
);
if (typeof args[0] !== "string" || typeof args[1] !== "string")
throw new BehaviorError(
`Invalid behavior ID (expected: string:string, got: ${typeof args[0]}:${typeof args[1]})`
);
if (typeof args[3] !== "undefined")
throw new BehaviorError("Invalid behavior ID: Too many segments");
return args;
}
export function registerBehavior<
K extends keyof IBehaviorContextStateDefinitions,
Context = IBehaviorContextStateDefinitions[K]["context"],
State = IBehaviorContextStateDefinitions[K]["state"]
>(id: TBehaviorID, behavior: TBehaviorCallback<Context, State>) {
try {
const args = splitBehaviorID(id);
const namespace = args[0];
const action = args[1];
if (!behaviorMap.has(namespace)) behaviorMap.set(namespace, {});
const set = behaviorMap.get(namespace);
if (!set)
throw new BehaviorError("Unable to resolve namespace to value");
set[action] = behavior as TBehaviorCallback<unknown, unknown>;
} catch (err) {
if (!(err instanceof BehaviorError)) err = "Unknown error";
throw new BehaviorError("Unable to register behavior: " + err);
}
}
export async function executeBehavior<
K extends keyof IBehaviorContextStateDefinitions,
Context = IBehaviorContextStateDefinitions[K]["context"],
State = IBehaviorContextStateDefinitions[K]["state"]
>(id: TBehaviorID, context: Context): Promise<TBehaviorResponse<State>> {
const args = splitBehaviorID(id);
const namespace = args[0];
const action = args[1];
const set = behaviorMap.get(namespace);
if (!set) throw new BehaviorError("Unable to resolve namespace to value");
const callback = set[action];
if (!callback)
return {
success: false,
err: "No callback defined for " + id
};
return (await callback(context)) as TBehaviorResponse<State>;
}

View File

@ -0,0 +1,76 @@
import { addBack } from "@server/backs";
import { CosmicColor } from "@util/CosmicColor";
import { registerBehavior } from "..";
registerBehavior<"eat">("burger:eat", async context => {
const r = Math.random();
// 2%
if (r < 0.02) {
const color = new CosmicColor("#E5B73B");
addBack(context.id, {
m: "color",
id: context.part.id,
color: color.toHexa()
});
return {
success: true,
state: {
shouldRemove: true,
and: `and it made him/her turn ${color
.getName()
.toLowerCase()}.`
}
};
} else {
return {
success: true,
state: {
shouldRemove: true
}
};
}
});
registerBehavior<"yeet">("burger:yeet", async context => {
const name = context.part.name;
const tossed = [
`Friend ${name} tossed the meaty burger. It skipped off the water like a pebble.`,
`Friend ${name} yeeted the slimy burger into the stratosphere. It came around the other side of the planet and hit them in the head.`,
`The hamburger gracefully soared through the air.`,
`In a majestic flight, the burger takes off like a plane and floats away into the horizon.`,
`A film crew and director watch ${name} toss a burger from afar. After a few months, the burger wins an Oscar.`,
`Friend ${name} flings a Junior Baconator into the sky with enough force to create a second moon.`,
`The burger launched like a rocket and reaches outer space. Suddenly, a ship full of Kerbonauts crashes into the patty, diverting it back to the planet.`,
`Friend ${name} tosses the burger. Suddenly, the burger is hit by a flying hot dog.`,
`The burger sprouts wings and floats away.`,
`Friend ${name} tosses a burger ${
Math.trunc(Math.random() * 1000) / 100
} inches in front of themselves.`,
`Friend ${name} winds up for a big throw and yeets the burger. After travelling the sky for a few seconds, it enters somebody else's car window.`,
`Friend ${name} tosses the burger. The local weather station reports falling food.`,
`The burger goes through a cloud shaped like ${name}'s head.`,
`After a long 10 minutes, the burger comes down from the ceiling.`,
`Friend ${name} tosses the burger like a frisbee and it lands on a nearby roof.`,
`Friend ${name} carelessly sends the burger into a passing seagull's mouth.`,
`Friend ${name} managed to deliver a burger perfectly into the window of a house.`,
`The burger hits the water and a shark fin suddenly appears.`,
`After ${name} hurls the burger, it lands onto a nearby grill.`,
`Friend ${name} flings a burger patty into the atmosphere.`,
`Friend ${name} throws a boomerang-shaped hamburger. It comes back and hits ${name} on the head.`,
`Friend ${name} tosses the burger.`,
`Friend ${name} tosses the burger into the air and it lands on their face.`,
`Friend ${name} tosses the burger to the other side of a rainbow.`
];
return {
success: true,
state: {
shouldRemove: true,
text: tossed[Math.floor(Math.random() * tossed.length)]
}
};
});

View File

@ -0,0 +1,39 @@
import { addBack } from "@server/backs";
import { CosmicColor } from "@util/CosmicColor";
import { registerBehavior } from "..";
registerBehavior<"eat">("fish:eat", async context => {
const r = Math.random();
// 50%
if (r < 0.5) {
const color = new CosmicColor(
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255)
);
addBack(context.id, {
m: "color",
id: context.part.id,
color: color.toHexa()
});
return {
success: true,
state: {
shouldRemove: true,
and: `and it made him/her turn ${color
.getName()
.toLowerCase()}.`
}
};
} else {
return {
success: true,
state: {
shouldRemove: true
}
};
}
});

View File

@ -0,0 +1,14 @@
import { incrementFishingChance } from "@server/fish/fishers";
import { registerBehavior } from "..";
registerBehavior<"eat">("kekklefruit:eat", async context => {
const test = await incrementFishingChance(context.user.id);
return {
success: true,
state: {
shouldRemove: true,
and: "and got a temporary fishing boost."
}
};
});

View File

@ -0,0 +1,11 @@
import { registerBehavior } from "..";
registerBehavior<"yeet">("sand:yeet", async context => {
return {
success: true,
state: {
shouldRemove: false,
text: `No, ${context.part.name}, don't yeet sand.`
}
};
});

View File

@ -0,0 +1,5 @@
export function registerBehaviors() {
require("./objects/fish");
require("./objects/kekklefruit");
require("./objects/burger");
}

View File

@ -1,6 +1,7 @@
import type { ReadlineCommand } from "./ReadlineCommand"; import type { ReadlineCommand } from "./ReadlineCommand";
import { deltoken } from "./commands/deltoken"; import { deltoken } from "./commands/deltoken";
import { gentoken } from "./commands/gentoken"; import { gentoken } from "./commands/gentoken";
import { grow_fruit } from "./commands/grow_fruit";
import { help } from "./commands/help"; import { help } from "./commands/help";
import { lstoken } from "./commands/lstoken"; import { lstoken } from "./commands/lstoken";
import { stop } from "./commands/stop"; import { stop } from "./commands/stop";
@ -12,3 +13,4 @@ readlineCommands.push(gentoken);
readlineCommands.push(deltoken); readlineCommands.push(deltoken);
readlineCommands.push(lstoken); readlineCommands.push(lstoken);
readlineCommands.push(stop); readlineCommands.push(stop);
readlineCommands.push(grow_fruit);

View File

@ -0,0 +1,20 @@
import { getAllTokens } from "@server/data/token";
import { ReadlineCommand } from "../ReadlineCommand";
import { growFruit } from "@server/fish/tree";
export const grow_fruit = new ReadlineCommand(
"grow_fruit",
["grow_fruit", "grow", "grow_kekklefruit"],
"Grow kekklefruit on the tree",
"grow_fruit <number>",
async line => {
try {
const args = line.split(" ");
const num = parseInt(args[1], 10);
await growFruit(num);
return `grew ${num} kekklefruit`;
} catch (err) {
return "bad";
}
}
);

View File

@ -0,0 +1,46 @@
import type { User } from "@prisma/client";
import Command from "./Command";
import { behaviorMap, executeBehavior } from "@server/behavior";
import { logger } from "./handler";
export class BehaviorCommand extends Command {
constructor(
public id: string,
public aliases: string[],
public description: string,
public usage: string,
public permissionNode: string,
callback: TCommandCallbackWithSelf<User, BehaviorCommand>,
public visible: boolean = true
) {
super(
id,
aliases,
description,
usage,
permissionNode,
props => callback(props, this),
visible
);
}
public async behave<
K extends keyof IBehaviorContextStateDefinitions,
C = IBehaviorContextStateDefinitions[K]["context"],
S = IBehaviorContextStateDefinitions[K]["state"]
>(context: C, objectId: string, defaultCallback: TBehaviorCallback<C, S>) {
const key = `${objectId}:${this.id}` as TBehaviorID;
let hasBehavior = false;
const container = behaviorMap.get(objectId);
if (container) hasBehavior = typeof container[this.id] === "function";
if (hasBehavior) {
return await executeBehavior(key, context);
} else {
return (await defaultCallback(context)) as TBehaviorResponse<S>;
}
}
}
export default BehaviorCommand;

View File

@ -3,7 +3,7 @@ import Command from "@server/commands/Command";
export const info = new Command( export const info = new Command(
"info", "info",
["info"], ["info"],
"Get your own user ID", "Show information about the bot",
"info", "info",
"command.general.info", "command.general.info",
async ({ id, command, args, prefix, part, user }) => { async ({ id, command, args, prefix, part, user }) => {

View File

@ -13,10 +13,10 @@ import { eat } from "./inventory/eat";
import { sack } from "./inventory/sack"; import { sack } from "./inventory/sack";
import { reel } from "./fishing/reel"; import { reel } from "./fishing/reel";
import { memory } from "./util/mem"; import { memory } from "./util/mem";
import { pokemon } from "./inventory/pokemon"; import { pokemon } from "./pokemon/pokemon";
import { color } from "./general/color"; import { color } from "./general/color";
import { autofish } from "./util/autofish"; import { autofish } from "./util/autofish";
import { pokedex } from "./util/pokedex"; import { pokedex } from "./pokemon/pokedex";
import { myid } from "./general/myid"; import { myid } from "./general/myid";
import { yeet } from "./inventory/yeet"; import { yeet } from "./inventory/yeet";
import { tree } from "./fishing/tree"; import { tree } from "./fishing/tree";
@ -63,7 +63,7 @@ commandGroups.push(inventoryGroup);
const pokemonGroup: ICommandGroup = { const pokemonGroup: ICommandGroup = {
id: "pokemon", id: "pokemon",
displayName: "Pokémon", displayName: "Pokémon",
commands: [daily, pokedex] commands: [daily, pokemon, pokedex]
}; };
commandGroups.push(pokemonGroup); commandGroups.push(pokemonGroup);

View File

@ -1,16 +1,16 @@
import Command from "@server/commands/Command"; import BehaviorCommand from "@server/commands/BehaviorCommand";
import { logger } from "@server/commands/handler"; import { logger } from "@server/commands/handler";
import { getInventory, updateInventory } from "@server/data/inventory"; import { getInventory, updateInventory } from "@server/data/inventory";
import { findItemByNameFuzzy, removeItem } from "@server/items"; import { findItemByNameFuzzy, removeItem } from "@server/items";
import { itemBehaviorMap, runBehavior } from "@server/items/behavior"; // import { itemBehaviorMap, runBehavior } from "@server/items/behavior";
export const eat = new Command( export const eat = new BehaviorCommand(
"eat", "eat",
["eat", "oot"], ["eat", "oot"],
"Eat literally anything you have (except non-fish animals)", "Eat literally anything you have (except non-fish animals)",
"eat <something>", "eat <something>",
"command.inventory.eat", "command.inventory.eat",
async props => { async (props, self) => {
const { args, prefix, part, user } = props; const { args, prefix, part, user } = props;
// const eating = args[0]; // const eating = args[0];
const eating = args.join(" "); const eating = args.join(" ");
@ -33,18 +33,48 @@ export const eat = new Command(
let thingy = foundObject.id; let thingy = foundObject.id;
if (foundObject.objtype == "fish") thingy = "fish"; if (foundObject.objtype == "fish") thingy = "fish";
const bhv = itemBehaviorMap[thingy]; // const bhv = itemBehaviorMap[thingy];
// let res;
// if (bhv) {
// if (!bhv["eat"]) return `You can't eat the ${foundObject.name}.`;
// res = await runBehavior(thingy, "eat", foundObject, props);
// // Check if response had an error, and populate our state that way
// if (res.success) shouldRemove = res.state.shouldRemove;
// else shouldRemove = false;
// } else {
// shouldRemove = true;
// }
let res; let res;
let bhvNamespace = foundObject.id;
if (bhv) { if (foundObject.objtype == "fish") {
if (!bhv["eat"]) return `You can't eat the ${foundObject.name}.`; bhvNamespace = "fish";
res = await runBehavior(thingy, "eat", foundObject, props);
shouldRemove = res.shouldRemove;
} else {
shouldRemove = true;
} }
res = await self.behave<"eat">(
{ id: props.id, part, object: foundObject, user },
bhvNamespace,
async () => {
shouldRemove = true;
return {
success: true,
state: {
shouldRemove: true
}
};
}
);
if (!res) throw new Error(`Unable to eat fish: no behavior result`);
if (res.success) shouldRemove = res.state.shouldRemove;
else shouldRemove = false;
if (shouldRemove) { if (shouldRemove) {
if (foundObject.objtype == "fish") { if (foundObject.objtype == "fish") {
removeItem(inventory.fishSack, foundObject); removeItem(inventory.fishSack, foundObject);
@ -55,15 +85,22 @@ export const eat = new Command(
await updateInventory(inventory); await updateInventory(inventory);
} }
if (!res.success) {
return `You broke the bot trying to ${prefix}eat the ${foundObject.name}. Congratulations.`;
}
const state =
res.state as IBehaviorContextStateDefinitions["eat"]["state"];
if (foundObject.id == "sand") { if (foundObject.id == "sand") {
if (res && res.and) { if (res && res.success && typeof state.and !== "undefined") {
return `Our friend ${part.name} ate of his/her ${foundObject.name} ${res.and}`; return `Our friend ${part.name} ate of his/her ${foundObject.name} ${state.and}`;
} else { } else {
return `Our friend ${part.name} ate of his/her ${foundObject.name}.`; return `Our friend ${part.name} ate of his/her ${foundObject.name}.`;
} }
} else { } else {
if (res && res.and) { if (res && res.success && typeof state.and !== "undefined") {
return `Our friend ${part.name} ate his/her ${foundObject.name} ${res.and}`; return `Our friend ${part.name} ate his/her ${foundObject.name} ${state.and}`;
} else { } else {
return `Our friend ${part.name} ate his/her ${foundObject.name}.`; return `Our friend ${part.name} ate his/her ${foundObject.name}.`;
} }

View File

@ -1,23 +1,20 @@
import { addBack } from "@server/backs"; import BehaviorCommand from "@server/commands/BehaviorCommand";
import Command from "@server/commands/Command";
import { logger } from "@server/commands/handler"; import { logger } from "@server/commands/handler";
import { getInventory, updateInventory } from "@server/data/inventory"; import { getInventory, updateInventory } from "@server/data/inventory";
import prisma from "@server/data/prisma";
import { getUser } from "@server/data/user"; import { getUser } from "@server/data/user";
import { getSizeString } from "@server/fish/fish"; import { getSizeString } from "@server/fish/fish";
import { fishers, getFishing } from "@server/fish/fishers"; import { fishers } from "@server/fish/fishers";
import { locations } from "@server/fish/locations"; import { locations } from "@server/fish/locations";
import { addItem } from "@server/items"; import { addItem, findItemByNameFuzzy, removeItem } from "@server/items";
import { CosmicColor } from "@util/CosmicColor";
export const yeet = new Command( export const yeet = new BehaviorCommand(
"yeet", "yeet",
["yeet", "yoot"], ["yeet", "yoot"],
"Yeet literally anything you have (except non-fish animals)", "Yeet literally anything you have (except non-fish animals)",
"yeet <something>", "yeet <something>",
"command.inventory.yeet", "command.inventory.yeet",
async ({ id, command, args, prefix, part, user }) => { async ({ id, command, args, prefix, part, user }, self) => {
const yeeting = args[0]; const yeeting = args.join(" ");
if (!yeeting) return `What do you want to ${prefix}yeet?`; if (!yeeting) return `What do you want to ${prefix}yeet?`;
const inventory = await getInventory(user.inventoryId); const inventory = await getInventory(user.inventoryId);
@ -26,77 +23,37 @@ export const yeet = new Command(
let foundObject: IObject | undefined; let foundObject: IObject | undefined;
let tryKekGen = false; let tryKekGen = false;
let i = 0; let i = 0;
for (const item of inventory.items as unknown as IItem[]) {
if (!item.name.toLowerCase().includes(yeeting.toLowerCase())) {
i++;
continue;
}
foundObject = item;
let shouldRemove = false; let shouldRemove = false;
if (typeof item.count !== "undefined") { foundObject =
if (item.count > 1) { findItemByNameFuzzy(inventory.items, yeeting) ||
shouldRemove = false; findItemByNameFuzzy(inventory.fishSack, yeeting);
((inventory.items as TInventoryItems)[i].count as number)--;
} else {
shouldRemove = true;
}
} else {
shouldRemove = true;
}
if (shouldRemove && item.id !== "sand")
(inventory.items as TInventoryItems).splice(i, 1);
break;
}
i = 0;
for (const fish of inventory.fishSack as TFishSack) {
if (!fish.name.toLowerCase().includes(yeeting.toLowerCase())) {
i++;
continue;
}
foundObject = fish;
let shouldRemove = false;
if (typeof fish.count !== "undefined") {
if (fish.count > 1) {
shouldRemove = false;
((inventory.fishSack as TFishSack)[i].count as number)--;
} else {
shouldRemove = true;
}
} else {
shouldRemove = true;
}
if (shouldRemove) (inventory.fishSack as TFishSack).splice(i, 1);
break;
}
if (!foundObject) return `You don't have "${yeeting}" to yeet.`; if (!foundObject) return `You don't have "${yeeting}" to yeet.`;
let bhvNamespace = foundObject.id;
let output = `Friend ${part.name} tossed his/her ${foundObject.name}.`;
if (foundObject.objtype == "fish") { if (foundObject.objtype == "fish") {
bhvNamespace = "fish";
tryKekGen = true; tryKekGen = true;
} }
await updateInventory(inventory); const res = await self.behave<"yeet">(
{
if (foundObject.id == "sand") { part
return `No, ${ },
part.name foundObject.id,
}, don't yeet ${foundObject.name.toLowerCase()}.`; async ctx => {
} else { logger.debug("stuff");
if (tryKekGen) {
// 15%
if (Math.random() < 0.15) { if (Math.random() < 0.15) {
const randomFisher = const randomFisher =
Object.values(fishers)[ Object.values(fishers)[
Math.floor( Math.floor(
Math.random() * Object.values(fishers).length Math.random() *
Object.values(fishers).length
) )
]; ];
@ -192,24 +149,47 @@ export const yeet = new Command(
"It got hung in his/her shirt and he/she flung it out onto the ground and it was quite a silly scene.", "It got hung in his/her shirt and he/she flung it out onto the ground and it was quite a silly scene.",
`It scooted across his/her head before rebounding off onto the ground nearby. The ${ `It scooted across his/her head before rebounding off onto the ground nearby. The ${
itemAdjective[ itemAdjective[
Math.floor(Math.random() * itemAdjective.length) Math.floor(
Math.random() * itemAdjective.length
)
] ]
} residue was left behind in ${target}'s hair.` } residue was left behind in ${target}'s hair.`
]; ];
return `Friend ${part.name}'s ${ return {
success: true,
state: {
shouldRemove: true,
text: `Friend ${part.name}'s ${
handsAdjective[ handsAdjective[
Math.floor(Math.random() * handsAdjective.length) Math.floor(
Math.random() *
handsAdjective.length
)
]
} hands grabbed his/her ${
foundObject.name
} and ${
pastTense[
Math.floor(
Math.random() * pastTense.length
)
] ]
} hands grabbed his/her ${foundObject.name} and ${
pastTense[Math.floor(Math.random() * pastTense.length)]
} it ${ } it ${
presentTense[ presentTense[
Math.floor(Math.random() * presentTense.length) Math.floor(
Math.random() * presentTense.length
)
] ]
} ${ending[Math.floor(Math.random() * ending.length)]} ${ } ${
ps[Math.floor(Math.random() * ps.length)] ending[
}`; Math.floor(
Math.random() * ending.length
)
]
} ${ps[Math.floor(Math.random() * ps.length)]}`
}
};
} }
if (Math.random() < 0.15) { if (Math.random() < 0.15) {
@ -221,11 +201,22 @@ export const yeet = new Command(
let fish = foundObject.name; let fish = foundObject.name;
let name = part.name; let name = part.name;
const loc = locations.find(loc => loc.id == inventory.location); const loc = locations.find(
loc => loc.id == inventory.location
);
if (!loc) if (!loc)
return `Friend ${part.name} carelessly hurled their ${foundObject.name} into the void.`; return {
success: true,
state: {
shouldRemove: true,
text: `Friend ${part.name} carelessly hurled their ${foundObject.name} into the void.`
}
};
addItem(locations[locations.indexOf(loc)].objects, foundObject); addItem(
locations[locations.indexOf(loc)].objects,
foundObject
);
let kekNames = [ let kekNames = [
"kek of good fortune", "kek of good fortune",
@ -239,14 +230,16 @@ export const yeet = new Command(
addItem(locations[locations.indexOf(loc)].objects, { addItem(locations[locations.indexOf(loc)].objects, {
id: "kekklefruit", id: "kekklefruit",
name: kekNames[Math.floor(Math.random() * kekNames.length)], name: kekNames[
Math.floor(Math.random() * kekNames.length)
],
objtype: "item", objtype: "item",
count: 1, count: 1,
emoji: "🍍" emoji: "🍍"
}); });
// transcribed from the old code // transcribed from the old code
let yeets = [ const yeets = [
"The " + "The " +
size + size +
" " + " " +
@ -273,7 +266,15 @@ export const yeet = new Command(
" if you still want it after that." " if you still want it after that."
]; ];
return yeets[Math.floor(Math.random() * yeets.length)]; return {
success: true,
state: {
shouldRemove: true,
text: yeets[
Math.floor(Math.random() * yeets.length)
]
}
};
} }
if (Math.random() < 0.4) { if (Math.random() < 0.4) {
@ -289,10 +290,47 @@ export const yeet = new Command(
" It's resting at the edge of the water where you can /take it." " It's resting at the edge of the water where you can /take it."
]; ];
return yeets[Math.floor(Math.random() * yeets.length)]; return {
success: true,
state: {
shouldRemove: true,
text: yeets[
Math.floor(Math.random() * yeets.length)
]
}
};
}
} }
return `Friend ${part.name} tossed his/her ${foundObject.name}.`; return {
success: true,
state: {
shouldRemove: true,
text: output
} }
};
}
);
if (res.success == true) {
const state =
res.state as IBehaviorContextStateDefinitions["yeet"]["state"];
shouldRemove = state.shouldRemove;
output = state.text;
}
if (shouldRemove) {
if (foundObject.objtype == "fish") {
removeItem(inventory.fishSack, foundObject);
} else if (foundObject.objtype == "item") {
removeItem(inventory.items, foundObject);
}
await updateInventory(inventory);
}
await updateInventory(inventory);
return output;
} }
); );

View File

@ -2,12 +2,14 @@ import { startAutorestart } from "@util/autorestart";
import { startFisherTick } from "./fish/fishers"; import { startFisherTick } from "./fish/fishers";
import { startObjectTimers } from "./fish/locations"; import { startObjectTimers } from "./fish/locations";
import { initTree } from "./fish/tree"; import { initTree } from "./fish/tree";
import { loadDefaultBehaviors } from "./items/behavior/defaults"; import { registerBehaviors } from "./behavior/register";
// import { loadDefaultBehaviors } from "./items/behavior/defaults";
startObjectTimers(); startObjectTimers();
await startFisherTick(); await startFisherTick();
await initTree(); await initTree();
loadDefaultBehaviors(); // loadDefaultBehaviors();
registerBehaviors();
require("./api/server"); require("./api/server");
require("./cli/readline"); require("./cli/readline");

View File

@ -1,13 +0,0 @@
import { addItemBehavior } from ".";
import { fish } from "./items/fish";
import { kekklefruit } from "./items/kekklefruit";
export function loadDefaultBehaviors() {
const list: IBehaviorDefinition[] = [fish, kekklefruit];
for (const item of list) {
for (const key of Object.keys(item.bhv)) {
addItemBehavior(item.id, key, item.bhv[key]);
}
}
}

View File

@ -1,26 +0,0 @@
export const itemBehaviorMap: TBehaviorMap = {};
export function addItemBehavior(
itemID: string,
bhvID: string,
bhv: TBehaviorCallback
) {
if (!itemBehaviorMap[itemID]) itemBehaviorMap[itemID] = {};
itemBehaviorMap[itemID][bhvID] = bhv;
}
export async function runBehavior(
itemID: string,
bhvID: string,
obj: IObject,
props: IContextProps
): Promise<IBehaviorResponse> {
const callback = itemBehaviorMap[itemID][bhvID];
if (!callback)
return {
success: false,
err: "No callback",
shouldRemove: false
};
return await callback(obj, props);
}

View File

@ -1,41 +0,0 @@
import { addBack } from "@server/backs";
import { CosmicColor } from "@util/CosmicColor";
export const fish: IBehaviorDefinition = {
id: "fish",
bhv: {
async eat(obj, props) {
const r = Math.random();
const fish = obj as IFish;
// 50%
if (r < 0.5) {
const color = new CosmicColor(
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255)
);
addBack(props.id, {
m: "color",
id: props.part.id,
color: color.toHexa()
});
return {
success: true,
shouldRemove: true,
and: `and it made him/her turn ${color
.getName()
.toLowerCase()}.`
};
} else {
return {
success: true,
shouldRemove: true
};
}
}
}
};

View File

@ -1,16 +0,0 @@
import { incrementFishingChance } from "@server/fish/fishers";
export const kekklefruit: IBehaviorDefinition = {
id: "kekklefruit",
bhv: {
async eat(obj, props) {
const test = await incrementFishingChance(props.user.id);
return {
success: true,
shouldRemove: true,
and: "and got a temporary fishing boost."
};
}
}
};

31
src/util/types.d.ts vendored
View File

@ -8,7 +8,7 @@ interface ICommandResponse {
response: string; response: string;
} }
interface IContextProps { interface ICommandContextProps {
id: string; id: string;
channel: string; channel: string;
command: string; command: string;
@ -19,7 +19,14 @@ interface IContextProps {
isDM: boolean; isDM: boolean;
} }
type TCommandCallback<User> = (props: IContextProps) => Promise<string | void>; type TCommandCallback<User> = (
props: ICommandContextProps
) => Promise<string | void>;
type TCommandCallbackWithSelf<User, T> = (
props: ICommandContextProps,
self: T
) => Promise<string | void>;
interface ICountComponent { interface ICountComponent {
count: number; count: number;
@ -139,26 +146,6 @@ interface IGroup {
permissions: string[]; permissions: string[];
} }
interface IBehaviorResponse {
success: boolean;
err?: string;
shouldRemove: boolean;
and?: string;
}
type TBehaviorCallback = (
obj: IObject,
props: IContextProps
) => Promise<IBehaviorResponse>;
type TBehavior = Record<string, TBehaviorCallback>;
type TBehaviorMap = Record<string, TBehavior>;
interface IBehaviorDefinition {
id: string;
bhv: TBehavior;
}
interface IFishingChance { interface IFishingChance {
chance: number; chance: number;
t: number; t: number;

View File

@ -0,0 +1,60 @@
import { executeBehavior, registerBehavior } from "@server/behavior";
import { test, expect } from "bun:test";
test("Behavior registering works", async () => {
try {
registerBehavior("fish:eat", async ctx => {
return {
success: true,
state: {
shouldRemove: true
}
};
});
registerBehavior("hamburger:yeet", async ctx => {
return {
success: true,
state: {
shouldRemove: false,
and: "the hamburger rolled away"
}
};
});
} catch (err) {
expect(err).toBeUndefined();
}
});
test("Behavior execution is correct", async () => {
registerBehavior<"eat", { cooked: boolean }, { shouldRemove: boolean }>(
"hamburger:eat",
async ctx => {
if (ctx.cooked === false) {
return {
success: false,
err: "the hamburgers are not cooked yet"
};
} else {
return {
success: true,
state: {
shouldRemove: true
}
};
}
}
);
let response = await executeBehavior("hamburger:eat", {
cooked: false
});
expect(response.success).toBeFalse();
response = await executeBehavior("hamburger:eat", {
cooked: true
});
expect(response.success).toBeTrue();
});