diff --git a/src/api/commands/groups/inventory/eat.ts b/src/api/commands/groups/inventory/eat.ts index 67e5b3e..b6dbe6d 100644 --- a/src/api/commands/groups/inventory/eat.ts +++ b/src/api/commands/groups/inventory/eat.ts @@ -1,7 +1,6 @@ -import { addBack } from "@server/backs"; import Command from "@server/commands/Command"; import { getInventory, updateInventory } from "@server/data/inventory"; -import { CosmicColor } from "@util/CosmicColor"; +import { itemBehaviorMap, runBehavior } from "@server/items/behavior"; export const eat = new Command( "eat", @@ -9,7 +8,8 @@ export const eat = new Command( "Eat literally anything you have (except non-fish animals)", "eat ", "command.inventory.eat", - async ({ id, command, args, prefix, part, user }) => { + async props => { + const { args, prefix, part, user } = props; const eating = args[0]; if (!eating) return `What do you want to ${prefix}eat?`; @@ -17,8 +17,8 @@ export const eat = new Command( if (!inventory) return; let foundObject: IObject | undefined; - let tryChangingColor = false; let i = 0; + let shouldRemove = false; for (const item of inventory.items as unknown as IItem[]) { if (!item.name.toLowerCase().includes(eating.toLowerCase())) { @@ -27,54 +27,11 @@ export const eat = new Command( } 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) (inventory.items as TInventoryItems).splice(i, 1); break; } i = 0; - // no more eating animals - // for (const pokemon of inventory.pokemon as TPokemonSack) { - // if (!pokemon.name.toLowerCase().includes(eating.toLowerCase())) { - // i++; - // continue; - // } - - // foundObject = pokemon as unknown as IObject; - - // let shouldRemove = false; - - // if (typeof pokemon.count !== "undefined") { - // if (pokemon.count > 1) { - // shouldRemove = false; - // ((inventory.pokemon as TPokemonSack)[i].count as number)--; - // } else { - // shouldRemove = true; - // } - // } else { - // shouldRemove = true; - // } - - // if (shouldRemove) (inventory.pokemon as TPokemonSack).splice(i, 1); - // break; - // } - - i = 0; - for (const fish of inventory.fishSack as TFishSack) { if (!fish.name.toLowerCase().includes(eating.toLowerCase())) { i++; @@ -82,56 +39,77 @@ export const eat = new Command( } 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 "${eating}" to eat.`; - if (foundObject.objtype == "fish") { - tryChangingColor = true; + + // Get item behaviors and run the "eat" script + let thingy = foundObject.id; + if (foundObject.objtype == "fish") thingy = "fish"; + + const bhv = itemBehaviorMap[thingy]; + if (!bhv) return `The ${foundObject.name} isn't edible.`; + if (!bhv["eat"]) return `You can't eat the ${foundObject.name}.`; + + const res = await runBehavior(thingy, "eat", foundObject, props); + shouldRemove = res.shouldRemove; + + if (shouldRemove) { + if (foundObject.objtype == "fish") { + i = 0; + + for (const fish of inventory.fishSack as TFishSack) { + 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; + } + } else if (foundObject.objtype == "item") { + i = 0; + + for (const item of inventory.items as unknown as IItem[]) { + if (typeof item.count == "number") { + if (item.count > 1) { + shouldRemove = false; + ((inventory.items as TInventoryItems)[i] + .count as number)--; + } else { + shouldRemove = true; + } + } else { + shouldRemove = true; + } + + if (shouldRemove) + (inventory.items as TInventoryItems).splice(i, 1); + break; + } + } + + await updateInventory(inventory); } - await updateInventory(inventory); - - if (!tryChangingColor) { - if (foundObject.id == "sand") { - return `Our friend ${part.name} ate of his/her ${foundObject.name}.`; + if (foundObject.id == "sand") { + if (res.and) { + return `Our friend ${part.name} ate of his/her ${foundObject.name} ${res.and}`; } else { - return `Our friend ${part.name} ate his/her ${foundObject.name}.`; + return `Our friend ${part.name} ate of his/her ${foundObject.name}.`; } } else { - const r = Math.random(); - - if (r < 0.3) { - const color = new CosmicColor( - Math.floor(Math.random() * 255), - Math.floor(Math.random() * 255), - Math.floor(Math.random() * 255) - ); - - addBack(id, { - m: "color", - id: part.id, - color: color.toHexa() - }); - - return `Our friend ${part.name} ate his/her ${ - foundObject.name - } and it made him/her turn ${color.getName().toLowerCase()}.`; + if (res.and) { + return `Our friend ${part.name} ate his/her ${foundObject.name} ${res.and}`; } else { return `Our friend ${part.name} ate his/her ${foundObject.name}.`; } diff --git a/src/api/fish/fishers.ts b/src/api/fish/fishers.ts index 11c8220..0aae76f 100644 --- a/src/api/fish/fishers.ts +++ b/src/api/fish/fishers.ts @@ -41,8 +41,11 @@ export async function tick() { } const r = Math.random(); + const data = await getFishingChance(user.id); + // After 30 minutes, reset chance + if (data.t > 30 * 60000) await resetFishingChance(user.id); - if (r < 0.1) { + if (r < data.chance / 10) { stopFishing( winner.id, winner.userID, @@ -131,3 +134,33 @@ export function stopFishing( export function getFishing(id: string, userID: string) { return fishers[id + "~" + userID]; } + +export async function getFishingChance(userID: string) { + const key = `fishingChance~${userID}`; + const data = (await kvGet(key)) as IFishingChance; + + if (!data) { + await resetFishingChance(userID); + return await getFishingChance(userID); + } + + return data; +} + +export async function resetFishingChance(userID: string) { + const key = `fishingChance~${userID}`; + await kvSet(key, { + t: Date.now(), + chance: 1 + } as IFishingChance); +} + +export async function incrementFishingChance(userID: string) { + const key = `fishingChance~${userID}`; + const data = (await kvGet(key)) as IFishingChance; + if (!data) await resetFishingChance(userID); + const r = Math.random(); + data.chance += r; + await kvSet(key, data); + return r; +} diff --git a/src/api/fish/locations.ts b/src/api/fish/locations.ts index cbf731a..aeaaec7 100644 --- a/src/api/fish/locations.ts +++ b/src/api/fish/locations.ts @@ -35,14 +35,6 @@ export const locations = loadConfig("config/locations.yml", [ nearby: ["pond", "lake", "river"], hasSand: true, objects: [] - }, - - { - id: "forest", - name: "Forest", - nearby: ["pond", "lake", "beach"], - hasSand: false, - objects: [] } ]); diff --git a/src/api/index.ts b/src/api/index.ts index d6b643c..d56e418 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,7 +3,9 @@ import "./cli/readline"; import { startFisherTick } from "./fish/fishers"; import { startObjectTimers } from "./fish/locations"; import { initTree } from "./fish/tree"; +import { loadDefaultBehaviors } from "./items/behavior/defaults"; startObjectTimers(); await startFisherTick(); await initTree(); +loadDefaultBehaviors(); diff --git a/src/api/items/behavior/defaults.ts b/src/api/items/behavior/defaults.ts new file mode 100644 index 0000000..37cf2bb --- /dev/null +++ b/src/api/items/behavior/defaults.ts @@ -0,0 +1,13 @@ +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]); + } + } +} diff --git a/src/api/items/behavior/index.ts b/src/api/items/behavior/index.ts index 8cf61eb..baf4c3b 100644 --- a/src/api/items/behavior/index.ts +++ b/src/api/items/behavior/index.ts @@ -1,5 +1,26 @@ -export const itemBehaviorMap: TItemBehaviorMap = {}; +export const itemBehaviorMap: TBehaviorMap = {}; -export function addItemBehavior(itemID: string, bhv: TItemBehavior) { - itemBehaviorMap[itemID] = bhv; +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 { + const callback = itemBehaviorMap[itemID][bhvID]; + if (!callback) + return { + success: false, + err: "No callback", + shouldRemove: false + }; + return await callback(obj, props); } diff --git a/src/api/items/behavior/items/fish.ts b/src/api/items/behavior/items/fish.ts index e69de29..b5fbd80 100644 --- a/src/api/items/behavior/items/fish.ts +++ b/src/api/items/behavior/items/fish.ts @@ -0,0 +1,29 @@ +import { addBack } from "@server/backs"; +import { CosmicColor } from "@util/CosmicColor"; + +export const fish: IBehaviorDefinition = { + id: "fish", + bhv: { + async eat(obj, props) { + 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()}.` + }; + } + } +}; diff --git a/src/api/items/behavior/items/kekklefruit.ts b/src/api/items/behavior/items/kekklefruit.ts new file mode 100644 index 0000000..3287610 --- /dev/null +++ b/src/api/items/behavior/items/kekklefruit.ts @@ -0,0 +1,16 @@ +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." + }; + } + } +}; diff --git a/src/util/types.d.ts b/src/util/types.d.ts index e502e97..71309d8 100644 --- a/src/util/types.d.ts +++ b/src/util/types.d.ts @@ -8,7 +8,7 @@ interface ICommandResponse { response: string; } -type TCommandCallback = (props: { +interface IContextProps { id: string; command: string; args: string[]; @@ -16,7 +16,9 @@ type TCommandCallback = (props: { part: IPart; user: User; isDM: boolean; -}) => Promise; +} + +type TCommandCallback = (props: IContextProps) => Promise; interface CountComponent { count: number; @@ -112,19 +114,32 @@ interface TFisher { type TPokedex = IPokemon[]; -type TBehavior = () => Promise; -type TBehaviorMap = Record; - -interface IItemBehaviorData { - status: boolean; - text: string; - userID: string; -} - -type TItemBehavior = Behavior; -type TItemBehaviorMap = TBehaviorMap; - interface IGroup { id: string; permissions: string[]; } + +interface IBehaviorResponse { + success: boolean; + err?: string; + + shouldRemove: boolean; + and?: string; +} + +type TBehaviorCallback = ( + obj: IObject, + props: IContextProps +) => Promise; +type TBehavior = Record; +type TBehaviorMap = Record; + +interface IBehaviorDefinition { + id: string; + bhv: TBehavior; +} + +interface IFishingChance { + chance: number; + t: number; +}