From b23d129e8b98406cf031920e6d3e9504e90a1926 Mon Sep 17 00:00:00 2001 From: Hri7566 Date: Thu, 22 Feb 2024 14:46:45 -0500 Subject: [PATCH] Fish and locations --- config/fish.yml | 607 ++++++++++++++++++++ config/locations.yml | 20 + prisma/schema.prisma | 7 +- scripts/numfish.ts | 32 ++ scripts/rfish.ts | 15 + src/api/commands/groups/fishing/location.ts | 34 ++ src/api/commands/groups/index.ts | 3 +- src/api/fish/fish.ts | 103 ++++ src/api/fish/locations.ts | 56 ++ src/api/index.ts | 3 + src/util/types.d.ts | 29 +- 11 files changed, 904 insertions(+), 5 deletions(-) create mode 100644 config/fish.yml create mode 100644 config/locations.yml create mode 100644 scripts/numfish.ts create mode 100644 scripts/rfish.ts create mode 100644 src/api/commands/groups/fishing/location.ts create mode 100644 src/api/fish/fish.ts create mode 100644 src/api/fish/locations.ts diff --git a/config/fish.yml b/config/fish.yml new file mode 100644 index 0000000..6ddfc1a --- /dev/null +++ b/config/fish.yml @@ -0,0 +1,607 @@ +# https://nookipedia.com/wiki/Fish +- id: "angelfish" + name: "Angelfish" + size: 10 + rarity: 1 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b000011111100 +- id: "arowana" + name: "Arowana" + size: 70 + rarity: 1 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b000011111000 +- id: "barbel_steed" + name: "Barbel steed" + size: 20 + rarity: 0 + location: "river" + activeMonths: 0b001000000110 +- id: "bass" + name: "Bass" + size: 40 + rarity: 0 + location: "river" +- id: "bitterling" + name: "Bitterling" + size: 8 + rarity: 0 + location: "river" + activeMonths: 0b110000000011 +- id: "bluegill" + name: "Bluegill" + size: 25 + rarity: 0 + startHour: 9 + endHour: 16 +- id: "carp" + name: "Carp" + size: 60 + rarity: 0 + location: "river" +- id: "catfish" + name: "Catfish" + size: 60 + rarity: 0 + location: "pond" + activeMonths: 0b000011111100 +- id: "char" + name: "Char" + size: 50 + rarity: 2 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b001111001110 +- id: "cherry_salmon" + name: "Cherry salmon" + size: 35 + rarity: 1 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b001111000000 +- id: "coelacanth" + name: "Coelacanth" + size: 150 + rarity: 2 + location: "sea" + onlyRain: true +- id: "crucian_carp" + name: "Crucian carp" + size: 20 + rarity: 0 + location: "river" +- id: "dace" + name: "Dace" + size: 35 + rarity: 0 + location: "river" + startHour: 16 + endHour: 9 +- id: "eel" + name: "Eel" + size: 100 + rarity: 1 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b000001111000 +- id: "freshwater_goby" + name: "Freshwater goby" + size: 15 + rarity: 1 + location: "river" + startHour: 16 + endHour: 9 +- id: "giant_catfish" + name: "Giant catfish" + size: 40 + rarity: 1 + location: "lake" + startHour: 16 + endHour: 9 + activeMonths: 0b000001110000 +- id: "giant_snakehead" + name: "Giant snakehead" + size: 85 + rarity: 2 + location: "pond" + startHour: 9 + endHour: 16 +- id: "goldfish" + name: "Goldfish" + size: 15 + rarity: 0 + location: "pond" +- id: "guppy" + name: "Guppy" + size: 4 + rarity: 2 + location: "river" + startHour: 9 + endHour: 16 + activeMonths: 0b000111111110 +- id: "herabuna" + name: "Herabuna" + size: 50 + rarity: 0 + location: "lake" +- id: "koi" + name: "Koi" + size: 60 + rarity: 1 + location: "pond" + startHour: 16 + endHour: 9 +- id: "large_bass" + name: "Large bass" + size: 75 + rarity: 1 + location: "river" +- id: "loach" + name: "Loach" + size: 20 + rarity: 0 + location: "river" + activeMonths: 0b001110000000 +- id: "pale_chub" + name: "Pale chub" + size: 15 + rarity: 1 + location: "river" +- id: "piranha" + name: "Piranha" + size: 30 + rarity: 2 + location: "river" + startHour: 9 + endHour: 16 + activeMonths: 0b000001111000 +- id: "pond_smelt" + name: "Pond smelt" + size: 15 + rarity: 0 + location: "river" + activeMonths: 0b110000000001 +- id: "pop-eyed_goldfish" + name: "Pop-eyed goldfish" + size: 15 + rarity: 1 + location: "pond_smelt" + startHour: 9 + endHour: 4 +- id: "rainbow_trout" + name: "Rainbow trout" + size: 60 + rarity: 0 + location: "river" + startHour: 4 + endHour: 21 + activeMonths: 0b001111001110 +- id: "salmon" + name: "Salmon" + size: 90 + rarity: 0 + location: "river" + activeMonths: 0b000000001000 +- id: "small_bass" + name: "Small bass" + size: 30 + rarity: 0 + location: "river" +- id: "stringfish" + name: "Stringfish" + size: 140 + rarity: 2 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b111000000001 +- id: "sweetfish" + name: "Sweetfish" + size: 25 + rarity: 1 + location: "river" + activeMonths: 0b000000111000 +- id: "arapaima" + name: "Arapaima" + size: 300 + rarity: 2 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b000001111000 +- id: "barred_knifejaw" + name: "Barred knifejaw" + size: 60 + rarity: 1 + location: "sea" + activeMonths: 0b001111111110 +- id: "brook_trout" + name: "Brook trout" + size: 50 + rarity: 0 + location: "lake" +- id: "crawfish" + name: "Crawfish" + size: 12 + rarity: 0 + location: "pond" + activeMonths: 0b000111111000 +- id: "frog" + name: "Frog" + size: 12 + rarity: 0 + location: "pond" + activeMonths: 0b000011110000 +- id: "jellyfish" + name: "Jellyfish" + size: 25 + rarity: 1 + location: "sea" + activeMonths: 0b000000111000 +- id: "moon_jellyfish" + name: "Moon jellyfish" + size: 30 + rarity: 1 + location: "sea" + activeMonths: 0b000001111000 +- id: "killifish" + name: "Killifish" + size: 4 + rarity: 1 + location: "pond" + activeMonths: 0b000111110000 +- id: "red_snapper" + name: "Red snapper" + size: 90 + rarity: 1 + location: "sea" +- id: "sea_bass" + name: "Sea bass" + size: 100 + rarity: 0 + location: "sea" +- id: "blue_marlin" + name: "Blue marlin" + size: 220 + rarity: 2 + location: "sea" # "pier" + activeMonths: 0b111100111011 +- id: "dab" + name: "Dab" + size: 50 + rarity: 1 + location: "sea" + activeMonths: 0b111100000111 +- id: "horse_mackerel" + name: "Horse mackerel" + size: 40 + rarity: 0 + location: "sea" +- id: "octopus" + name: "Octopus" + size: 50 + rarity: 0 + location: "sea" +- id: "olive_flounder" + name: "Olive flounder" + size: 80 + rarity: 0 + location: "sea" +- id: "puffer_fish" + name: "Puffer fish" + size: 35 + rarity: 1 + location: "sea" +- id: "sea_horse" + name: "Sea horse" + size: 8 + rarity: 0 + location: "sea" + activeMonths: 0b000111111110 +- id: "squid" + name: "Squid" + size: 35 + rarity: 0 + location: "sea" + activeMonths: 0b111111110001 +- id: "black_bass" + name: "Black bass" + size: 50 + rarity: 0 + location: "river" +- id: "clown_fish" + name: "Clown fish" + size: 15 + rarity: 0 + location: "sea" + activeMonths: 0b000111111000 +- id: "dorado" + name: "Dorado" + size: 100 + rarity: 2 + location: "river" + activeMonths: 0b000001111000 +- id: "football_fish" + name: "Football fish" + size: 60 + rarity: 1 + startHour: 16 + endHour: 9 + location: "sea" +- id: "gar" + name: "Gar" + size: 190 + rarity: 1 + location: "pond" + startHour: 16 + endHour: 9 + activeMonths: 0b000001111000 +- id: "great_white_shark" + name: "Great white shark" + size: 540 + rarity: 2 + location: "sea" + startHour: 16 + endHour: 9 + activeMonths: 0b000001111000 +- id: "hammerhead_shark" + name: "Hammerhead shark" + size: 250 + rarity: 2 + location: "sea" + startHour: 16 + endHour: 9 + activeMonths: 0b000001111000 +- id: "king_salmon" + name: "King salmon" + size: 140 + rarity: 1 + location: "river" + activeMonths: 0b000000001000 +- id: "ocean_sunfish" + name: "Ocean sunfish" + size: 300 + rarity: 2 + location: "sea" + startHour: 4 + endHour: 21 +- id: "sea_butterfly" + name: "Sea butterfly" + size: 3 + rarity: 0 + location: "sea" + activeMonths: 0b111000000001 +- id: "tuna" + name: "Tuna" + size: 230 + rarity: 2 + location: "sea" + activeMonths: 0b111100000011 +- id: "yellow_perch" + name: "Yellow perch" + size: 35 + rarity: 0 + location: "river" + activeMonths: 0b111000001111 +- id: "zebra_turkeyfish" + name: "Zebra turkeyfish" + size: 30 + rarity: 0 + location: "sea" + activeMonths: 0b111100000011 +- id: "butterfly_fish" + name: "Butterfly fish" + size: 18 + rarity: 1 + location: "sea" + activeMonths: 0b111100001111 +- id: "lobster" + name: "Lobster" + size: 50 + rarity: 2 + location: "sea" + activeMonths: 0b100111000001 +- id: "mooray_eel" + name: "Mooray eel" + size: 80 + rarity: 1 + location: "sea" + activeMonths: 0b000000011100 +- id: "napoleanfish" + name: "Napoleanfish" + size: 180 + rarity: 2 + location: "sea" + startHour: 4 + endHour: 21 + activeMonths: 0b000000110000 +- id: "neon_tetra" + name: "Neon tetra" + size: 2 + rarity: 1 + location: "river" + startHour: 9 + endHour: 16 + activeMonths: 0b000111111110 +- id: "pike" + name: "Pike" + size: 120 + rarity: 1 + location: "river" + activeMonths: 0b000000001111 +- id: "ray" + name: "Ray" + size: 120 + rarity: 1 + location: "sea" + startHour: 4 + endHour: 21 + activeMonths: 0b000000011110 +- id: "surgeonfish" + name: "Surgeonfish" + size: 31 + rarity: 1 + location: "sea" + activeMonths: 0b000111111000 +- id: "blowfish" + name: "Blowfish" + size: 25 + rarity: 1 + location: "sea" + startHour: 21 + endHour: 4 + activeMonths: 0b110000000011 +- id: "giant_trevally" + name: "Giant trevally" + size: 180 + rarity: 2 + location: "sea" + activeMonths: 0b000011111100 +- id: "mitten_crab" + name: "Mitten crab" + size: 8 + rarity: 2 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b000000001110 +- id: "nibble_fish" + name: "Nibble fish" + size: 8 + rarity: 2 + location: "river" + startHour: 9 + endHour: 16 + activeMonths: 0b000011111000 +- id: "oarfish" + name: "Oarfish" + size: 580 + rarity: 2 + location: "sea" + activeMonths: 0b111110000001 +- id: "ribbon_eel" + name: "Ribbon eel" + size: 120 + rarity: 1 + location: "sea" + activeMonths: 0b000001111100 +- id: "saddled_bichir" + name: "Saddled bichir" + size: 60 + rarity: 1 + location: "river" + startHour: 21 + endHour: 4 + activeMonths: 0b000001111000 +- id: "saw_shark" + name: "Saw shark" + size: 150 + rarity: 2 + location: "sea" + startHour: 16 + endHour: 9 + activeMonths: 0b000001111000 +- id: "soft-shelled_turtle" + name: "Soft-shelled Turtle" + size: 30 + rarity: 1 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b000000011000 +- id: "tadpole" + name: "Tadpole" + size: 4 + rarity: 0 + location: "pond" + activeMonths: 0b001111100000 +- id: "whale_shark" + name: "Whale shark" + size: 600 + rarity: 2 + location: "sea" + activeMonths: 0b000001111000 +- id: "anchovy" + name: "Anchovy" + size: 15 + rarity: 1 + location: "sea" + startHour: 4 + endHour: 21 +- id: "barreleye" + name: "Barreleye" + size: 20 + rarity: 2 + location: "sea" + startHour: 21 + endHour: 4 +- id: "betta" + name: "Betta" + size: 7 + rarity: 1 + location: "river" + startHour: 9 + endHour: 16 + activeMonths: 0b000011111100 +- id: "golden_trout" + name: "Golden trout" + size: 30 + rarity: 2 + location: "river" + startHour: 16 + endHour: 9 + activeMonths: 0b001110001110 +- id: "mahi-mahi" + name: "Mahi-mahi" + size: 110 + rarity: 2 + location: "sea" + activeMonths: 0b000011111100 +- id: "rainbowfish" + name: "Rainbowfish" + size: 12 + rarity: 1 + location: "river" + startHour: 9 + endHour: 16 + activeMonths: 0b000011111100 +- id: "ranchu_goldfish" + name: "Ranchu goldfish" + size: 15 + rarity: 1 + location: "pond" + startHour: 9 + endHour: 16 +- id: "snapping_turtle" + name: "Snapping turtle" + size: 50 + rarity: 0 + location: "river" + startHour: 21 + endHour: 4 + activeMonths: 0b000111111100 +- id: "sturgeon" + name: "Sturgeon" + size: 20 + rarity: 2 + location: "river" + activeMonths: 0b111000001111 +- id: "suckerfish" + name: "Suckerfish" + size: 110 + rarity: 2 + location: "sea" + activeMonths: 0b000001111000 +- id: "tilapia" + name: "Tilapia" + size: 20 + rarity: 0 + location: "river" + activeMonths: 0b000001111100 diff --git a/config/locations.yml b/config/locations.yml new file mode 100644 index 0000000..46fa4a9 --- /dev/null +++ b/config/locations.yml @@ -0,0 +1,20 @@ +- id: pond + name: Pond + nearby: [] + hasSand: true + objects: [] +- id: lake + name: Lake + nearby: [] + hasSand: false + objects: [] +- id: river + name: River + nearby: [] + hasSand: false + objects: [] +- id: sea + name: Sea + nearby: [] + hasSand: true + objects: [] diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fcd94d0..ca8c38b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,14 +20,15 @@ model User { } model Inventory { - id Int @id @default(autoincrement()) - balance Int @default(0) + id Int @id @default(autoincrement()) + balance Int @default(0) + location String @default("pond") items Json @default("[]") fishSack Json @default("[]") pokemon Json @default("[]") - User User? + user User? } model AuthToken { diff --git a/scripts/numfish.ts b/scripts/numfish.ts new file mode 100644 index 0000000..3d6a278 --- /dev/null +++ b/scripts/numfish.ts @@ -0,0 +1,32 @@ +import { Logger } from "@util/Logger"; +import { argv } from "bun"; +import { existsSync, readFileSync, writeFileSync } from "fs"; +import YAML from "yaml"; + +const logger = new Logger("Numfish"); + +const inFile = argv[2]; + +if (typeof inFile !== "string") { + logger.error(`Usage: `); + process.exit(); +} + +if (!existsSync(inFile)) { + logger.error("Input file not found"); + process.exit(); +} + +logger.info("Reading YAML..."); + +let data: IFish[]; + +try { + const ydata = readFileSync(inFile).toString(); + data = YAML.parse(ydata); + logger.info("Number of fish:", data.length); +} catch (err) { + logger.error(err); + logger.error("YAML read error"); + process.exit(); +} diff --git a/scripts/rfish.ts b/scripts/rfish.ts new file mode 100644 index 0000000..bee702c --- /dev/null +++ b/scripts/rfish.ts @@ -0,0 +1,15 @@ +import { randomFish } from "@server/fish/fish"; +import { Logger } from "@util/Logger"; +import { argv } from "bun"; + +const logger = new Logger("Numfish"); + +const location = argv[2]; + +if (typeof location !== "string") { + logger.error(`Usage: `); + process.exit(); +} + +const fish = randomFish(location); +logger.info(fish); diff --git a/src/api/commands/groups/fishing/location.ts b/src/api/commands/groups/fishing/location.ts new file mode 100644 index 0000000..7bd00e6 --- /dev/null +++ b/src/api/commands/groups/fishing/location.ts @@ -0,0 +1,34 @@ +import Command from "@server/commands/Command"; +import { getInventory } from "@server/data/inventory"; +import { locations } from "@server/fish/locations"; + +const answers = [ + "You are at {loc}.", + "You appear to be at {loc}.", + "According to your map, you are at {loc}.", + "The map says you are at {loc}.", + "Looking at your world atlas, you appear to be at {loc}.", + "The world atlas defines your location as {loc}.", + "Judging by the wind direction, hemisphere sun angle, and the size of the waves in the water, you deduce that you must be at {loc}.", + "You run your fingers down the map until you find {loc}. This is where you must be.", + "Your cell phone's maps app shows your location as {loc}.", + "You pull your cell phone out of your pocket and turn it on. Opening the maps app, you realize you are at {loc}." +]; + +export const location = new Command( + "location", + ["location", "loc", "where", "whereami", "l"], + "Get current location", + "location", + "command.fishing.location", + async ({ id, command, args, prefix, part, user }) => { + const inventory = await getInventory(user.inventoryId); + if (!inventory) return; + + const loc = locations.find(loc => loc.id == inventory.location); + if (!loc) return "You are in the middle of nowhere."; + + const answer = answers[Math.floor(Math.random() * answers.length)]; + return answer.replaceAll("{loc}", loc.name); + } +); diff --git a/src/api/commands/groups/index.ts b/src/api/commands/groups/index.ts index 17749c9..a4cd14e 100644 --- a/src/api/commands/groups/index.ts +++ b/src/api/commands/groups/index.ts @@ -3,6 +3,7 @@ import { fish } from "./fishing/fish"; import { help } from "./general/help"; import { setcolor } from "./util/setcolor"; import { data } from "./util/data"; +import { location } from "./fishing/location"; interface ICommandGroup { id: string; @@ -23,7 +24,7 @@ commandGroups.push(general); const fishing: ICommandGroup = { id: "fishing", displayName: "Fishing", - commands: [fish] + commands: [fish, location] }; commandGroups.push(fishing); diff --git a/src/api/fish/fish.ts b/src/api/fish/fish.ts new file mode 100644 index 0000000..e3b90cf --- /dev/null +++ b/src/api/fish/fish.ts @@ -0,0 +1,103 @@ +import { Logger } from "@util/Logger"; +import { loadConfig } from "@util/config"; + +export const fish = loadConfig("config/fish.yml", []); + +const logger = new Logger("Fishies"); + +export function randomFish(location: string, r: number = Math.random()) { + let rarity = 0; + + // logger.debug("R:", r); + + if (r < 0.1) { + rarity = 2; + } else if (r < 0.6) { + rarity = 1; + } else if (r < 1) { + rarity = 0; + } + + let animal: IFish | undefined; + + let matchesLocation = false; + let matchesRarity = false; + let matchesMonth = false; + let matchesTime = false; + + let maxRecursion = 1000; + let i = 0; + + while ( + !matchesLocation || + !matchesRarity || + !matchesMonth || + !matchesTime + ) { + if (i >= maxRecursion) { + break; + } + + animal = fish[Math.floor(Math.random() * fish.length)]; + + matchesLocation = animal.location == location; + matchesRarity = animal.rarity == rarity; + + matchesMonth = + typeof animal.activeMonths !== "undefined" + ? hasFishMonths(animal.activeMonths) + : true; + + matchesTime = + typeof animal.startHour !== "undefined" && + typeof animal.endHour !== "undefined" + ? hasFishTime(animal.startHour, animal.endHour) + : true; + + // logger.debug("Matches location:", matchesLocation); + // logger.debug("Matches rarity:", matchesRarity); + // logger.debug("Matches month:", matchesMonth); + // logger.debug("Matches time:", matchesTime); + + i++; + } + + return animal as IFish; +} + +export function hasFishMonths(activeMonths: string, t: number = Date.now()) { + const d = new Date(t); + const month = d.getMonth(); + const bin = 0x800 >> month; + const active = parseInt(activeMonths.substring(2), 2); + + // logger.debug("Current month: ", bin.toString(2).padStart(12, "0")); + // logger.debug("Active: ", active.toString(2).padStart(12, "0")); + // logger.debug( + // "Bitwise: ", + // (bin & active).toString(2).padStart(12, "0") + // ); + + return !!(bin & active); +} + +export function hasFishTime( + startHour: number, + endHour: number, + t: number = Date.now() +) { + const d = new Date(t); + const hour = d.getHours(); + + if (startHour < endHour) { + if (hour > startHour && hour < endHour) { + return true; + } + } else { + if (hour > startHour || hour < startHour) { + return true; + } + } + + return false; +} diff --git a/src/api/fish/locations.ts b/src/api/fish/locations.ts new file mode 100644 index 0000000..255be81 --- /dev/null +++ b/src/api/fish/locations.ts @@ -0,0 +1,56 @@ +import { loadConfig } from "@util/config"; + +export const locations = loadConfig("config/locations.yml", [ + { + id: "pond", + name: "Pond", + nearby: [], + hasSand: true, + objects: [] + }, + { + id: "lake", + name: "Lake", + nearby: [], + hasSand: false, + objects: [] + }, + { + id: "river", + name: "River", + nearby: [], + hasSand: false, + objects: [] + }, + { + id: "sea", + name: "Sea", + nearby: [], + hasSand: true, + objects: [] + } +]); + +const sand: IItem = { + id: "sand", + name: "Sand" +}; + +let sandInterval: Timer; + +export function startSandInterval() { + sandInterval = setInterval(() => { + for (const loc of locations) { + if (!loc.hasSand) continue; + + let existing = loc.objects.find(obj => obj.id == "sand"); + if (typeof existing !== "undefined") continue; + + loc.objects.push(sand); + } + }, 6000); +} + +export function stopSandInterface() { + clearInterval(sandInterval); +} diff --git a/src/api/index.ts b/src/api/index.ts index cb784cc..e502e1e 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,2 +1,5 @@ import "./api/server"; import "./cli/readline"; +import { startSandInterval } from "./fish/locations"; + +startSandInterval(); diff --git a/src/util/types.d.ts b/src/util/types.d.ts index d96eda0..864e5ec 100644 --- a/src/util/types.d.ts +++ b/src/util/types.d.ts @@ -21,10 +21,24 @@ interface CountComponent { count: number; } +interface IItem { + id: string; + name: string; +} + interface IFish extends JsonValue { id: string; name: string; - size: string; + size: number; + rarity: number; + location: string; + + onlyRain?: true; + startHour?: number; + endHour?: number; + activeMonths?: string; + + emoji?: string; } interface IPokemon extends JsonValue { @@ -46,14 +60,19 @@ interface IPokemon extends JsonValue { }; } +type TInventoryItems = JsonArray & IItem[]; type TFishSack = JsonArray & IFish[]; type TPokemonSack = JsonArray & IPokemon[]; interface IInventory { id: number; balance: number; + + items: TInventoryItems; fishSack: TFishSack; pokemon: TPokemonSack; + + user: User; } interface IBack extends Record { @@ -65,3 +84,11 @@ interface Backs extends Record> { m: "color"; }; } + +interface ILocation { + id: string; + name: string; + nearby: string[]; + objects: (IItem | IFish | IPokemon)[]; + hasSand: boolean; +}