Rework behaviors
This commit is contained in:
parent
f6ca90329c
commit
c73a4d2abf
|
@ -17450,7 +17450,10 @@ var TalkomaticBot = class extends import_node_events.EventEmitter {
|
|||
async start() {
|
||||
this.logger.info("Starting");
|
||||
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);
|
||||
if (typeof data !== "undefined") {
|
||||
try {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
- lake
|
||||
- river
|
||||
- sea
|
||||
- shop
|
||||
hasSand: true
|
||||
objects: []
|
||||
- id: lake
|
||||
|
@ -30,3 +31,11 @@
|
|||
- river
|
||||
hasSand: true
|
||||
objects: []
|
||||
- id: shop
|
||||
name: Shop
|
||||
nearby:
|
||||
- pond
|
||||
- id: bed # /go sleep?
|
||||
name: Bed
|
||||
nearby:
|
||||
- pond
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
- channel:
|
||||
name: test/fishing
|
||||
type: public
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -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)]
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
|
@ -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."
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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.`
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
export function registerBehaviors() {
|
||||
require("./objects/fish");
|
||||
require("./objects/kekklefruit");
|
||||
require("./objects/burger");
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import type { ReadlineCommand } from "./ReadlineCommand";
|
||||
import { deltoken } from "./commands/deltoken";
|
||||
import { gentoken } from "./commands/gentoken";
|
||||
import { grow_fruit } from "./commands/grow_fruit";
|
||||
import { help } from "./commands/help";
|
||||
import { lstoken } from "./commands/lstoken";
|
||||
import { stop } from "./commands/stop";
|
||||
|
@ -12,3 +13,4 @@ readlineCommands.push(gentoken);
|
|||
readlineCommands.push(deltoken);
|
||||
readlineCommands.push(lstoken);
|
||||
readlineCommands.push(stop);
|
||||
readlineCommands.push(grow_fruit);
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
);
|
|
@ -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;
|
|
@ -3,7 +3,7 @@ import Command from "@server/commands/Command";
|
|||
export const info = new Command(
|
||||
"info",
|
||||
["info"],
|
||||
"Get your own user ID",
|
||||
"Show information about the bot",
|
||||
"info",
|
||||
"command.general.info",
|
||||
async ({ id, command, args, prefix, part, user }) => {
|
||||
|
|
|
@ -13,10 +13,10 @@ import { eat } from "./inventory/eat";
|
|||
import { sack } from "./inventory/sack";
|
||||
import { reel } from "./fishing/reel";
|
||||
import { memory } from "./util/mem";
|
||||
import { pokemon } from "./inventory/pokemon";
|
||||
import { pokemon } from "./pokemon/pokemon";
|
||||
import { color } from "./general/color";
|
||||
import { autofish } from "./util/autofish";
|
||||
import { pokedex } from "./util/pokedex";
|
||||
import { pokedex } from "./pokemon/pokedex";
|
||||
import { myid } from "./general/myid";
|
||||
import { yeet } from "./inventory/yeet";
|
||||
import { tree } from "./fishing/tree";
|
||||
|
@ -63,7 +63,7 @@ commandGroups.push(inventoryGroup);
|
|||
const pokemonGroup: ICommandGroup = {
|
||||
id: "pokemon",
|
||||
displayName: "Pokémon",
|
||||
commands: [daily, pokedex]
|
||||
commands: [daily, pokemon, pokedex]
|
||||
};
|
||||
|
||||
commandGroups.push(pokemonGroup);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import Command from "@server/commands/Command";
|
||||
import BehaviorCommand from "@server/commands/BehaviorCommand";
|
||||
import { logger } from "@server/commands/handler";
|
||||
import { getInventory, updateInventory } from "@server/data/inventory";
|
||||
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", "oot"],
|
||||
"Eat literally anything you have (except non-fish animals)",
|
||||
"eat <something>",
|
||||
"command.inventory.eat",
|
||||
async props => {
|
||||
async (props, self) => {
|
||||
const { args, prefix, part, user } = props;
|
||||
// const eating = args[0];
|
||||
const eating = args.join(" ");
|
||||
|
@ -33,18 +33,48 @@ export const eat = new Command(
|
|||
let thingy = foundObject.id;
|
||||
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 bhvNamespace = foundObject.id;
|
||||
|
||||
if (bhv) {
|
||||
if (!bhv["eat"]) return `You can't eat the ${foundObject.name}.`;
|
||||
|
||||
res = await runBehavior(thingy, "eat", foundObject, props);
|
||||
shouldRemove = res.shouldRemove;
|
||||
} else {
|
||||
shouldRemove = true;
|
||||
if (foundObject.objtype == "fish") {
|
||||
bhvNamespace = "fish";
|
||||
}
|
||||
|
||||
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 (foundObject.objtype == "fish") {
|
||||
removeItem(inventory.fishSack, foundObject);
|
||||
|
@ -55,15 +85,22 @@ export const eat = new Command(
|
|||
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 (res && res.and) {
|
||||
return `Our friend ${part.name} ate of his/her ${foundObject.name} ${res.and}`;
|
||||
if (res && res.success && typeof state.and !== "undefined") {
|
||||
return `Our friend ${part.name} ate of his/her ${foundObject.name} ${state.and}`;
|
||||
} else {
|
||||
return `Our friend ${part.name} ate of his/her ${foundObject.name}.`;
|
||||
}
|
||||
} else {
|
||||
if (res && res.and) {
|
||||
return `Our friend ${part.name} ate his/her ${foundObject.name} ${res.and}`;
|
||||
if (res && res.success && typeof state.and !== "undefined") {
|
||||
return `Our friend ${part.name} ate his/her ${foundObject.name} ${state.and}`;
|
||||
} else {
|
||||
return `Our friend ${part.name} ate his/her ${foundObject.name}.`;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
import { addBack } from "@server/backs";
|
||||
import Command from "@server/commands/Command";
|
||||
import BehaviorCommand from "@server/commands/BehaviorCommand";
|
||||
import { logger } from "@server/commands/handler";
|
||||
import { getInventory, updateInventory } from "@server/data/inventory";
|
||||
import prisma from "@server/data/prisma";
|
||||
import { getUser } from "@server/data/user";
|
||||
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 { addItem } from "@server/items";
|
||||
import { CosmicColor } from "@util/CosmicColor";
|
||||
import { addItem, findItemByNameFuzzy, removeItem } from "@server/items";
|
||||
|
||||
export const yeet = new Command(
|
||||
export const yeet = new BehaviorCommand(
|
||||
"yeet",
|
||||
["yeet", "yoot"],
|
||||
"Yeet literally anything you have (except non-fish animals)",
|
||||
"yeet <something>",
|
||||
"command.inventory.yeet",
|
||||
async ({ id, command, args, prefix, part, user }) => {
|
||||
const yeeting = args[0];
|
||||
async ({ id, command, args, prefix, part, user }, self) => {
|
||||
const yeeting = args.join(" ");
|
||||
if (!yeeting) return `What do you want to ${prefix}yeet?`;
|
||||
|
||||
const inventory = await getInventory(user.inventoryId);
|
||||
|
@ -26,77 +23,37 @@ export const yeet = new Command(
|
|||
let foundObject: IObject | undefined;
|
||||
let tryKekGen = false;
|
||||
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;
|
||||
|
||||
if (typeof item.count !== "undefined") {
|
||||
if (item.count > 1) {
|
||||
shouldRemove = false;
|
||||
((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;
|
||||
}
|
||||
foundObject =
|
||||
findItemByNameFuzzy(inventory.items, yeeting) ||
|
||||
findItemByNameFuzzy(inventory.fishSack, yeeting);
|
||||
|
||||
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") {
|
||||
bhvNamespace = "fish";
|
||||
tryKekGen = true;
|
||||
}
|
||||
|
||||
await updateInventory(inventory);
|
||||
|
||||
if (foundObject.id == "sand") {
|
||||
return `No, ${
|
||||
part.name
|
||||
}, don't yeet ${foundObject.name.toLowerCase()}.`;
|
||||
} else {
|
||||
const res = await self.behave<"yeet">(
|
||||
{
|
||||
part
|
||||
},
|
||||
foundObject.id,
|
||||
async ctx => {
|
||||
logger.debug("stuff");
|
||||
if (tryKekGen) {
|
||||
// 15%
|
||||
if (Math.random() < 0.15) {
|
||||
const randomFisher =
|
||||
Object.values(fishers)[
|
||||
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 scooted across his/her head before rebounding off onto the ground nearby. The ${
|
||||
itemAdjective[
|
||||
Math.floor(Math.random() * itemAdjective.length)
|
||||
Math.floor(
|
||||
Math.random() * itemAdjective.length
|
||||
)
|
||||
]
|
||||
} 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[
|
||||
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 ${
|
||||
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) {
|
||||
|
@ -221,11 +201,22 @@ export const yeet = new Command(
|
|||
let fish = foundObject.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)
|
||||
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 = [
|
||||
"kek of good fortune",
|
||||
|
@ -239,14 +230,16 @@ export const yeet = new Command(
|
|||
|
||||
addItem(locations[locations.indexOf(loc)].objects, {
|
||||
id: "kekklefruit",
|
||||
name: kekNames[Math.floor(Math.random() * kekNames.length)],
|
||||
name: kekNames[
|
||||
Math.floor(Math.random() * kekNames.length)
|
||||
],
|
||||
objtype: "item",
|
||||
count: 1,
|
||||
emoji: "🍍"
|
||||
});
|
||||
|
||||
// transcribed from the old code
|
||||
let yeets = [
|
||||
const yeets = [
|
||||
"The " +
|
||||
size +
|
||||
" " +
|
||||
|
@ -273,7 +266,15 @@ export const yeet = new Command(
|
|||
" 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) {
|
||||
|
@ -289,10 +290,47 @@ export const yeet = new Command(
|
|||
" 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;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -2,12 +2,14 @@ import { startAutorestart } from "@util/autorestart";
|
|||
import { startFisherTick } from "./fish/fishers";
|
||||
import { startObjectTimers } from "./fish/locations";
|
||||
import { initTree } from "./fish/tree";
|
||||
import { loadDefaultBehaviors } from "./items/behavior/defaults";
|
||||
import { registerBehaviors } from "./behavior/register";
|
||||
// import { loadDefaultBehaviors } from "./items/behavior/defaults";
|
||||
|
||||
startObjectTimers();
|
||||
await startFisherTick();
|
||||
await initTree();
|
||||
loadDefaultBehaviors();
|
||||
// loadDefaultBehaviors();
|
||||
registerBehaviors();
|
||||
|
||||
require("./api/server");
|
||||
require("./cli/readline");
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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."
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
|
@ -8,7 +8,7 @@ interface ICommandResponse {
|
|||
response: string;
|
||||
}
|
||||
|
||||
interface IContextProps {
|
||||
interface ICommandContextProps {
|
||||
id: string;
|
||||
channel: string;
|
||||
command: string;
|
||||
|
@ -19,7 +19,14 @@ interface IContextProps {
|
|||
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 {
|
||||
count: number;
|
||||
|
@ -139,26 +146,6 @@ interface IGroup {
|
|||
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 {
|
||||
chance: number;
|
||||
t: number;
|
||||
|
|
|
@ -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();
|
||||
});
|
Loading…
Reference in New Issue