diff options
-rw-r--r-- | Cargo.lock | 4 | ||||
-rw-r--r-- | data/maps/sushibar.yaml | 20 | ||||
-rw-r--r-- | data/recipes/default.ts | 302 | ||||
-rw-r--r-- | server/src/data/mod.rs | 17 | ||||
-rw-r--r-- | server/src/entity/portal.rs | 17 | ||||
-rw-r--r-- | server/src/state.rs | 3 | ||||
-rw-r--r-- | test-client/tiles.ts | 44 | ||||
-rw-r--r-- | test-client/visual.ts | 15 |
8 files changed, 252 insertions, 170 deletions
@@ -924,9 +924,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ "bitflags", ] diff --git a/data/maps/sushibar.yaml b/data/maps/sushibar.yaml index b96dcafb..3dee515a 100644 --- a/data/maps/sushibar.yaml +++ b/data/maps/sushibar.yaml @@ -17,16 +17,16 @@ map: - "*''''*''''*''''''''*''" - "'''*'''''''**''''''''*" - "''█████████████'X'''*'" - - "''█R.....#c...█'''''**" - - "''█T.....#c...d____'''" - - "'*█F..S..#c...d____'*'" - - "*'█L..S..#c...█''__'''" - - "''█f..⌷..#c...█''__''*" - - "*'█f..⌷..#c.~.█''__'*'" - - "'*█o..C..#c...█''__''*" - - "''█o..C..███d██''__'''" - - "*'█s..........█''__'*'" - - "'*█⌷ZZ⌷⌷⌷ggg⌷⌷█''__''*" + - "''█f.....#c...█'''''**" + - "''█f..⌷⌷.#c...d____'''" + - "'*█o..S⌷.#c...d____'*'" + - "*'█o..S⌷.#c...█''__'''" + - "''█R.....#c...█''__''*" + - "*'█T..⌷⌷.#c.~.█''__'*'" + - "'*█F..C⌷.#c...█''__''*" + - "''█L..C⌷.███d██''__'''" + - "*'█Z..........█''__'*'" + - "'*█Z⌷s⌷ggggg⌷⌷█''__''*" - "*'█████████████''__'''" - "'''*'''''''''''''_!'''" - "'*''''''''''''''''''''" diff --git a/data/recipes/default.ts b/data/recipes/default.ts index 33b433a6..662b6817 100644 --- a/data/recipes/default.ts +++ b/data/recipes/default.ts @@ -16,12 +16,12 @@ */ -//? Is is a good idea? Probably not. +//? Is this a good idea? Probably not. export interface Recipe { tile?: string, - inputs: (string | null)[], - outputs: (string | null)[], + inputs: (Item | null | undefined)[], + outputs: (Item | null | undefined)[], action: "instant" | "passive" | "active" | "demand" | "demand" duration?: number revert_duration?: number, @@ -29,184 +29,204 @@ export interface Recipe { points?: number, } -function trash_output(ifull: string) { - const [i, ic] = get_container(ifull) - if (i == "plate") return ifull - if (i == "glass") return ifull - if (i == "pot") return ifull - if (i == "foodprocessor") return ifull - if (i == "dirty") return ifull - if (ic == "glass") return "glass" - if (ic == "plate") return "dirty-plate" - return null -} - -export const all_items = new Set<string>() -export function auto_trash() { - for (const ifull of all_items) { - let [i, ic] = get_container(ifull) - if (i == "plate") continue - if (i == "glass") continue - if (i == "pot") continue - if (i == "foodprocessor") continue - if (i == "dirty") continue - if (ic == "glass") ic = "glass" - if (ic == "plate") ic = "dirty-plate" - out({ - action: "instant", - tile: ic == "glass" ? "sink" : "trash", - inputs: [ifull], - outputs: [ic] - }) - } -} - +const all_items = new Set<Item>() +const all_recipes = new Set<Recipe>() export function out(r: Recipe) { r.inputs.forEach(i => i ? all_items.add(i) : void 0) r.outputs.forEach(i => i ? all_items.add(i) : void 0) r.inputs = r.inputs.filter(e => e) r.outputs = r.outputs.filter(e => e) - console.log(`- ${JSON.stringify(r).replaceAll("\"active\"", "!active").replaceAll("\"passive\"", "!passive").replaceAll("\"instant\"", "!instant")}`); + all_recipes.add(r); +} +export function finish() { + const k = new Set<string>() + for (const r of all_recipes) { + let s = `- action: !${r.action}\n` + s += ` inputs: [${r.inputs.map(e => e!.toString()).join(",")}]\n` + s += ` outputs: [${r.outputs.map(e => e!.toString()).join(",")}]\n` + if (r.warn) s += ` warn: true\n` + if (r.duration) s += ` duration: ${r.duration}\n` + if (r.revert_duration) s += ` revert_duration: ${r.revert_duration}\n` + if (r.points) s += ` points: ${r.points}\n` + if (r.tile) s += ` tile: ${r.tile}\n` + k.add(s) + } + for (const r of k) + console.log(r); +} +function auto_trash() { + for (const i of all_items) { + if (i instanceof Container) continue + if (!i.container) out({ action: "instant", inputs: [i], outputs: [], tile: "trash" }) + else { + out({ action: "instant", inputs: [i], outputs: [i.container.dispose ?? i.container], tile: "trash" }) + } + } } -export function edible(item: string) { - let i = item - if (!item.endsWith("-plate") && !item.endsWith("-glass")) { - i += "-plate" - out({ action: "instant", inputs: [item], outputs: [i] }) +class Item { + constructor( + public name: string, + public container?: Container + ) { } + as(s: string) { this.name = s; return this } + tr(container?: Container) { + const o = new Item(this.name, container) + if (this.container) out({ action: "instant", inputs: [this, container], outputs: [this.container, o] }) + else out({ action: "instant", inputs: [container, this], outputs: [o] }) + return o + } + toString() { + return this.name + (this.container ? "-" + this.container : "") } - out({ action: "demand", inputs: [i], outputs: [trash_output(i)], duration: 10 }) } -export function cut(from: string, to?: string, to2?: string) { - out({ action: "active", duration: 2, tile: "cuttingboard", inputs: [from], outputs: [to ?? ("sliced-" + from), to2 ?? null] }) +class Container extends Item { constructor(name: string, public dispose?: Item) { super(name) } } +const FP = new Container("foodprocessor") +const POT = new Container("pot") +const PL = new Container("plate", new Container("dirty-plate")) +const GL = new Container("glass") + +function crate(s: string): Item { + const item = new Item(s); + out({ action: "instant", inputs: [], outputs: [item], tile: `${s}-crate`, points: -1 }) + return item } -export function cook(from: string, to?: string) { - const i = from.endsWith("-pot") ? from : from + "-pot" - const o = (to ?? ("cooked-" + from)) + "-pot" - if (!from.endsWith("-pot")) out({ action: "instant", inputs: ["pot", from], outputs: [i] }) - out({ action: "passive", duration: 20, revert_duration: 40, tile: "stove", inputs: [i], outputs: [o] }) - out({ action: "passive", duration: 5, revert_duration: 10, tile: "stove", inputs: [o], outputs: ["burned-pot"], warn: true }) +function cut(s: Item, two?: boolean): Item { + const o = new Item(`sliced-${s.name}`, s.container) + out({ action: "active", inputs: [s], outputs: [o, two ? o : null], tile: "cuttingboard", duration: 2 }) + return o } -export function process(from: string, to?: string) { - const i = from.endsWith("-foodprocessor") ? from : from + "-foodprocessor" - const o = (to ?? (from + "-juice")) + "-foodprocessor" - if (!from.endsWith("-foodprocessor")) out({ action: "instant", inputs: ["foodprocessor", from], outputs: [i] }) - out({ action: "passive", duration: 5, inputs: [i], outputs: [o] }) +function cook(s: Item, duration = 20): Item { + const o = new Item(`cooked-${s.name}`, s.container) + out({ action: "passive", duration, tile: "stove", inputs: [s], outputs: [o] }) + out({ action: "passive", duration: duration / 3, revert_duration: 20, tile: "stove", inputs: [o], outputs: [new Item("burned", POT)], warn: true }) + return o } -export function bake(from: string, to?: string) { - const o = (to ?? ("baked-" + from)) - out({ action: "passive", duration: 25, tile: "oven", inputs: [from], outputs: [o] }) - out({ action: "passive", duration: 15, revert_duration: 20, tile: "oven", inputs: [o], outputs: ["burned"], warn: true }) +function bake(s: Item, duration = 25): Item { + const o = new Item(`sliced-${s.name}`, s.container) + out({ action: "passive", duration, tile: "oven", inputs: [s], outputs: [o] }) + out({ action: "passive", duration: duration / 2, revert_duration: 20, tile: "oven", inputs: [o], outputs: [new Item("burned")], warn: true }) + return o } -export function crate(item: string) { - out({ action: "instant", tile: item + "-crate", inputs: [], outputs: [item], points: -1 }) +function freeze(s: Item): Item { + const o = new Item(`frozen-${s.name}`, s.container) + out({ action: "passive", duration: 25, tile: "freezer", inputs: [s], outputs: [o] }) + return o } - -export function get_container(ifull: string): [string, string | null] { - const iparts = ifull.split("-") - const ic = iparts.pop() - if (ic && iparts.length && ["pot", "plate", "foodprocessor", "glass"].includes(ic)) return [iparts.join("-"), ic] - return [ifull, null] +function process(s: Item): Item { + const o = new Item(`processed-${s.name}`, s.container) + out({ action: "passive", duration: 5, inputs: [s], outputs: [o] }) + return o } - -export function combine(container: string, ...inputs: string[]) { - const open = inputs.map(ifull => { - const [i, ic] = get_container(ifull) - out({ action: "instant", inputs: [container, ifull], outputs: [i + "-" + container, ic] }) - return [i] - }) +function container_add(base: Item, add: Item): Item { + const o = new Item("!!!", base.container) + out({ action: "instant", inputs: [base, add], outputs: [o, add.container] }) + return o +} +function combine(c: Container, ...items: Item[]): Item { + const open = items.map(i => (i.tr(c), [i.name])) const seen = new Set<string>() + let result while (1) { const cur = open.pop() if (!cur) break; - const c = cur.join("-") + "-" + container - for (const ifull of inputs) { - const [i, ic] = get_container(ifull) - if (cur.includes(i)) continue - const rkey = cur.join("-") + "#" + i + for (const new_item of items) { + if (cur.includes(new_item.name)) continue + const rkey = cur.join("-") + "#" + new_item + if (seen.has(rkey)) continue seen.add(rkey) - const parts = [...cur, i] + const parts = [...cur, new_item.name] parts.sort() - const o = parts.join("-") + "-" + container open.push(parts) + const i = new Item(cur.join("-"), c) + const o = new Item(parts.join("-"), c) + if (parts.length == items.length) result = o out({ action: "instant", - inputs: [c, ifull], - outputs: [o, ic] + inputs: [i, new_item], + outputs: [o, new_item.container] }) } } + return result! +} +function edible(...items: Item[]) { + for (const i of items) { + out({ action: "demand", inputs: [i], outputs: [i.container?.dispose ?? i.container], duration: 10 }) + } +} +function either(a: Item, b: Item) { + if (a.name != b.name) throw new Error("either options are named differently"); + if (a.container != b.container) throw new Error("either options are contained differently"); + return a +} +function sink_fill(c: Container) { + const o = new Item("water", c) + out({ action: "instant", inputs: [c], outputs: [o], tile: "sink" }) + return o } +out({ action: "active", duration: 2, tile: "sink", inputs: [new Container("dirty-plate")], outputs: [PL] }) -if (import.meta.main) { - out({ action: "active", duration: 2, tile: "sink", inputs: ["dirty-plate"], outputs: ["plate"] }) - - crate("tomato") - crate("raw-steak") - crate("flour") - crate("leek") - - cut("tomato") - cook("raw-steak", "steak") - - // bread - process("flour", "dough") - out({ action: "instant", inputs: ["dough-foodprocessor"], outputs: ["foodprocessor", "dough"] }) - bake("dough", "bread") - cut("bread", "bread-slice", "bread-slice") - - // tomato soup - process("tomato") - combine("pot", "leek", "tomato-juice-foodprocessor") - cook("leek-tomato-juice-pot", "tomato-soup") - out({ action: "instant", inputs: ["tomato-soup-pot", "plate"], outputs: ["pot", "tomato-soup-plate"] }) +const tomato = crate("tomato") +const raw_steak = crate("raw-steak") +const flour = crate("flour") +const leek = crate("leek") +const rice = crate("rice") +const fish = crate("fish") +const coconut = crate("coconut") +const strawberry = crate("strawberry") - combine("plate", "steak-pot", "sliced-tomato", "bread-slice") +// Bread +const dough = process(flour.tr(FP)).as("dough").tr() +const bread = bake(dough).as("bread") +const bread_slice = cut(bread, true).as("bread-slice") +edible(bread) - edible("steak-plate") - edible("bread-slice-steak-plate") - edible("bread-slice-sliced-tomato-plate") - edible("bread-slice-sliced-tomato-steak-plate") - out({ action: "demand", inputs: ["bread"], outputs: [], duration: 0 }) +// Burger +const steak_pot = cook(raw_steak.tr(POT)).as("steak") +const sliced_tomato = cut(tomato).as("sliced-tomato") +const burger = combine(PL, steak_pot, sliced_tomato, bread_slice) +const tomato_toast = combine(PL, sliced_tomato, bread_slice) +edible(burger, tomato_toast) - crate("rice") - crate("fish") - crate("coconut") - crate("strawberry") +// Soup +const tomato_juice = process(tomato.tr(FP)).as("tomato-juice") +const leek_tj_pot = combine(POT, leek, tomato_juice) +const tomato_soup_plate = cook(leek_tj_pot).as("tomato-soup").tr(PL) +edible(tomato_soup_plate) - // Rice and nigiri - cut("fish") - cook("rice") - out({ action: "instant", inputs: ["sliced-fish", "cooked-rice-pot"], outputs: ["nigiri", "pot"] }) +// Rice and nigiri +const nigiri = container_add(cut(fish), cook(rice.tr(POT))).as("nigiri").tr(PL) +edible(nigiri) - out({ action: "instant", inputs: ["plate", "cooked-rice-pot"], outputs: ["cooked-rice-plate", "pot"] }) - edible("cooked-rice-plate") - edible("nigiri") +// coconut milk and strawberry puree +const strawberry_puree = process(strawberry.tr(FP)).as("strawberry-puree") +const milk = process(coconut.tr(FP)).as("milk") +const strawberry_shake = either( + process(container_add(milk, strawberry).as("coconut-strawberry-puree")).as("strawberry-shake"), + process(container_add(strawberry_puree, coconut).as("milk-strawberry")).as("strawberry-shake") +) - // coconut milk and strawberry puree - process("strawberry", "strawberry-puree") - process("coconut", "milk") - out({ action: "instant", inputs: ["milk-foodprocessor", "strawberry"], outputs: ["strawberry-milk-foodprocessor"] }) - out({ action: "instant", inputs: ["strawberry-puree-foodprocessor", "coconut"], outputs: ["strawberry-milk-foodprocessor"] }) - process("strawberry-milk-foodprocessor", "strawberrymilk") - process("coconut-strawberry-puree-foodprocessor", "strawberrymilk") +// Icecream +const strawberry_icecream = freeze(strawberry_shake).as("strawberry-icecream").tr(PL) +edible(strawberry_icecream) - // icecream - out({ action: "passive", inputs: ["strawberrymilk-foodprocessor"], outputs: ["strawberry-icecream-foodprocessor"], tile: "freezer", duration: 20 }) - out({ action: "instant", inputs: ["strawberry-icecream-foodprocessor", "plate"], outputs: ["foodprocessor", "strawberry-icecream-plate"] }) - edible("strawberry-icecream-plate") +// Mochi +const rice_flour = process(rice.tr(FP)).as("rice-flour") +const mochi_dough = cook(rice_flour.tr(POT), 5).as("mochi-dough") +const strawberry_mochi = container_add(strawberry, mochi_dough).as("strawberry-mochi") +edible(strawberry_mochi) - // drinks - out({ action: "instant", inputs: ["glass"], outputs: ["water-glass"], tile: "sink" }) - out({ action: "instant", inputs: ["glass", "milk-foodprocessor"], outputs: ["milk-glass", "foodprocessor"] }) - out({ action: "instant", inputs: ["glass", "strawberrymilk-foodprocessor"], outputs: ["strawberrymilk-glass", "foodprocessor"] }) +// Drinks +edible( + strawberry_shake.tr(GL), + sink_fill(GL) +) - edible("water-glass") - edible("strawberrymilk-glass") +auto_trash() +finish() - auto_trash() -} diff --git a/server/src/data/mod.rs b/server/src/data/mod.rs index 6df60535..0f1976b9 100644 --- a/server/src/data/mod.rs +++ b/server/src/data/mod.rs @@ -369,3 +369,20 @@ impl Gamedata { .map(|(i, e)| (RecipeIndex(i), e)) } } +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +*/
\ No newline at end of file diff --git a/server/src/entity/portal.rs b/server/src/entity/portal.rs index e0195b9f..092a8da5 100644 --- a/server/src/entity/portal.rs +++ b/server/src/entity/portal.rs @@ -1,3 +1,20 @@ +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +*/ use super::EntityT; use crate::{ data::Gamedata, diff --git a/server/src/state.rs b/server/src/state.rs index 215f9a1b..347e2a92 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -15,13 +15,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -use std::time::Duration; - use crate::{data::DataIndex, game::Game}; use anyhow::{anyhow, bail, Result}; use clap::{Parser, ValueEnum}; use hurrycurry_protocol::{Message, PacketC, PacketS, PlayerID}; use log::{debug, trace}; +use std::time::Duration; use tokio::sync::broadcast::Sender; pub struct State { diff --git a/test-client/tiles.ts b/test-client/tiles.ts index 046d8297..fee27c9f 100644 --- a/test-client/tiles.ts +++ b/test-client/tiles.ts @@ -114,8 +114,7 @@ const foodprocessor = [circle(0.35, "rgb(86, 168, 189)", "rgb(88, 222, 255)", 0. const burned = [circle(0.3, "rgb(61, 18, 0)"), cross(0.2, "red", 0.02)] const leek = [circle(0.3, "rgb(50, 133, 17)")] -export const FALLBACK_ITEM: Component[] = [circle(0.3, "#f0f")]; -export const ITEMS: { [key: string]: Component[] } = { +const ITEMS: { [key: string]: Component[] } = { "pot": pot, "foodprocessor": foodprocessor, "leek": leek, @@ -158,8 +157,7 @@ const floor = [base("#333", "#222", 0.05)]; const counter = [base("rgb(182, 172, 164)")]; const crate = (i: string) => [base("#60701e", "#b9da37", 0.05), ...ITEMS[i]]; -export const FALLBACK_TILE: Component[] = [base("#f0f")]; -export const TILES: { [key: string]: Component[] } = { +const TILES: { [key: string]: Component[] } = { "floor": floor, "table": table, "door": [...floor, door], @@ -184,3 +182,41 @@ export const TILES: { [key: string]: Component[] } = { "tomato-crate": crate("tomato"), "leek-crate": crate("leek"), } + +function debug_label(ctx: CanvasRenderingContext2D, name: string) { + ctx.save() + ctx.font = "10px sans-serif" + ctx.fillStyle = "white" + ctx.strokeStyle = "black" + ctx.lineWidth = 1 + ctx.textAlign = "center" + ctx.textBaseline = "middle" + ctx.scale(0.03, 0.03) + ctx.strokeText(name, 0, 0) + ctx.fillText(name, 0, 0) + ctx.restore() +} + +export function draw_item_sprite(ctx: CanvasRenderingContext2D, name: string) { + const comps = ITEMS[name] + if (comps) { + for (const c of comps) { + c(ctx) + } + } else { + circle(0.4, "#f0f")(ctx) + debug_label(ctx, name) + } +} +export function draw_tile_sprite(ctx: CanvasRenderingContext2D, name: string) { + const comps = TILES[name] + if (comps) { + for (const c of comps) { + c(ctx) + } + } else { + base("#f0f")(ctx) + debug_label(ctx, name) + } + +}
\ No newline at end of file diff --git a/test-client/visual.ts b/test-client/visual.ts index d3f8a8b0..a5e603c5 100644 --- a/test-client/visual.ts +++ b/test-client/visual.ts @@ -17,7 +17,7 @@ */ import { ItemData, MessageData, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, demands_completed, demands_failed, get_interact_target, global_message, interact_active_anim, interact_possible_anim, interact_target_anim, items_removed, keys_down, my_id, nametag_scale_anim, players, points, tiles, time_remaining } from "./main.ts"; import { PLAYER_SIZE } from "./movement.ts"; -import { FALLBACK_TILE, ITEMS, TILES, FALLBACK_ITEM } from "./tiles.ts"; +import { draw_item_sprite, draw_tile_sprite } from "./tiles.ts"; import { V2, ceil_v2, floor_v2 } from "./util.ts"; export function draw_wait(text: string) { @@ -109,10 +109,7 @@ function draw_debug() { function draw_tile(tile: TileData) { ctx.save() ctx.translate(tile.x + 0.5, tile.y + 0.5) - const comps = TILES[data.tile_names[tile.kind]] ?? FALLBACK_TILE - for (const c of comps) { - c(ctx) - } + draw_tile_sprite(ctx, data.tile_names[tile.kind]) ctx.restore() } @@ -120,10 +117,7 @@ function draw_item(item: ItemData) { ctx.save() ctx.translate(item.x, item.y) if (item.remove_anim) ctx.scale(1 - item.remove_anim, 1 - item.remove_anim) - const comps = ITEMS[data.item_names[item.kind]] ?? FALLBACK_ITEM - for (const c of comps) { - c(ctx) - } + draw_item_sprite(ctx, data.item_names[item.kind]) if (item.progress !== null && item.progress !== undefined) { ctx.fillStyle = item.progress_warn ? "rgba(230, 58, 58, 0.66)" : "rgba(115, 230, 58, 0.66)" ctx.fillRect(-0.5, -0.5, 1, item.progress) @@ -226,8 +220,7 @@ function draw_message(m: MessageData) { ctx.fill() ctx.translate(0, -1) - const comps = ITEMS[data.item_names[m.inner.item]] ?? FALLBACK_ITEM - for (const c of comps) c(ctx) + draw_item_sprite(ctx, data.item_names[m.inner.item]) ctx.translate(0, 1) } if ("text" in m.inner) { |