aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYING8
-rw-r--r--README.md1
-rw-r--r--client/.gitignore1
-rw-r--r--client/audio/play_random.gd2
-rw-r--r--client/audio/sound.gd2
-rw-r--r--client/game.gd34
-rw-r--r--client/global.gd2
-rw-r--r--client/gui/components/blur_setup.gd2
-rw-r--r--client/gui/components/controller_button.gd2
-rw-r--r--client/gui/components/message/chat_message.gd2
-rw-r--r--client/gui/components/message/chat_message.tscn2
-rw-r--r--client/gui/components/message/item/item_message.gd2
-rw-r--r--client/gui/components/message/item/item_render.gd2
-rw-r--r--client/gui/components/message/renderer.gd30
-rw-r--r--client/gui/components/smart_margin_container.gd2
-rw-r--r--client/gui/components/touch_scroll_container.gd2
-rw-r--r--client/gui/menus/book/book.gd4
-rw-r--r--client/gui/menus/book/diagram.gd7
-rw-r--r--client/gui/menus/character.gd6
-rw-r--r--client/gui/menus/character.tscn2
-rw-r--r--client/gui/menus/chat.gd2
-rw-r--r--client/gui/menus/chat.tscn2
-rw-r--r--client/gui/menus/entry.gd2
-rw-r--r--client/gui/menus/error.gd2
-rw-r--r--client/gui/menus/error.tscn4
-rw-r--r--client/gui/menus/game.gd18
-rw-r--r--client/gui/menus/ingame.gd2
-rw-r--r--client/gui/menus/ingame.tscn2
-rw-r--r--client/gui/menus/main/about.gd6
-rw-r--r--client/gui/menus/main/background.gd2
-rw-r--r--client/gui/menus/main/clouds.gdshader2
-rw-r--r--client/gui/menus/main/main.gd6
-rw-r--r--client/gui/menus/main/main.tscn2
-rw-r--r--client/gui/menus/main/play.gd2
-rw-r--r--client/gui/menus/main/play.tscn2
-rw-r--r--client/gui/menus/main/server_list_item.gd2
-rw-r--r--client/gui/menus/map_selector/map_details_selector.gd120
-rw-r--r--client/gui/menus/map_selector/map_details_selector.gd.uid1
-rw-r--r--client/gui/menus/map_selector/map_details_selector.tscn112
-rw-r--r--client/gui/menus/map_selector/map_list_item.gd28
-rw-r--r--client/gui/menus/map_selector/map_list_item.gd.uid1
-rw-r--r--client/gui/menus/map_selector/map_list_item.tscn59
-rw-r--r--client/gui/menus/map_selector/map_selector.gd43
-rw-r--r--client/gui/menus/map_selector/map_selector.gd.uid1
-rw-r--r--client/gui/menus/map_selector/map_selector.tscn76
-rw-r--r--client/gui/menus/menu.gd13
-rw-r--r--client/gui/menus/popup.gd2
-rw-r--r--client/gui/menus/popup.tscn2
-rw-r--r--client/gui/menus/popup_large.gd2
-rw-r--r--client/gui/menus/popup_large.tscn2
-rw-r--r--client/gui/menus/rating/rating.gd2
-rw-r--r--client/gui/menus/rating/rating.tscn4
-rw-r--r--client/gui/menus/scoreboard.gd2
-rw-r--r--client/gui/menus/settings/button_setting.gd2
-rw-r--r--client/gui/menus/settings/dropdown_setting.gd2
-rw-r--r--client/gui/menus/settings/game_setting.gd2
-rw-r--r--client/gui/menus/settings/input/input_manager.gd2
-rw-r--r--client/gui/menus/settings/input/input_setting.gd2
-rw-r--r--client/gui/menus/settings/input/input_value_node.gd2
-rw-r--r--client/gui/menus/settings/number_setting.gd2
-rw-r--r--client/gui/menus/settings/path_setting.gd2
-rw-r--r--client/gui/menus/settings/preset_row.gd2
-rw-r--r--client/gui/menus/settings/range_setting.gd2
-rw-r--r--client/gui/menus/settings/settings.gd6
-rw-r--r--client/gui/menus/settings/settings.tscn2
-rw-r--r--client/gui/menus/settings/settings_category.gd2
-rw-r--r--client/gui/menus/settings/settings_root.gd2
-rw-r--r--client/gui/menus/settings/settings_row.gd2
-rw-r--r--client/gui/menus/settings/settings_row.tscn4
-rw-r--r--client/gui/menus/settings/text_setting.gd2
-rw-r--r--client/gui/menus/settings/toggle_setting.gd2
-rw-r--r--client/gui/menus/setup/hairstyle_preview.gd2
-rw-r--r--client/gui/menus/setup/setup.gd2
-rw-r--r--client/gui/menus/transition/scene_transition.gd2
-rw-r--r--client/gui/overlays/announce_title.gd2
-rw-r--r--client/gui/overlays/chat.gd2
-rw-r--r--client/gui/overlays/chat.tscn2
-rw-r--r--client/gui/overlays/controls_visualization/controller/controller_explanation.gd2
-rw-r--r--client/gui/overlays/controls_visualization/device_explanation.gd2
-rw-r--r--client/gui/overlays/controls_visualization/explanation.gd2
-rw-r--r--client/gui/overlays/controls_visualization/explanation.tscn2
-rw-r--r--client/gui/overlays/controls_visualization/keyboard/keyboard_explanation.gd2
-rw-r--r--client/gui/overlays/debug/debug.gd2
-rw-r--r--client/gui/overlays/debug/debug_pie.gd2
-rw-r--r--client/gui/overlays/lobby/lobby.gd177
-rw-r--r--client/gui/overlays/lobby/lobby.gd.uid1
-rw-r--r--client/gui/overlays/lobby/lobby.tscn181
-rw-r--r--client/gui/overlays/lobby/player.gd2
-rw-r--r--client/gui/overlays/lobby/player.tscn4
-rw-r--r--client/gui/overlays/overlays.gd2
-rw-r--r--client/gui/overlays/overlays.tscn4
-rw-r--r--client/gui/overlays/pinned_messages.gd2
-rw-r--r--client/gui/overlays/popup_message/popup_message.gd2
-rw-r--r--client/gui/overlays/popup_message/popup_message.tscn2
-rw-r--r--client/gui/overlays/popup_message/server_message.gd8
-rw-r--r--client/gui/overlays/score.gd2
-rw-r--r--client/gui/overlays/score.tscn2
-rw-r--r--client/gui/resources/materials/dark_blur_material.tres2
-rw-r--r--client/gui/resources/shaders/blur_mix.gdshader3
-rw-r--r--client/gui/resources/shaders/clouds_canvas_item.gdshader2
-rw-r--r--client/gui/resources/shaders/grayscale.gdshader2
-rw-r--r--client/gui/resources/shaders/printed.gdshader2
-rw-r--r--client/gui/resources/style/error_focus_style.tres10
-rw-r--r--client/gui/resources/style/error_normal_style.tres10
-rw-r--r--client/gui/resources/style/focus_style.tres10
-rw-r--r--client/gui/resources/style/hover_style.tres10
-rw-r--r--client/gui/resources/style/normal_style.tres10
-rw-r--r--client/gui/resources/style/panel_button_backround_style.tres8
-rw-r--r--client/gui/resources/style/panel_style.tres8
-rw-r--r--client/gui/resources/style/square_panel_override.tres (renamed from client/gui/resources/style/lobby_panel_override.tres)0
-rw-r--r--client/gui/resources/theme/default.tres (renamed from client/gui/resources/theme/theme.tres)5
l---------client/locale_book1
-rw-r--r--client/map/auto_setup/environment_setup.gd2
-rw-r--r--client/map/auto_setup/light_setup.gd2
-rw-r--r--client/map/auto_setup/sky_light_setup.gd2
-rw-r--r--client/map/effects/angry/angry.gd (renamed from client/player/particles/effect.gd)26
-rw-r--r--client/map/effects/angry/angry.gd.uid1
-rw-r--r--client/map/effects/angry/angry.tscn63
-rw-r--r--client/map/effects/angry/angry.webp (renamed from client/player/particles/angry/angry.webp)bin5022 -> 5022 bytes
-rw-r--r--client/map/effects/angry/angry.webp.import (renamed from client/player/particles/angry/angry.webp.import)6
-rw-r--r--client/map/effects/effect.gd (renamed from client/map/tiles/counter_base.gd)21
-rw-r--r--client/map/effects/effect.gd.uid (renamed from client/player/particles/effect.gd.uid)0
-rw-r--r--client/map/effects/effect_factory.gd34
-rw-r--r--client/map/effects/effect_factory.gd.uid1
-rw-r--r--client/map/effects/points/points.gd30
-rw-r--r--client/map/effects/points/points.gd.uid1
-rw-r--r--client/map/effects/points/points.tscn90
-rw-r--r--client/map/effects/satisfied/satisfied.gd24
-rw-r--r--client/map/effects/satisfied/satisfied.gd.uid1
-rw-r--r--client/map/effects/satisfied/satisfied.tscn (renamed from client/player/particles/satisfied/stars.tscn)13
-rw-r--r--client/map/effects/satisfied/star.webp (renamed from client/player/particles/satisfied/star.webp)bin5818 -> 5818 bytes
-rw-r--r--client/map/effects/satisfied/star.webp.import (renamed from client/player/particles/satisfied/star.webp.import)6
-rw-r--r--client/map/item_factory.gd2
-rw-r--r--client/map/items/basket.gd2
-rw-r--r--client/map/items/bun.gd2
-rw-r--r--client/map/items/burned.gd2
-rw-r--r--client/map/items/cuttable.gd2
-rw-r--r--client/map/items/food_processor.gd2
-rw-r--r--client/map/items/food_processor_fill.gd2
-rw-r--r--client/map/items/generic_item.gd2
-rw-r--r--client/map/items/glass.gd2
-rw-r--r--client/map/items/glass_fill.gd2
-rw-r--r--client/map/items/glass_items.gd2
-rw-r--r--client/map/items/icecream.gd2
-rw-r--r--client/map/items/item.gd2
-rw-r--r--client/map/items/mochi.gd2
-rw-r--r--client/map/items/pan.gd2
-rw-r--r--client/map/items/pizza.gd2
-rw-r--r--client/map/items/plate.gd2
-rw-r--r--client/map/items/plate_fill.gd2
-rw-r--r--client/map/items/pot.gd2
-rw-r--r--client/map/items/pot_fill.gd2
-rw-r--r--client/map/items/pot_fill_transparent.gd2
-rw-r--r--client/map/items/rolled_dough.gd2
-rw-r--r--client/map/items/unknown_item.gd2
-rw-r--r--client/map/kitchen_background.gd2
-rw-r--r--client/map/map.gd22
-rw-r--r--client/map/particles/checkmark/checkmark.gd (renamed from client/player/particles/checkmark/checkmark.gd)2
-rw-r--r--client/map/particles/checkmark/checkmark.gd.uid (renamed from client/player/particles/checkmark/checkmark.gd.uid)0
-rw-r--r--client/map/particles/checkmark/checkmark.svg (renamed from client/player/particles/checkmark/checkmark.svg)0
-rw-r--r--client/map/particles/checkmark/checkmark.svg.import (renamed from client/player/particles/checkmark/checkmark.svg.import)8
-rw-r--r--client/map/particles/checkmark/checkmark.tscn (renamed from client/player/particles/checkmark/checkmark.tscn)4
-rw-r--r--client/map/progress/progress.gd2
-rw-r--r--client/map/progress/progress.gdshader2
-rw-r--r--client/map/tile_factory.gd34
-rw-r--r--client/map/tiles/active_interact_counter.gd2
-rw-r--r--client/map/tiles/button/button.gd2
-rw-r--r--client/map/tiles/button/button_model.gd2
-rw-r--r--client/map/tiles/button_base.gd2
-rw-r--r--client/map/tiles/chair.gd2
-rw-r--r--client/map/tiles/conveyor.gd2
-rw-r--r--client/map/tiles/conveyor_direction.gdshader2
-rw-r--r--client/map/tiles/conveyor_model.gd2
-rw-r--r--client/map/tiles/counter.gd71
-rw-r--r--client/map/tiles/counter.gd.uid2
-rw-r--r--client/map/tiles/counter_base.gd.uid1
-rw-r--r--client/map/tiles/counter_like.gd80
-rw-r--r--client/map/tiles/counter_like.gd.uid1
-rw-r--r--client/map/tiles/counter_window.gd2
-rw-r--r--client/map/tiles/counter_window_conveyor.gd2
-rw-r--r--client/map/tiles/crate.gd4
-rw-r--r--client/map/tiles/cutting_board.gd2
-rw-r--r--client/map/tiles/deep_fryer.gd2
-rw-r--r--client/map/tiles/door.gd2
-rw-r--r--client/map/tiles/exterior_tree.gd2
-rw-r--r--client/map/tiles/fence.gd2
-rw-r--r--client/map/tiles/floor.gd2
-rw-r--r--client/map/tiles/floor.gdshader2
-rw-r--r--client/map/tiles/floor_like.gd2
-rw-r--r--client/map/tiles/floor_mesher.gd2
-rw-r--r--client/map/tiles/freezer.gd4
-rw-r--r--client/map/tiles/freezer_model.gd2
-rw-r--r--client/map/tiles/generic_tile.gd2
-rw-r--r--client/map/tiles/grass.gd9
-rw-r--r--client/map/tiles/grass_mesher.gd17
-rw-r--r--client/map/tiles/house_balcony.gd2
-rw-r--r--client/map/tiles/house_door.gd2
-rw-r--r--client/map/tiles/house_oriel.gd2
-rw-r--r--client/map/tiles/house_roof.gd2
-rw-r--r--client/map/tiles/house_roof_chimney.gd2
-rw-r--r--client/map/tiles/house_side.gd2
-rw-r--r--client/map/tiles/house_tile.gd2
-rw-r--r--client/map/tiles/house_wall.gd2
-rw-r--r--client/map/tiles/item_portal.gd7
-rw-r--r--client/map/tiles/lamp.gd2
-rw-r--r--client/map/tiles/light_tile.gd2
-rw-r--r--client/map/tiles/oven.gd4
-rw-r--r--client/map/tiles/oven_model.gd2
-rw-r--r--client/map/tiles/path.gd2
-rw-r--r--client/map/tiles/player_portal.gd2
-rw-r--r--client/map/tiles/portal.gd7
-rw-r--r--client/map/tiles/portal.gdshader2
-rw-r--r--client/map/tiles/rolling_board.gd2
-rw-r--r--client/map/tiles/screen/screen.gd2
-rw-r--r--client/map/tiles/screen/screen_model.gd2
-rw-r--r--client/map/tiles/sink.gd4
-rw-r--r--client/map/tiles/sink_particles.gd2
-rw-r--r--client/map/tiles/stove.gd4
-rw-r--r--client/map/tiles/street.gd2
-rw-r--r--client/map/tiles/table.gd2
-rw-r--r--client/map/tiles/tile.gd3
-rw-r--r--client/map/tiles/trash.gd4
-rw-r--r--client/map/tiles/unknown_tile.gd2
-rw-r--r--client/map/tiles/wall.gd2
-rw-r--r--client/map/tiles/wall_tile.gd2
-rw-r--r--client/map/tiles/window.gd2
-rw-r--r--client/message_parser.gd2
-rw-r--r--client/multiplayer.gd17
-rw-r--r--client/player/camera_recorder.gd2
-rw-r--r--client/player/character/character.gd2
-rw-r--r--client/player/character/headwear/cat_ears.gd2
-rw-r--r--client/player/chat_bubble.gd2
-rw-r--r--client/player/controllable_player.gd2
-rw-r--r--client/player/follow_camera.gd17
-rw-r--r--client/player/item_bubble.gd2
-rw-r--r--client/player/marker/interact_marker.gdshader2
-rw-r--r--client/player/marker/marker.gd2
-rw-r--r--client/player/mirror.gd2
-rw-r--r--client/player/onscreen_controls/controls.gd5
-rw-r--r--client/player/onscreen_controls/virtual_joystick.gd2
-rw-r--r--client/player/particles/angry/angry.tscn41
-rw-r--r--client/player/particles/effect.tscn36
-rw-r--r--client/player/player.gd20
-rw-r--r--client/project.godot9
-rw-r--r--client/service/discover.gd2
-rw-r--r--client/service/editor.gd2
-rw-r--r--client/service/server.gd2
-rw-r--r--client/service/service.gd2
-rw-r--r--client/system/cli.gd4
-rw-r--r--client/system/disable_wrong_joypads.gd2
-rw-r--r--client/system/gltf_apply_visibility.gd4
-rw-r--r--client/system/profile.gd2
-rw-r--r--client/system/render_tool.gd30
-rw-r--r--client/system/server_list.gd2
-rw-r--r--client/system/settings.gd2
-rw-r--r--client/system/translation_manager.gd2
-rw-r--r--data/.gitignore1
-rw-r--r--data/index.yaml46
-rw-r--r--data/maps/5star.yaml5
-rw-r--r--data/maps/anticurry.yaml5
-rw-r--r--data/maps/auto_sushi.yaml2
-rw-r--r--data/maps/bar.yaml2
-rw-r--r--data/maps/bbq.yaml35
-rw-r--r--data/maps/burgers_inc.yaml2
-rw-r--r--data/maps/bus.yaml2
-rw-r--r--data/maps/campaign/01.yaml4
-rw-r--r--data/maps/campaign/02.yaml4
-rw-r--r--data/maps/campaign/lobby.yaml2
-rw-r--r--data/maps/ctf.yaml4
-rw-r--r--data/maps/debug.yaml4
-rw-r--r--data/maps/debug2.yaml2
-rw-r--r--data/maps/duplex.yaml11
-rw-r--r--data/maps/junior.yaml5
-rw-r--r--data/maps/line.yaml2
-rw-r--r--data/maps/lobby.yaml7
-rw-r--r--data/maps/paris.yaml2
-rw-r--r--data/maps/ring.yaml47
-rw-r--r--data/maps/salad.yaml50
-rw-r--r--data/maps/scattered.yaml2
-rw-r--r--data/maps/senior.yaml33
-rw-r--r--data/maps/sophomore.yaml2
-rw-r--r--data/maps/star.yaml2
-rw-r--r--data/maps/station.yaml2
-rw-r--r--data/maps/streetfood.yaml2
-rw-r--r--data/maps/sushibar.yaml2
-rw-r--r--data/maps/tag.yaml4
-rw-r--r--data/maps/teeny.yaml2
-rw-r--r--data/maps/zigzag.yaml2
-rw-r--r--data/palettes.yaml2
-rw-r--r--[-rwxr-xr-x]data/recipes/anticurry.sed1
-rw-r--r--data/recipes/default.js2
-rw-r--r--data/tiles.yaml10
-rw-r--r--locale/ar.ini4
-rw-r--r--locale/bar.ini8
-rw-r--r--locale/de.ini8
-rw-r--r--locale/el.ini8
-rw-r--r--locale/en.ini14
-rw-r--r--locale/es.ini8
-rw-r--r--locale/eu.ini8
-rw-r--r--locale/fi.ini8
-rw-r--r--locale/fr.ini8
-rw-r--r--locale/it.ini8
-rw-r--r--locale/ja.ini8
-rw-r--r--locale/nl.ini8
-rw-r--r--locale/pl.ini8
-rw-r--r--locale/ru.ini8
-rw-r--r--locale/sv.ini8
-rw-r--r--locale/tr.ini8
-rw-r--r--locale/vi.ini8
-rw-r--r--locale/zh_Hans.ini8
-rw-r--r--locale/zh_Hant.ini8
-rw-r--r--makefile197
-rw-r--r--manifest.scm40
-rw-r--r--protocol.md15
-rw-r--r--server/book-export/src/book_html.css28
-rw-r--r--server/book-export/src/book_html.rs9
-rw-r--r--server/book-export/src/bpy_glb_render.py30
-rw-r--r--server/book-export/src/diagram_svg.rs2
-rw-r--r--server/book-export/src/main.rs12
-rw-r--r--server/bot/Cargo.toml2
-rw-r--r--server/bot/src/algos/customer.rs86
-rw-r--r--server/bot/src/algos/dishwasher.rs19
-rw-r--r--server/bot/src/algos/frank.rs2
-rw-r--r--server/bot/src/algos/mod.rs2
-rw-r--r--server/bot/src/algos/simple.rs239
-rw-r--r--server/bot/src/algos/waiter.rs56
-rw-r--r--server/bot/src/lib.rs11
-rw-r--r--server/bot/src/main.rs2
-rw-r--r--server/bot/src/pathfinding.rs50
-rw-r--r--server/bot/src/step.rs34
-rw-r--r--server/data/src/book/diagram_layout.rs2
-rw-r--r--server/data/src/book/mod.rs23
-rw-r--r--server/data/src/book/recipe_diagram.rs2
-rw-r--r--server/data/src/entities.rs5
-rw-r--r--server/data/src/filter_demands.rs44
-rw-r--r--server/data/src/lib.rs87
-rw-r--r--server/data/src/recipes.rs27
-rw-r--r--server/data/src/registry.rs13
-rw-r--r--server/discover/src/main.rs2
-rw-r--r--server/editor/src/main.rs17
-rw-r--r--server/game-core/src/gamedata_index.rs2
-rw-r--r--server/game-core/src/interaction.rs23
-rw-r--r--server/game-core/src/lib.rs49
-rw-r--r--server/game-core/src/network/mod.rs2
-rw-r--r--server/game-core/src/network/sync.rs2
-rw-r--r--server/game-core/src/network/tokio.rs2
-rw-r--r--server/game-core/src/spatial_index.rs2
-rw-r--r--server/locale-export/src/godot_csv.rs70
-rw-r--r--server/locale-export/src/json_pack.rs46
-rw-r--r--server/locale-export/src/main.rs309
-rw-r--r--server/locale-export/src/po.rs190
-rw-r--r--server/locale/src/error.rs2
-rw-r--r--server/locale/src/lib.rs2
-rw-r--r--server/locale/src/message.rs2
-rw-r--r--server/protocol/src/book.rs5
-rw-r--r--server/protocol/src/helpers.rs19
-rw-r--r--server/protocol/src/lib.rs53
-rw-r--r--server/protocol/src/movement.rs5
-rw-r--r--server/protocol/src/registry.rs2
-rw-r--r--server/registry/src/conn_test.rs2
-rw-r--r--server/registry/src/list.rs2
-rw-r--r--server/registry/src/lobby.rs11
-rw-r--r--server/registry/src/main.rs2
-rw-r--r--server/registry/src/register.rs2
-rw-r--r--server/replaytool/src/main.rs2
-rw-r--r--server/replaytool/src/record.rs2
-rw-r--r--server/replaytool/src/render.rs2
-rw-r--r--server/replaytool/src/replay.rs2
-rw-r--r--server/src/benchmark.rs35
-rw-r--r--server/src/commands.rs153
-rw-r--r--server/src/entity/bot.rs2
-rw-r--r--server/src/entity/button.rs2
-rw-r--r--server/src/entity/campaign.rs9
-rw-r--r--server/src/entity/conveyor.rs2
-rw-r--r--server/src/entity/ctf_minigame.rs15
-rw-r--r--server/src/entity/customers.rs2
-rw-r--r--server/src/entity/demand_sink.rs14
-rw-r--r--server/src/entity/environment_effect.rs2
-rw-r--r--server/src/entity/item_portal.rs2
-rw-r--r--server/src/entity/mod.rs31
-rw-r--r--server/src/entity/pedestrians.rs5
-rw-r--r--server/src/entity/player_portal.rs2
-rw-r--r--server/src/entity/player_portal_pair.rs2
-rw-r--r--server/src/entity/tag_minigame.rs2
-rw-r--r--server/src/entity/tram.rs2
-rw-r--r--server/src/entity/tutorial.rs122
-rw-r--r--server/src/lib.rs99
-rw-r--r--server/src/main.rs122
-rw-r--r--server/src/network/mdns.rs2
-rw-r--r--server/src/network/mod.rs2
-rw-r--r--server/src/network/register.rs2
-rw-r--r--server/src/network/upnp.rs2
-rw-r--r--server/src/scoreboard.rs29
-rw-r--r--server/src/server.rs150
-rw-r--r--server/src/state.rs55
-rw-r--r--server/src/vote.rs119
-rw-r--r--server/tools/src/diagram_dot.rs2
-rw-r--r--server/tools/src/graph.rs13
-rw-r--r--server/tools/src/graph_summary.rs13
-rw-r--r--server/tools/src/main.rs47
-rw-r--r--server/tools/src/map_linter.rs2
-rw-r--r--test-client/.gitignore5
-rw-r--r--test-client/announcement.ts77
-rw-r--r--test-client/assets/item_sprites.ini169
-rw-r--r--test-client/assets/tile_sprites.ini80
-rw-r--r--test-client/chat.ts69
-rw-r--r--test-client/connect_screen.ts53
-rw-r--r--test-client/index.html2
-rw-r--r--test-client/locale.ts35
-rw-r--r--test-client/main.ts634
-rw-r--r--test-client/map/camera.ts54
-rw-r--r--test-client/map/debug_events.ts86
-rw-r--r--test-client/map/effect.ts85
-rw-r--r--test-client/map/item.ts134
-rw-r--r--test-client/map/message.ts107
-rw-r--r--test-client/map/mod.ts43
-rw-r--r--test-client/map/particles.ts90
-rw-r--r--test-client/map/players.ts209
-rw-r--r--test-client/map/server_hints.ts58
-rw-r--r--test-client/map/sprites.ts215
-rw-r--r--test-client/map/tiles.ts65
-rw-r--r--test-client/movement.ts12
-rw-r--r--test-client/particles.ts60
-rw-r--r--test-client/pinned_messages.ts48
-rw-r--r--test-client/player_controller.ts174
-rw-r--r--test-client/protocol.ts50
-rw-r--r--test-client/quick_commands.ts44
-rw-r--r--test-client/score_display.ts48
-rw-r--r--test-client/server_message.ts57
-rw-r--r--test-client/sprites.ts259
-rw-r--r--test-client/system.ts3
-rw-r--r--test-client/util.ts2
-rw-r--r--test-client/visual.ts346
-rw-r--r--test-client/vote.ts12
434 files changed, 5540 insertions, 3409 deletions
diff --git a/COPYING b/COPYING
index 838e95b5..6e532b4c 100644
--- a/COPYING
+++ b/COPYING
@@ -1,8 +1,8 @@
Hurry Curry! - a game about cooking
-Copyright (C) 2025 metamuffin
-Copyright (C) 2025 nokoe
-Copyright (C) 2025 tpart
-Copyright (C) 2025 Hurry Curry! contributors
+Copyright (C) 2026 metamuffin
+Copyright (C) 2026 nokoe
+Copyright (C) 2026 tpart
+Copyright (C) 2026 Hurry Curry! Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
diff --git a/README.md b/README.md
index 36d7b8a2..8bcf9293 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ make clean # Remove build files
- [Godot](https://godotengine.org) (>=4.5)
- [Rustup](https://rustup.rs)
- (Optional) [Graphviz](https://graphviz.org/)
+ - (Optional) [Blender](https://blender.org/)
## Contributing
diff --git a/client/.gitignore b/client/.gitignore
index 03408ec6..867b4a38 100644
--- a/client/.gitignore
+++ b/client/.gitignore
@@ -3,3 +3,4 @@
/menu/book/book_*.webp*
/icons/*
!/icons/main.png
+/client.pck \ No newline at end of file
diff --git a/client/audio/play_random.gd b/client/audio/play_random.gd
index 4bab2b78..3196b963 100644
--- a/client/audio/play_random.gd
+++ b/client/audio/play_random.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/audio/sound.gd b/client/audio/sound.gd
index 5f9af4a1..579c0224 100644
--- a/client/audio/sound.gd
+++ b/client/audio/sound.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/game.gd b/client/game.gd
index 45cca6f9..157188e4 100644
--- a/client/game.gd
+++ b/client/game.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -18,7 +18,6 @@ extends Node3D
signal update_players(players: Dictionary)
signal data_updated()
-signal in_lobby_updated(in_lobby: bool)
signal join_state_updated(state: JoinState)
signal text_message(message: TextMessage)
signal update_tutorial_running(running: bool)
@@ -65,7 +64,6 @@ var in_lobby := false
var is_replay := false
var tutorial_running := false
var tutorial_queue := []
-var last_position = null # : Vector2?
var players: Dictionary[float, Player] = {}
@@ -74,7 +72,6 @@ var spectating_mode: SpectatingMode = SpectatingMode.CENTER
@onready var mp: Multiplayer = $Multiplayer
@onready var map: Map = $Map
# TODO move all of this somewhere else
-@onready var overlay_lobby: Lobby = $"../Overlays/Lobby"
@onready var overlay_score: Overlay = $"../Overlays/Score"
@onready var overlay_popup_message: PopupMessage = $"../Overlays/PopupMessage"
@onready var overlay_pinned_messages: PinnedItemMessages = $"../Overlays/PinnedMessages"
@@ -116,15 +113,13 @@ func handle_packet(p):
Global.hand_count_change.emit(Global.hand_count)
Global.last_map_name = Global.current_map_name
- Global.current_map_name = p["current_map"]
+ Global.current_map_name = p.metadata.name
data_updated.emit()
"add_player":
var player_instance: Player
if p.id == my_player_id:
player_instance = ControllablePlayer.new(p.id, p.name, p.position, p.character, p.class, self)
- in_lobby_updated.connect(player_instance.onscreen_controls.in_lobby_updated)
- player_instance.onscreen_controls.in_lobby_updated(in_lobby)
follow_camera.target = player_instance.movement_base
follow_camera.reset()
set_join_state(JoinState.JOINED)
@@ -146,7 +141,6 @@ func handle_packet(p):
if p.id == my_player_id:
set_join_state(JoinState.SPECTATING)
follow_camera.target = $Center
- last_position = null
for h in player.hand:
if h != null:
h.queue_free()
@@ -157,12 +151,7 @@ func handle_packet(p):
if not players.has(p.player): return
var player_instance: Player = players[p.player]
player_instance.update_position(p.pos, p.dir, p.rot, p.boost)
- if p.player == my_player_id: last_position = p.pos
- "movement_sync":
- if not players.has(my_player_id): return
- var player_instance: ControllablePlayer = players[my_player_id]
- if last_position != null:
- player_instance.position_ = last_position
+ if p.player == my_player_id and p.get("sync"): player_instance.position_ = p.pos
"move_item":
if "player" in p.from and "player" in p.to:
players[p.from.player[0]].pass_to(players[p.to.player[0]], int(p.from.player[1]), int(p.to.player[1]))
@@ -294,12 +283,13 @@ func handle_packet(p):
tutorial_queue.erase(player.current_item_message)
player.clear_text_message()
player.clear_item_message()
- player.clear_effect()
overlay_pinned_messages.clear_item(p.player)
- "effect2":
- if not "location" in p: return # ignore old server format
- if "player" in p.location: players[p.location.player[0]].effect_message(p.name)
- elif "tile" in p.location: pass # TODO create effect at tile
+ "effect":
+ var target
+ if "player" in p.location: target = players[p.location.player[0]].movement_base
+ elif "tile" in p.location: target = map.get_topmost_instance(p.location.tile).base
+ EffectFactory.play_effect(target, p)
+
"set_ingame":
in_lobby = p.lobby
overlay_score.set_ingame(p.state, p.lobby)
@@ -310,7 +300,6 @@ func handle_packet(p):
map.flush()
await get_parent()._menu_open()
map.autoflush = true
- in_lobby_updated.emit(in_lobby)
if not in_lobby and not is_replay and not Global.using_touch and not join_state == JoinState.SPECTATING:
var using_joypad: bool = Global.using_joypad
@@ -324,7 +313,6 @@ func handle_packet(p):
map.autoflush = false
await get_parent()._menu_exit()
- if in_lobby: overlay_lobby.select_map(0)
if join_state == JoinState.SPECTATING:
if in_lobby:
toggle_join()
@@ -365,6 +353,10 @@ func handle_packet(p):
overlay_announce_title.announce_start()
"book":
menu.submenu("res://gui/menus/book/book.tscn", BookMenu.BookData.new(self, p.data))
+ "map_selector":
+ menu.submenu("res://gui/menus/map_selector/map_selector.tscn", [maps, bot_algos])
+ _:
+ push_error("Received unrecognized menu type %s" % p.menu)
"server_message":
if p.error:
overlay_popup_message.display_server_msg(tr("c.error.server").format([MessageParser.new(p.message, self).result]))
diff --git a/client/global.gd b/client/global.gd
index c9db6be6..72fb2f2b 100644
--- a/client/global.gd
+++ b/client/global.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/components/blur_setup.gd b/client/gui/components/blur_setup.gd
index b24a94d8..89a6b322 100644
--- a/client/gui/components/blur_setup.gd
+++ b/client/gui/components/blur_setup.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/components/controller_button.gd b/client/gui/components/controller_button.gd
index 71f1332f..06de9bf3 100644
--- a/client/gui/components/controller_button.gd
+++ b/client/gui/components/controller_button.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/components/message/chat_message.gd b/client/gui/components/message/chat_message.gd
index 27403701..547b2651 100644
--- a/client/gui/components/message/chat_message.gd
+++ b/client/gui/components/message/chat_message.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/components/message/chat_message.tscn b/client/gui/components/message/chat_message.tscn
index 83e47a50..e09b16e6 100644
--- a/client/gui/components/message/chat_message.tscn
+++ b/client/gui/components/message/chat_message.tscn
@@ -1,7 +1,6 @@
[gd_scene format=3 uid="uid://bpc2qgsvcafhe"]
[ext_resource type="Script" uid="uid://6rprqelfdp3" path="res://gui/components/message/chat_message.gd" id="1_ey0qp"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_rx6vg"]
[sub_resource type="FontVariation" id="FontVariation_jfhbh"]
variation_embolden = 1.3
@@ -9,7 +8,6 @@ variation_embolden = 1.3
[node name="ChatMessage" type="VBoxContainer" unique_id=1631647881]
offset_right = 72.0
offset_bottom = 192.0
-theme = ExtResource("1_rx6vg")
script = ExtResource("1_ey0qp")
[node name="Sender" type="Label" parent="." unique_id=1014991872]
diff --git a/client/gui/components/message/item/item_message.gd b/client/gui/components/message/item/item_message.gd
index f5a97723..6ea0d1a0 100644
--- a/client/gui/components/message/item/item_message.gd
+++ b/client/gui/components/message/item/item_message.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/components/message/item/item_render.gd b/client/gui/components/message/item/item_render.gd
index 374644c3..0c488e64 100644
--- a/client/gui/components/message/item/item_render.gd
+++ b/client/gui/components/message/item/item_render.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/components/message/renderer.gd b/client/gui/components/message/renderer.gd
index 573775fe..fda880de 100644
--- a/client/gui/components/message/renderer.gd
+++ b/client/gui/components/message/renderer.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -16,23 +16,19 @@
class_name Renderer
extends SubViewportContainer
-enum Mode {
- ITEMS,
- TILES
-}
-
var current_object: Node3D = null
-var mode: Mode
-func setup_object(object_name: String):
+func setup_item(name: String):
+ if current_object: current_object.queue_free()
+ current_object = ItemFactory.produce(name, $SubViewport/Node3D/Base)
+ $SubViewport/Node3D/Camera3D.size = 1.
+ $SubViewport/Node3D/Base.add_child(current_object)
+
+func setup_tile(parts: Array):
if current_object: current_object.queue_free()
- match mode:
- Mode.ITEMS:
- current_object = ItemFactory.produce(object_name, $SubViewport/Node3D/Base)
- $SubViewport/Node3D/Camera3D.size = 1.
- Mode.TILES:
- var tf = TileFactory.new()
- current_object = tf.produce(object_name, Vector2i(0, 0), ["counter", "floor", "counter", null])
- current_object.translate(Vector3(-0.5, 0.0, -0.5))
- $SubViewport/Node3D/Camera3D.size = 2.
+ var tf = TileFactory.new()
+ current_object = Node3D.new()
+ tf.produce(current_object, parts, Vector2i(0, 0), [["counter"], ["floor"], ["counter"], []])
+ current_object.translate(Vector3(-0.5, 0.0, -0.5))
+ $SubViewport/Node3D/Camera3D.size = 2.
$SubViewport/Node3D/Base.add_child(current_object)
diff --git a/client/gui/components/smart_margin_container.gd b/client/gui/components/smart_margin_container.gd
index 046dc6f3..30a219f3 100644
--- a/client/gui/components/smart_margin_container.gd
+++ b/client/gui/components/smart_margin_container.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/components/touch_scroll_container.gd b/client/gui/components/touch_scroll_container.gd
index 292d084a..f262b99c 100644
--- a/client/gui/components/touch_scroll_container.gd
+++ b/client/gui/components/touch_scroll_container.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/book/book.gd b/client/gui/menus/book/book.gd
index 00837e9a..8a0e0db1 100644
--- a/client/gui/menus/book/book.gd
+++ b/client/gui/menus/book/book.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -23,6 +23,8 @@ var current_page := 0
@onready var first := $Margin/HBoxContainer/First/PanelContainer/MarginContainer
@onready var second := $Margin/HBoxContainer/Second/PanelContainer/MarginContainer
+static var TOOLS_WITH_COUNTER = ["cutting-board", "rolling-board", "deep-fryer", "sink", "book"]
+
@export var title_font: Font
@export var default_font: Font
diff --git a/client/gui/menus/book/diagram.gd b/client/gui/menus/book/diagram.gd
index 2a6727d7..b4ae3f30 100644
--- a/client/gui/menus/book/diagram.gd
+++ b/client/gui/menus/book/diagram.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -67,9 +67,8 @@ func redraw_images() -> void:
add_child(r)
r.position = n.position - Vector2(hsize, hsize)
match n.label.kind:
- MessageParser.Kind.ITEM: r.mode = Renderer.Mode.ITEMS
- MessageParser.Kind.TILE: r.mode = Renderer.Mode.TILES
- r.setup_object(n.label.result)
+ MessageParser.Kind.ITEM: r.setup_object(n.label.result)
+ MessageParser.Kind.TILE: r.setup_object([n.label.result])
renderers.push_back(r)
func scale() -> void:
diff --git a/client/gui/menus/character.gd b/client/gui/menus/character.gd
index bc667f1a..42b22918 100644
--- a/client/gui/menus/character.gd
+++ b/client/gui/menus/character.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -26,7 +26,7 @@ func _ready():
func _menu_music(): Sound.set_music("reflets-dans-leau", -3, true)
-func exit():
+func exit(exit_data = null):
if username_edit.text == "":
var popup_data := MenuPopup.Data.new()
popup_data.text = tr("c.error.empty_username")
@@ -36,7 +36,7 @@ func exit():
await submenu("res://gui/menus/popup.tscn", popup_data)
return
Profile.write("username", username_edit.text)
- super()
+ super(exit_data)
func _on_character_back_pressed():
modify_style(func m(current_style: Dictionary):
diff --git a/client/gui/menus/character.tscn b/client/gui/menus/character.tscn
index b3f00cc1..24d698c5 100644
--- a/client/gui/menus/character.tscn
+++ b/client/gui/menus/character.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://1f7xpirm5d28"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_ak2pw"]
[ext_resource type="Script" uid="uid://bglusga8l5c27" path="res://gui/menus/character.gd" id="1_brhd1"]
[ext_resource type="PackedScene" uid="uid://b3hhir2fvnunu" path="res://player/character/character.tscn" id="3_odq7n"]
[ext_resource type="PackedScene" uid="uid://bg2d78ycorcqk" path="res://gui/menus/transition/scene_transition.tscn" id="4_c0ocf"]
@@ -21,7 +20,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_ak2pw")
script = ExtResource("1_brhd1")
[node name="Node3D" type="Node3D" parent="." unique_id=1910363995]
diff --git a/client/gui/menus/chat.gd b/client/gui/menus/chat.gd
index aae76f82..df065ebc 100644
--- a/client/gui/menus/chat.gd
+++ b/client/gui/menus/chat.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/chat.tscn b/client/gui/menus/chat.tscn
index 43e20b0f..441f3eea 100644
--- a/client/gui/menus/chat.tscn
+++ b/client/gui/menus/chat.tscn
@@ -2,7 +2,6 @@
[ext_resource type="Script" uid="uid://cfweimyoq5vv0" path="res://gui/menus/chat.gd" id="1_gntkb"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="2_1au48"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="3_lrbjr"]
[ext_resource type="StyleBox" uid="uid://bw4jamyna1top" path="res://gui/resources/style/panel_style_sidebar.tres" id="4_d4nta"]
[ext_resource type="Script" uid="uid://cmncjc06kadpe" path="res://gui/components/blur_setup.gd" id="5_l1coj"]
[ext_resource type="Script" uid="uid://bd7bylb2t2m0" path="res://gui/components/touch_scroll_container.gd" id="6_ff15x"]
@@ -23,7 +22,6 @@ anchors_preset = 9
anchor_bottom = 1.0
offset_right = 296.0
grow_vertical = 2
-theme = ExtResource("3_lrbjr")
theme_override_styles/panel = ExtResource("4_d4nta")
script = ExtResource("5_l1coj")
diff --git a/client/gui/menus/entry.gd b/client/gui/menus/entry.gd
index 83dfa10a..2c5905fa 100644
--- a/client/gui/menus/entry.gd
+++ b/client/gui/menus/entry.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/error.gd b/client/gui/menus/error.gd
index 3cc5f865..1e122fac 100644
--- a/client/gui/menus/error.gd
+++ b/client/gui/menus/error.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/error.tscn b/client/gui/menus/error.tscn
index d0058b5d..ccfcaeb4 100644
--- a/client/gui/menus/error.tscn
+++ b/client/gui/menus/error.tscn
@@ -1,11 +1,10 @@
[gd_scene format=3 uid="uid://cimgn07lbcs4v"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_cabdu"]
[ext_resource type="PackedScene" uid="uid://l4vm07dtda4j" path="res://gui/menus/main/background.tscn" id="2_5fxol"]
[ext_resource type="Script" uid="uid://bl0n4atrdcogm" path="res://gui/menus/error.gd" id="2_dbe41"]
[ext_resource type="PackedScene" uid="uid://bg2d78ycorcqk" path="res://gui/menus/transition/scene_transition.tscn" id="4_1nbt3"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="4_hxkkd"]
-[ext_resource type="StyleBox" uid="uid://de80aw86emnql" path="res://gui/resources/style/lobby_panel_override.tres" id="5_42a6r"]
+[ext_resource type="StyleBox" uid="uid://de80aw86emnql" path="res://gui/resources/style/square_panel_override.tres" id="5_42a6r"]
[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="5_rfcg2"]
[node name="ErrorMenu" type="Control" unique_id=1514511445]
@@ -15,7 +14,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_cabdu")
script = ExtResource("2_dbe41")
[node name="MenuBackground" parent="." unique_id=2057939786 instance=ExtResource("2_5fxol")]
diff --git a/client/gui/menus/game.gd b/client/gui/menus/game.gd
index c742796c..5379d32d 100644
--- a/client/gui/menus/game.gd
+++ b/client/gui/menus/game.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -17,6 +17,7 @@ extends Menu
class_name GameMenu
@onready var game: Game = $Game
+@onready var overlays: Overlays = $Overlays
@onready var popup_message: PopupMessage = $Overlays/PopupMessage
@onready var chat_preview: ChatPreview = $Overlays/ChatPreview
@onready var pinned_items: PinnedItemMessages = $Overlays/PinnedMessages
@@ -25,7 +26,6 @@ func _ready():
get_tree().get_root().go_back_requested.connect(open_ingame_menu)
super()
transition.set_loading_text(tr("c.menu.game.connecting"))
- Settings.hook_changed_init("gameplay.first_person", self, func (_a): update_mouse_capture())
func _input(_event):
if Input.is_action_just_pressed("ui_menu"):
@@ -59,12 +59,16 @@ func get_shot_path(template: String) -> String:
var filename = template % Time.get_datetime_string_from_system()
return "%s/%s" % [path, filename]
+func _menu_open():
+ await super()
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+
func _menu_cover(state):
super(state)
+ overlays.visible = not state
game.mp.send_idle(state)
- game.follow_camera.disable_input_menu = state
- game.follow_camera.update_disable_input()
- update_mouse_capture()
+ game.follow_camera._disable_input = state
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if not covered else Input.MOUSE_MODE_VISIBLE
func _menu_exit():
await super()
@@ -72,10 +76,6 @@ func _menu_exit():
func _menu_music(): Sound.set_music(null)
-func update_mouse_capture():
- var cap = Settings.read("gameplay.first_person") and not covered
- Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if cap else Input.MOUSE_MODE_VISIBLE
-
func open_ingame_menu():
if popup != null: return
Sound.play_click()
diff --git a/client/gui/menus/ingame.gd b/client/gui/menus/ingame.gd
index f40bb119..809bac32 100644
--- a/client/gui/menus/ingame.gd
+++ b/client/gui/menus/ingame.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/ingame.tscn b/client/gui/menus/ingame.tscn
index c1636abc..eb5d5f1f 100644
--- a/client/gui/menus/ingame.tscn
+++ b/client/gui/menus/ingame.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://lxlgtjm8hw7v"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_2vmyh"]
[ext_resource type="Script" uid="uid://dyi2xohgxeybb" path="res://gui/menus/ingame.gd" id="2_0h3no"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="3_vvvlt"]
[ext_resource type="Script" uid="uid://cmncjc06kadpe" path="res://gui/components/blur_setup.gd" id="4_b6bm7"]
@@ -53,7 +52,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_2vmyh")
script = ExtResource("2_0h3no")
[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=697888286]
diff --git a/client/gui/menus/main/about.gd b/client/gui/menus/main/about.gd
index 00ea6563..f1f71ef6 100644
--- a/client/gui/menus/main/about.gd
+++ b/client/gui/menus/main/about.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -17,7 +17,7 @@
extends Menu
var authors := ["metamuffin", "nokoe", "tpart"]
-var contributors := ["sofviic", "BigBrotherNii", "Miner34"]
+var contributors := ["sofviic", "BigBrotherNii", "Miner34", "jnms"]
const cc_by_4 := "CC-BY 4.0"
const cc_by_3 := "CC-BY 3.0"
const cc_by_sa_4 := "CC-BY-SA 4.0"
@@ -155,7 +155,7 @@ func legal_text() -> String:
all.append_array(translators_list)
var text := "Hurry Curry! - a game about cooking\n"
- text += "[code]Copyright 2024, 2025 %s\n\n" % ", ".join(dedup_array(all))
+ text += "[code]Copyright 2024, 2025, 2026 %s\n\n" % ", ".join(dedup_array(all))
text += "%s[/code]\n\n" % AGPL_NOTICE
text += tr("c.legal.using_godot")
text += "\n\n[code]%s[/code]" % Engine.get_license_text()
diff --git a/client/gui/menus/main/background.gd b/client/gui/menus/main/background.gd
index 4eff583c..e4ca9e5b 100644
--- a/client/gui/menus/main/background.gd
+++ b/client/gui/menus/main/background.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/main/clouds.gdshader b/client/gui/menus/main/clouds.gdshader
index 8103f691..02073e7a 100644
--- a/client/gui/menus/main/clouds.gdshader
+++ b/client/gui/menus/main/clouds.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/client/gui/menus/main/main.gd b/client/gui/menus/main/main.gd
index 0dc6e724..584dd404 100644
--- a/client/gui/menus/main/main.gd
+++ b/client/gui/menus/main/main.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -30,9 +30,9 @@ func _menu_music(): Sound.set_music("reflets-dans-leau", 3, false)
func _menu_cover(state):
$side.visible = not state
-func exit():
+func exit(exit_data = null):
Sound.set_music(null)
- super()
+ super(exit_data)
func _on_quit_pressed():
quit()
diff --git a/client/gui/menus/main/main.tscn b/client/gui/menus/main/main.tscn
index c7196ff5..86d9eda2 100644
--- a/client/gui/menus/main/main.tscn
+++ b/client/gui/menus/main/main.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://dbj8508whxgwv"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_3qfu3"]
[ext_resource type="Script" uid="uid://bpiynadrmdd37" path="res://gui/menus/main/main.gd" id="2_xjnc3"]
[ext_resource type="PackedScene" uid="uid://l4vm07dtda4j" path="res://gui/menus/main/background.tscn" id="3_4evao"]
[ext_resource type="Material" uid="uid://2j8a0c0a2ta5" path="res://gui/resources/materials/blur_material.tres" id="4_nx4vf"]
@@ -20,7 +19,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_3qfu3")
script = ExtResource("2_xjnc3")
[node name="MenuBackground" parent="." unique_id=828608925 instance=ExtResource("3_4evao")]
diff --git a/client/gui/menus/main/play.gd b/client/gui/menus/main/play.gd
index bb2ecf3c..0100a556 100644
--- a/client/gui/menus/main/play.gd
+++ b/client/gui/menus/main/play.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/main/play.tscn b/client/gui/menus/main/play.tscn
index 7404b6cd..bad01b3d 100644
--- a/client/gui/menus/main/play.tscn
+++ b/client/gui/menus/main/play.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://c8url5fpttbem"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_cckds"]
[ext_resource type="Script" uid="uid://b126k2228nj4s" path="res://gui/menus/main/play.gd" id="2_phxx0"]
[ext_resource type="Material" uid="uid://2j8a0c0a2ta5" path="res://gui/resources/materials/blur_material.tres" id="3_fsbt7"]
[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="4_gst6r"]
@@ -20,7 +19,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_cckds")
script = ExtResource("2_phxx0")
support_anim = false
diff --git a/client/gui/menus/main/server_list_item.gd b/client/gui/menus/main/server_list_item.gd
index 1f829e13..4ec191a3 100644
--- a/client/gui/menus/main/server_list_item.gd
+++ b/client/gui/menus/main/server_list_item.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/map_selector/map_details_selector.gd b/client/gui/menus/map_selector/map_details_selector.gd
new file mode 100644
index 00000000..250c299b
--- /dev/null
+++ b/client/gui/menus/map_selector/map_details_selector.gd
@@ -0,0 +1,120 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2026 Hurry Curry! Contributors
+#
+# 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/>.
+#
+extends Menu
+class_name MapDetailsSelector
+
+const MAX_BOT_COUNT_PER_TYPE: int = 3
+
+var two_handed: bool
+var default_two_handed: bool
+var bots_enabled := false
+var bot_counts: Dictionary[String, int] = {}
+
+var bot_reset_buttons: Dictionary[String, Button] = {}
+var bot_inc_buttons: Dictionary[String, Button] = {}
+var bot_dec_buttons: Dictionary[String, Button] = {}
+
+@onready var game: Game = $"../../Game" # TODO
+
+@onready var title_label: Label = $OuterMargin/Panel/InnerMargin/VBoxContainer/Title
+@onready var bots_container: VBoxContainer = $OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer/Bots
+@onready var bot_settings: Control = $OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer/Bots/ScrollContainerCustom/BotSettings
+@onready var bot_settings_container: Control = $OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer/Bots/ScrollContainerCustom
+@onready var start_button: Button = $OuterMargin/Panel/InnerMargin/VBoxContainer/HBoxContainer/Start
+@onready var two_handed_button: CheckButton = $OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer/TwoHanded
+@onready var custom_game_warning: Label = $OuterMargin/Panel/InnerMargin/VBoxContainer/CustomGameWarning
+
+func _ready() -> void:
+ title_label.text = data[0]["display_name"]
+ default_two_handed = false if data[0]["hand_count"] == 1. else true
+ two_handed = default_two_handed
+ two_handed_button.button_pressed = two_handed
+
+ var bot_algos: Array = data[1]
+ for algo: String in bot_algos:
+ bot_counts[algo] = 0
+
+ var h := HBoxContainer.new()
+ h.name = algo
+ var add := Button.new()
+ add.text = "+"
+ var reset := Button.new()
+ reset.size_flags_horizontal = SIZE_EXPAND_FILL
+ var remove := Button.new()
+ remove.text = "-"
+ bot_reset_buttons[algo] = reset
+ bot_inc_buttons[algo] = add
+ bot_dec_buttons[algo] = remove
+ update_bot_reset_text(algo)
+ add.pressed.connect(increase_bot_count.bind(algo))
+ reset.pressed.connect(reset_bot_count.bind(algo))
+ remove.pressed.connect(decrease_bot_count.bind(algo))
+ h.add_child(remove)
+ h.add_child(reset)
+ h.add_child(add)
+ bot_settings.add_child(h)
+
+ super()
+ start_button.grab_focus()
+
+func increase_bot_count(algo_id: String):
+ bot_counts[algo_id] += 1
+ update_bot_reset_text(algo_id)
+
+func decrease_bot_count(algo_id: String):
+ bot_counts[algo_id] -= 1
+ update_bot_reset_text(algo_id)
+
+func reset_bot_count(algo_id: String):
+ if bot_counts[algo_id] == 0: bot_counts[algo_id] = 1
+ else: bot_counts[algo_id] = 0
+ update_bot_reset_text(algo_id)
+
+func update_bot_reset_text(algo_id: String):
+ var display_name: String = tr("s.bot.%s" % algo_id)
+ bot_reset_buttons[algo_id].text = "%s (%d)" % [display_name, bot_counts[algo_id]]
+ set_disabled(bot_reset_buttons[algo_id], false)
+ set_disabled(bot_inc_buttons[algo_id], not bot_counts[algo_id] < MAX_BOT_COUNT_PER_TYPE)
+ set_disabled(bot_dec_buttons[algo_id], not bot_counts[algo_id] > 0)
+
+func _on_enable_bots_toggled(toggled_on: bool) -> void:
+ bots_enabled = toggled_on
+ bot_settings_container.visible = toggled_on
+ update_warning()
+
+func _on_two_handed_toggled(toggled_on: bool) -> void:
+ two_handed = toggled_on
+ update_warning()
+
+func _on_back_pressed() -> void:
+ exit(false)
+
+func _on_start_pressed() -> void:
+ var selected_map_name: String = data[0]["name"]
+ var hand_count: int = 2 if two_handed else 1
+
+ var selected_bots: Array[String] = []
+ if bots_enabled:
+ for k in bot_counts.keys():
+ for i in range(bot_counts[k]):
+ selected_bots.append(k)
+
+ @warning_ignore("incompatible_ternary")
+ game.mp.send_start_game_vote(game.my_player_id, selected_map_name, hand_count if two_handed != default_two_handed else null, selected_bots if bots_enabled else null)
+ exit(true)
+
+func update_warning():
+ custom_game_warning.visible = two_handed != default_two_handed or bots_enabled
diff --git a/client/gui/menus/map_selector/map_details_selector.gd.uid b/client/gui/menus/map_selector/map_details_selector.gd.uid
new file mode 100644
index 00000000..7a32eb14
--- /dev/null
+++ b/client/gui/menus/map_selector/map_details_selector.gd.uid
@@ -0,0 +1 @@
+uid://cntuh7y15r4uy
diff --git a/client/gui/menus/map_selector/map_details_selector.tscn b/client/gui/menus/map_selector/map_details_selector.tscn
new file mode 100644
index 00000000..750f6d57
--- /dev/null
+++ b/client/gui/menus/map_selector/map_details_selector.tscn
@@ -0,0 +1,112 @@
+[gd_scene format=3 uid="uid://bx0n0h6jh7hrd"]
+
+[ext_resource type="Script" uid="uid://cntuh7y15r4uy" path="res://gui/menus/map_selector/map_details_selector.gd" id="1_jsc2p"]
+[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="2_ksn3m"]
+[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="3_f0rsd"]
+[ext_resource type="Script" uid="uid://cmncjc06kadpe" path="res://gui/components/blur_setup.gd" id="4_d3ya2"]
+[ext_resource type="Script" uid="uid://bd7bylb2t2m0" path="res://gui/components/touch_scroll_container.gd" id="6_dxk78"]
+
+[node name="MapDetailsSelector" type="Control" unique_id=510306535]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_jsc2p")
+support_anim = false
+
+[node name="OuterMargin" type="MarginContainer" parent="." unique_id=869412596]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 50
+theme_override_constants/margin_top = 50
+theme_override_constants/margin_right = 50
+theme_override_constants/margin_bottom = 50
+script = ExtResource("2_ksn3m")
+
+[node name="Panel" type="Panel" parent="OuterMargin" unique_id=1557970649]
+material = ExtResource("3_f0rsd")
+layout_mode = 2
+script = ExtResource("4_d3ya2")
+
+[node name="InnerMargin" type="MarginContainer" parent="OuterMargin/Panel" unique_id=2023236197]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 50
+theme_override_constants/margin_top = 50
+theme_override_constants/margin_right = 50
+theme_override_constants/margin_bottom = 50
+
+[node name="VBoxContainer" type="VBoxContainer" parent="OuterMargin/Panel/InnerMargin" unique_id=2137087020]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Title" type="Label" parent="OuterMargin/Panel/InnerMargin/VBoxContainer" unique_id=1471975030]
+layout_mode = 2
+theme_override_font_sizes/font_size = 36
+text = "Map name"
+horizontal_alignment = 1
+
+[node name="VBoxContainer" type="VBoxContainer" parent="OuterMargin/Panel/InnerMargin/VBoxContainer" unique_id=1831622843]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="TwoHanded" type="CheckButton" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer" unique_id=1928176691]
+layout_mode = 2
+text = "c.menu.map_selector.two_handed"
+
+[node name="Bots" type="VBoxContainer" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer" unique_id=1569430044]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="EnableBots" type="CheckButton" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer/Bots" unique_id=1715103927]
+layout_mode = 2
+text = "c.menu.map_selector.enable_bots"
+
+[node name="ScrollContainerCustom" type="ScrollContainer" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer/Bots" unique_id=1499397868]
+visible = false
+layout_mode = 2
+size_flags_vertical = 3
+script = ExtResource("6_dxk78")
+metadata/_custom_type_script = "uid://bd7bylb2t2m0"
+
+[node name="BotSettings" type="VBoxContainer" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer/Bots/ScrollContainerCustom" unique_id=101347071]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="CustomGameWarning" type="Label" parent="OuterMargin/Panel/InnerMargin/VBoxContainer" unique_id=557709694]
+visible = false
+layout_mode = 2
+theme_override_colors/font_color = Color(1, 0.9019608, 0.49803922, 1)
+text = "s.custom_game_warn"
+horizontal_alignment = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="OuterMargin/Panel/InnerMargin/VBoxContainer" unique_id=2025174871]
+layout_mode = 2
+
+[node name="Back" type="Button" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/HBoxContainer" unique_id=2006434901]
+layout_mode = 2
+text = "c.menu.back"
+
+[node name="Spacer" type="Control" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/HBoxContainer" unique_id=399194568]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Start" type="Button" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/HBoxContainer" unique_id=1675695981]
+layout_mode = 2
+text = "c.menu.map_selector.start"
+
+[connection signal="toggled" from="OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer/TwoHanded" to="." method="_on_two_handed_toggled"]
+[connection signal="toggled" from="OuterMargin/Panel/InnerMargin/VBoxContainer/VBoxContainer/Bots/EnableBots" to="." method="_on_enable_bots_toggled"]
+[connection signal="pressed" from="OuterMargin/Panel/InnerMargin/VBoxContainer/HBoxContainer/Back" to="." method="_on_back_pressed"]
+[connection signal="pressed" from="OuterMargin/Panel/InnerMargin/VBoxContainer/HBoxContainer/Start" to="." method="_on_start_pressed"]
diff --git a/client/gui/menus/map_selector/map_list_item.gd b/client/gui/menus/map_selector/map_list_item.gd
new file mode 100644
index 00000000..7775c079
--- /dev/null
+++ b/client/gui/menus/map_selector/map_list_item.gd
@@ -0,0 +1,28 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2026 Hurry Curry! Contributors
+#
+# 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/>.
+#
+class_name MapListItem
+extends PanelContainer
+
+@onready var button: Button = $Button
+
+@onready var name_label: Label = $MarginContainer/VBoxContainer/Name
+@onready var difficulty_label: Label = $MarginContainer/VBoxContainer/HBoxContainer/Difficulty
+@onready var recommended_players_label: Label = $MarginContainer/VBoxContainer/HBoxContainer/RecommendedPlayers
+
+func setup(name_: String, difficulty: int, recommended_players: int):
+ name_label.text = name_
+ difficulty_label.text = tr("c.map.difficulty.%d" % (difficulty - 1))
+ recommended_players_label.text = tr("c.map.players_recommended").format([recommended_players])
diff --git a/client/gui/menus/map_selector/map_list_item.gd.uid b/client/gui/menus/map_selector/map_list_item.gd.uid
new file mode 100644
index 00000000..41f512a7
--- /dev/null
+++ b/client/gui/menus/map_selector/map_list_item.gd.uid
@@ -0,0 +1 @@
+uid://di7j0scdcg2ox
diff --git a/client/gui/menus/map_selector/map_list_item.tscn b/client/gui/menus/map_selector/map_list_item.tscn
new file mode 100644
index 00000000..65736fc7
--- /dev/null
+++ b/client/gui/menus/map_selector/map_list_item.tscn
@@ -0,0 +1,59 @@
+[gd_scene format=3 uid="uid://buyelb8w7edpe"]
+
+[ext_resource type="Script" uid="uid://di7j0scdcg2ox" path="res://gui/menus/map_selector/map_list_item.gd" id="1_fywrp"]
+[ext_resource type="StyleBox" uid="uid://d1xhwgrptnlli" path="res://gui/resources/style/panel_button_backround_style.tres" id="2_3fooa"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1n1yg"]
+
+[node name="ServerListItem" type="PanelContainer" unique_id=2062705951]
+offset_right = 400.0
+offset_bottom = 40.0
+size_flags_horizontal = 3
+theme_override_styles/panel = SubResource("StyleBoxEmpty_1n1yg")
+script = ExtResource("1_fywrp")
+
+[node name="Panel" type="Panel" parent="." unique_id=485329348]
+layout_mode = 2
+theme_override_styles/panel = ExtResource("2_3fooa")
+
+[node name="Button" type="Button" parent="." unique_id=1213365601]
+layout_mode = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="." unique_id=1363186299]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/margin_left = 10
+theme_override_constants/margin_top = 10
+theme_override_constants/margin_right = 10
+theme_override_constants/margin_bottom = 10
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer" unique_id=1686608173]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="Name" type="Label" parent="MarginContainer/VBoxContainer" unique_id=27759863]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.87451, 0.87451, 0.87451, 1)
+theme_override_font_sizes/font_size = 18
+text = "Example Map"
+text_overrun_behavior = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer" unique_id=1537690218]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/separation = 16
+
+[node name="Difficulty" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=741851992]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.749781, 0.74978, 0.74978, 1)
+theme_override_font_sizes/font_size = 14
+text = "Medium"
+
+[node name="RecommendedPlayers" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=1214181115]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_colors/font_color = Color(0.749781, 0.74978, 0.74978, 1)
+theme_override_font_sizes/font_size = 14
+text = "5 players recommended"
+horizontal_alignment = 2
+text_overrun_behavior = 3
diff --git a/client/gui/menus/map_selector/map_selector.gd b/client/gui/menus/map_selector/map_selector.gd
new file mode 100644
index 00000000..9190b8f3
--- /dev/null
+++ b/client/gui/menus/map_selector/map_selector.gd
@@ -0,0 +1,43 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2026 Hurry Curry! Contributors
+#
+# 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/>.
+#
+extends Menu
+class_name MapSelector
+
+const MAP_LIST_ITEM = preload("res://gui/menus/map_selector/map_list_item.tscn")
+
+@onready var map_container: VBoxContainer = $OuterMargin/Panel/InnerMargin/VBoxContainer/ScrollContainer/VBoxContainer
+
+func _ready() -> void:
+ var maps: Array = data[0]
+ for m in maps:
+ var item: MapListItem = MAP_LIST_ITEM.instantiate()
+ item.name = m["name"]
+ map_container.add_child(item)
+ item.setup(m["display_name"], int(m["difficulty"]), int(m["players"]))
+ item.button.pressed.connect(select_map.bind(m))
+ super()
+
+func select_map(map):
+ var algos: Array = data[1]
+ var success = await submenu("res://gui/menus/map_selector/map_details_selector.tscn", [map, algos])
+ if success: exit()
+
+func _menu_cover(state: bool):
+ # TODO: Find a better way to hide this menu without hiding submenu, this feels hacky
+ $OuterMargin.visible = not state
+
+func _on_back_pressed() -> void:
+ exit()
diff --git a/client/gui/menus/map_selector/map_selector.gd.uid b/client/gui/menus/map_selector/map_selector.gd.uid
new file mode 100644
index 00000000..64f6e6d9
--- /dev/null
+++ b/client/gui/menus/map_selector/map_selector.gd.uid
@@ -0,0 +1 @@
+uid://dgl836r2uwjfw
diff --git a/client/gui/menus/map_selector/map_selector.tscn b/client/gui/menus/map_selector/map_selector.tscn
new file mode 100644
index 00000000..b88ec0ea
--- /dev/null
+++ b/client/gui/menus/map_selector/map_selector.tscn
@@ -0,0 +1,76 @@
+[gd_scene format=3 uid="uid://b26sufndh0pm5"]
+
+[ext_resource type="Script" uid="uid://dgl836r2uwjfw" path="res://gui/menus/map_selector/map_selector.gd" id="1_37to6"]
+[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="2_5vs08"]
+[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="3_txniv"]
+[ext_resource type="Script" uid="uid://cmncjc06kadpe" path="res://gui/components/blur_setup.gd" id="4_kobpr"]
+[ext_resource type="Script" uid="uid://bd7bylb2t2m0" path="res://gui/components/touch_scroll_container.gd" id="6_7xmra"]
+
+[node name="MapSelector" type="Control" unique_id=510306535]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_37to6")
+support_anim = false
+
+[node name="OuterMargin" type="MarginContainer" parent="." unique_id=869412596]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 50
+theme_override_constants/margin_top = 50
+theme_override_constants/margin_right = 50
+theme_override_constants/margin_bottom = 50
+script = ExtResource("2_5vs08")
+
+[node name="Panel" type="Panel" parent="OuterMargin" unique_id=1557970649]
+material = ExtResource("3_txniv")
+layout_mode = 2
+script = ExtResource("4_kobpr")
+
+[node name="InnerMargin" type="MarginContainer" parent="OuterMargin/Panel" unique_id=2023236197]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 50
+theme_override_constants/margin_top = 50
+theme_override_constants/margin_right = 50
+theme_override_constants/margin_bottom = 50
+
+[node name="VBoxContainer" type="VBoxContainer" parent="OuterMargin/Panel/InnerMargin" unique_id=2137087020]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Title" type="Label" parent="OuterMargin/Panel/InnerMargin/VBoxContainer" unique_id=1471975030]
+layout_mode = 2
+theme_override_font_sizes/font_size = 36
+text = "c.map.select"
+horizontal_alignment = 1
+
+[node name="ScrollContainer" type="ScrollContainer" parent="OuterMargin/Panel/InnerMargin/VBoxContainer" unique_id=394287120]
+layout_mode = 2
+size_flags_vertical = 3
+script = ExtResource("6_7xmra")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/ScrollContainer" unique_id=1798845933]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="OuterMargin/Panel/InnerMargin/VBoxContainer" unique_id=1907231166]
+layout_mode = 2
+
+[node name="Back" type="Button" parent="OuterMargin/Panel/InnerMargin/VBoxContainer/HBoxContainer" unique_id=1801916460]
+layout_mode = 2
+text = "c.menu.back"
+
+[connection signal="pressed" from="OuterMargin/Panel/InnerMargin/VBoxContainer/HBoxContainer/Back" to="." method="_on_back_pressed"]
diff --git a/client/gui/menus/menu.gd b/client/gui/menus/menu.gd
index ddf16395..6d440118 100644
--- a/client/gui/menus/menu.gd
+++ b/client/gui/menus/menu.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -24,7 +24,7 @@ extends Control
var data
-signal submenu_close()
+signal submenu_close(exit_data: Variant)
const transition_scene = preload("res://gui/menus/transition/scene_transition.tscn")
var transition: SceneTransition
@@ -60,7 +60,7 @@ func _menu_music():
var popup: Menu = null
var covered := false
-func submenu(path: String, data_ = null):
+func submenu(path: String, data_ = null) -> Variant:
var prev_focus = Global.focused_node
if popup != null: return
disabled = true; _update_disabled(self)
@@ -70,7 +70,7 @@ func submenu(path: String, data_ = null):
popup.data = data_
add_child(popup)
# print("Submenu opened ", path)
- await submenu_close
+ var exit_data = await submenu_close
# print("Submenu closed ", path)
covered = false
await _menu_cover(false)
@@ -78,6 +78,7 @@ func submenu(path: String, data_ = null):
_menu_music()
disabled = false; _update_disabled(self)
if prev_focus != null: prev_focus.grab_focus()
+ return exit_data
func _update_disabled(node: Node):
if node is BaseButton: _update_button_disabled(node)
@@ -97,7 +98,7 @@ func set_disabled(node: BaseButton, state: bool):
else: node.remove_from_group("disabled")
node.disabled = state or disabled
-func exit():
+func exit(exit_data: Variant = null):
disabled = true; _update_disabled(self)
if transition and transition.fading:
push_error("menu exit() called twice")
@@ -107,7 +108,7 @@ func exit():
if previous_path != null:
replace_menu(previous_path)
else:
- get_parent().submenu_close.emit()
+ get_parent().submenu_close.emit(exit_data)
queue_free()
func quit():
diff --git a/client/gui/menus/popup.gd b/client/gui/menus/popup.gd
index d4849e92..75ed20fd 100644
--- a/client/gui/menus/popup.gd
+++ b/client/gui/menus/popup.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/popup.tscn b/client/gui/menus/popup.tscn
index 07dccabc..9824befd 100644
--- a/client/gui/menus/popup.tscn
+++ b/client/gui/menus/popup.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://lwtym0pbc17g"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_m0d0r"]
[ext_resource type="Script" uid="uid://bevyiytj5tawr" path="res://gui/menus/popup.gd" id="2_1h10j"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="3_iouvy"]
[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="3_j0ajn"]
@@ -13,7 +12,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_m0d0r")
script = ExtResource("2_1h10j")
support_anim = false
diff --git a/client/gui/menus/popup_large.gd b/client/gui/menus/popup_large.gd
index f821e7a8..41f6bd9f 100644
--- a/client/gui/menus/popup_large.gd
+++ b/client/gui/menus/popup_large.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/popup_large.tscn b/client/gui/menus/popup_large.tscn
index 66a07306..4bf5109e 100644
--- a/client/gui/menus/popup_large.tscn
+++ b/client/gui/menus/popup_large.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://7mqbxa054bjv"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_kabr3"]
[ext_resource type="Script" uid="uid://c3eimx76ucpsp" path="res://gui/menus/popup_large.gd" id="2_m0b5d"]
[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="3_36vhf"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="4_8ybj3"]
@@ -14,7 +13,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_kabr3")
script = ExtResource("2_m0b5d")
support_anim = false
diff --git a/client/gui/menus/rating/rating.gd b/client/gui/menus/rating/rating.gd
index 5bae0268..4d47637f 100644
--- a/client/gui/menus/rating/rating.gd
+++ b/client/gui/menus/rating/rating.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/rating/rating.tscn b/client/gui/menus/rating/rating.tscn
index 087bfdfb..4e54572c 100644
--- a/client/gui/menus/rating/rating.tscn
+++ b/client/gui/menus/rating/rating.tscn
@@ -1,8 +1,7 @@
[gd_scene format=3 uid="uid://buu3cdpigs8qq"]
-[ext_resource type="Texture2D" uid="uid://b10goh4dsa3b0" path="res://player/particles/satisfied/star.webp" id="1_7qv7r"]
+[ext_resource type="Texture2D" uid="uid://b10goh4dsa3b0" path="res://map/effects/satisfied/star.webp" id="1_7qv7r"]
[ext_resource type="Shader" uid="uid://cekkkqsvd7rvw" path="res://gui/menus/rating/desaturate.gdshader" id="1_pddsm"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_uwajf"]
[ext_resource type="Script" uid="uid://5tmklxkaa6e0" path="res://gui/menus/rating/rating.gd" id="2_cq0se"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="4_hdurb"]
[ext_resource type="AudioStream" uid="uid://camy77x26mmpv" path="res://gui/resources/sounds/success.ogg" id="5_tutpj"]
@@ -34,7 +33,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_uwajf")
script = ExtResource("2_cq0se")
support_anim = false
diff --git a/client/gui/menus/scoreboard.gd b/client/gui/menus/scoreboard.gd
index 067736e0..464705b5 100644
--- a/client/gui/menus/scoreboard.gd
+++ b/client/gui/menus/scoreboard.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/button_setting.gd b/client/gui/menus/settings/button_setting.gd
index e8a2ab42..6d7f9f2f 100644
--- a/client/gui/menus/settings/button_setting.gd
+++ b/client/gui/menus/settings/button_setting.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/dropdown_setting.gd b/client/gui/menus/settings/dropdown_setting.gd
index 6693f6a9..dce4e4b3 100644
--- a/client/gui/menus/settings/dropdown_setting.gd
+++ b/client/gui/menus/settings/dropdown_setting.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/game_setting.gd b/client/gui/menus/settings/game_setting.gd
index 07d39d98..e9be3f45 100644
--- a/client/gui/menus/settings/game_setting.gd
+++ b/client/gui/menus/settings/game_setting.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/input/input_manager.gd b/client/gui/menus/settings/input/input_manager.gd
index 4647659f..6700029a 100644
--- a/client/gui/menus/settings/input/input_manager.gd
+++ b/client/gui/menus/settings/input/input_manager.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/input/input_setting.gd b/client/gui/menus/settings/input/input_setting.gd
index 8a494865..e689cbf2 100644
--- a/client/gui/menus/settings/input/input_setting.gd
+++ b/client/gui/menus/settings/input/input_setting.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/input/input_value_node.gd b/client/gui/menus/settings/input/input_value_node.gd
index fe5768cf..6034d7cc 100644
--- a/client/gui/menus/settings/input/input_value_node.gd
+++ b/client/gui/menus/settings/input/input_value_node.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/number_setting.gd b/client/gui/menus/settings/number_setting.gd
index 0301895c..85f3f363 100644
--- a/client/gui/menus/settings/number_setting.gd
+++ b/client/gui/menus/settings/number_setting.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/path_setting.gd b/client/gui/menus/settings/path_setting.gd
index 37492ed7..81d79777 100644
--- a/client/gui/menus/settings/path_setting.gd
+++ b/client/gui/menus/settings/path_setting.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/preset_row.gd b/client/gui/menus/settings/preset_row.gd
index ad878d00..5b83a9a2 100644
--- a/client/gui/menus/settings/preset_row.gd
+++ b/client/gui/menus/settings/preset_row.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/range_setting.gd b/client/gui/menus/settings/range_setting.gd
index 89a213e0..c400aa2c 100644
--- a/client/gui/menus/settings/range_setting.gd
+++ b/client/gui/menus/settings/range_setting.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/settings.gd b/client/gui/menus/settings/settings.gd
index 7523b9fb..1099ba1d 100644
--- a/client/gui/menus/settings/settings.gd
+++ b/client/gui/menus/settings/settings.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -35,6 +35,6 @@ func _process(_dt):
func _on_back_pressed():
exit()
-func exit():
+func exit(exit_data = null):
Settings.save()
- super()
+ super(exit_data)
diff --git a/client/gui/menus/settings/settings.tscn b/client/gui/menus/settings/settings.tscn
index 29874032..c9cc4a26 100644
--- a/client/gui/menus/settings/settings.tscn
+++ b/client/gui/menus/settings/settings.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://8ic77jmadadj"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_1vjiw"]
[ext_resource type="Script" uid="uid://bbqmsf8u5rhtn" path="res://gui/menus/settings/settings.gd" id="2_5xn7x"]
[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="3_h533i"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="4_b0x33"]
@@ -13,7 +12,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_1vjiw")
script = ExtResource("2_5xn7x")
support_anim = false
diff --git a/client/gui/menus/settings/settings_category.gd b/client/gui/menus/settings/settings_category.gd
index 28a0e75b..3801b4f1 100644
--- a/client/gui/menus/settings/settings_category.gd
+++ b/client/gui/menus/settings/settings_category.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/settings_root.gd b/client/gui/menus/settings/settings_root.gd
index 3f7fc027..972cc1f6 100644
--- a/client/gui/menus/settings/settings_root.gd
+++ b/client/gui/menus/settings/settings_root.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/settings_row.gd b/client/gui/menus/settings/settings_row.gd
index 15555eb0..4003ab5f 100644
--- a/client/gui/menus/settings/settings_row.gd
+++ b/client/gui/menus/settings/settings_row.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/settings_row.tscn b/client/gui/menus/settings/settings_row.tscn
index 9b9783bb..f72ca0f5 100644
--- a/client/gui/menus/settings/settings_row.tscn
+++ b/client/gui/menus/settings/settings_row.tscn
@@ -1,6 +1,6 @@
[gd_scene format=3 uid="uid://o5e5vpem8w0k"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_iij3k"]
+[ext_resource type="StyleBox" uid="uid://d1xhwgrptnlli" path="res://gui/resources/style/panel_button_backround_style.tres" id="1_ik3io"]
[ext_resource type="Script" uid="uid://b3m1f76o5qo68" path="res://gui/menus/settings/settings_row.gd" id="2_l8i7p"]
[ext_resource type="FontFile" uid="uid://5ixo6b3bd3km" path="res://gui/resources/fonts/font-josefin-sans.woff2" id="3_7k5da"]
[ext_resource type="Texture2D" uid="uid://cucnmy0j5n8l8" path="res://gui/resources/icons/reset.svg" id="4_bj3dr"]
@@ -15,7 +15,7 @@ base_font = ExtResource("3_7k5da")
offset_right = 105.0
offset_bottom = 23.0
size_flags_horizontal = 3
-theme = ExtResource("1_iij3k")
+theme_override_styles/panel = ExtResource("1_ik3io")
script = ExtResource("2_l8i7p")
[node name="HBoxContainer" type="HBoxContainer" parent="." unique_id=523957987]
diff --git a/client/gui/menus/settings/text_setting.gd b/client/gui/menus/settings/text_setting.gd
index 9a873629..1be2bbc1 100644
--- a/client/gui/menus/settings/text_setting.gd
+++ b/client/gui/menus/settings/text_setting.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/settings/toggle_setting.gd b/client/gui/menus/settings/toggle_setting.gd
index 0a9a62c1..b0dbd352 100644
--- a/client/gui/menus/settings/toggle_setting.gd
+++ b/client/gui/menus/settings/toggle_setting.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/setup/hairstyle_preview.gd b/client/gui/menus/setup/hairstyle_preview.gd
index eee3a9f2..7f85f03a 100644
--- a/client/gui/menus/setup/hairstyle_preview.gd
+++ b/client/gui/menus/setup/hairstyle_preview.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/setup/setup.gd b/client/gui/menus/setup/setup.gd
index 36972870..bf970029 100644
--- a/client/gui/menus/setup/setup.gd
+++ b/client/gui/menus/setup/setup.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/menus/transition/scene_transition.gd b/client/gui/menus/transition/scene_transition.gd
index 92715f2f..ffb0c8c5 100644
--- a/client/gui/menus/transition/scene_transition.gd
+++ b/client/gui/menus/transition/scene_transition.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/announce_title.gd b/client/gui/overlays/announce_title.gd
index 66d5434b..b9e7c542 100644
--- a/client/gui/overlays/announce_title.gd
+++ b/client/gui/overlays/announce_title.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/chat.gd b/client/gui/overlays/chat.gd
index 676337a6..a831cf2c 100644
--- a/client/gui/overlays/chat.gd
+++ b/client/gui/overlays/chat.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/chat.tscn b/client/gui/overlays/chat.tscn
index b7a190de..b0bd9728 100644
--- a/client/gui/overlays/chat.tscn
+++ b/client/gui/overlays/chat.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://xcxbmynn8mhi"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_lmy51"]
[ext_resource type="Script" uid="uid://bgt04y4ncl1fv" path="res://gui/overlays/chat.gd" id="2_3543w"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="3_15i2y"]
[ext_resource type="Script" uid="uid://cmncjc06kadpe" path="res://gui/components/blur_setup.gd" id="4_3rmhr"]
@@ -13,7 +12,6 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
-theme = ExtResource("1_lmy51")
script = ExtResource("2_3543w")
[node name="MarginContainer" type="MarginContainer" parent="." unique_id=468015652]
diff --git a/client/gui/overlays/controls_visualization/controller/controller_explanation.gd b/client/gui/overlays/controls_visualization/controller/controller_explanation.gd
index 84e93cfa..cb1a876e 100644
--- a/client/gui/overlays/controls_visualization/controller/controller_explanation.gd
+++ b/client/gui/overlays/controls_visualization/controller/controller_explanation.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/controls_visualization/device_explanation.gd b/client/gui/overlays/controls_visualization/device_explanation.gd
index 7bfe2fb4..ae39c3b6 100644
--- a/client/gui/overlays/controls_visualization/device_explanation.gd
+++ b/client/gui/overlays/controls_visualization/device_explanation.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/controls_visualization/explanation.gd b/client/gui/overlays/controls_visualization/explanation.gd
index b5a39118..20d60dc5 100644
--- a/client/gui/overlays/controls_visualization/explanation.gd
+++ b/client/gui/overlays/controls_visualization/explanation.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/controls_visualization/explanation.tscn b/client/gui/overlays/controls_visualization/explanation.tscn
index afe5bdaf..87cfd024 100644
--- a/client/gui/overlays/controls_visualization/explanation.tscn
+++ b/client/gui/overlays/controls_visualization/explanation.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://c7g5gpiyofmu8"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_81hfk"]
[ext_resource type="Script" uid="uid://bs2xryd5vamjf" path="res://gui/overlays/controls_visualization/explanation.gd" id="2_lxysr"]
[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="2_m528b"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="3_pomap"]
@@ -15,7 +14,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme = ExtResource("1_81hfk")
script = ExtResource("2_lxysr")
support_anim = false
diff --git a/client/gui/overlays/controls_visualization/keyboard/keyboard_explanation.gd b/client/gui/overlays/controls_visualization/keyboard/keyboard_explanation.gd
index 5d6dfb42..eb536556 100644
--- a/client/gui/overlays/controls_visualization/keyboard/keyboard_explanation.gd
+++ b/client/gui/overlays/controls_visualization/keyboard/keyboard_explanation.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/debug/debug.gd b/client/gui/overlays/debug/debug.gd
index 74175f2c..51bd0a48 100644
--- a/client/gui/overlays/debug/debug.gd
+++ b/client/gui/overlays/debug/debug.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/debug/debug_pie.gd b/client/gui/overlays/debug/debug_pie.gd
index 02201776..ce32c93a 100644
--- a/client/gui/overlays/debug/debug_pie.gd
+++ b/client/gui/overlays/debug/debug_pie.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/lobby/lobby.gd b/client/gui/overlays/lobby/lobby.gd
deleted file mode 100644
index e632a1e7..00000000
--- a/client/gui/overlays/lobby/lobby.gd
+++ /dev/null
@@ -1,177 +0,0 @@
-# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
-#
-# 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/>.
-#
-extends Control
-class_name Lobby
-
-const MAX_BOT_COUNT_PER_TYPE: int = 3
-const PLAYER = preload("res://gui/overlays/lobby/player.tscn")
-
-var map_count
-var selected_map := 0
-var selected_map_name: String
-
-var bots_enabled := false
-var bot_counts := {}
-var bot_reset_buttons := {}
-var bot_inc_buttons := {}
-var bot_dec_buttons := {}
-
-@onready var game: Game = $"../../Game" # TODO
-@onready var game_menu: Menu = Menu.get_parent_menu(self)
-@onready var player_container = $PlayerList/VBoxContainer/Players
-
-@onready var map_name_label = $Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/Map/Name
-@onready var map_player_label = $Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/Map/Players
-@onready var map_difficulty_label = $Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/Map/Difficulty
-@onready var map_list_container = $Sidebar/Bottom/MarginContainer/VBoxContainer/MapList/VBoxContainer
-
-@onready var prev_map = $Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer/Left
-@onready var next_map = $Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer2/Right
-
-@onready var bots_container = $Sidebar/Bottom/MarginContainer/VBoxContainer/Bots
-@onready var bot_settings = $Sidebar/Bottom/MarginContainer/VBoxContainer/Bots/ScrollContainerCustom/BotSettings
-@onready var bot_settings_conainer = $Sidebar/Bottom/MarginContainer/VBoxContainer/Bots/ScrollContainerCustom
-
-func _ready():
- game.update_players.connect(update_players)
- initialize()
- game.data_updated.connect(initialize)
- game.join_state_updated.connect(_update_visible)
- game.in_lobby_updated.connect(_update_visible)
- _update_visible(false)
-
-func initialize():
- map_count = game.maps.size()
-
- for c in map_list_container.get_children():
- c.queue_free()
- for c in bot_settings.get_children():
- c.queue_free()
-
- var i := 0
- for m in game.maps:
- var b := Button.new()
- b.name = m[0]
- b.text = "%s (%d)" % [m[1]["name"], m[1]["players"]]
- b.pressed.connect(select_map.bind(i))
- b.focus_entered.connect(select_map.bind(i))
- game_menu.set_disabled(b, false)
- map_list_container.add_child(b)
- i += 1
- select_map(0)
-
- for algo_id: String in game.bot_algos:
- if algo_id == "customer" or algo_id == "test":
- continue
-
- bot_counts[algo_id] = 0
-
- var h := HBoxContainer.new()
- h.name = algo_id
- var add := Button.new()
- add.text = "+"
- var reset := Button.new()
- reset.size_flags_horizontal = SIZE_EXPAND_FILL
- var remove := Button.new()
- remove.text = "-"
- bot_reset_buttons[algo_id] = reset
- bot_inc_buttons[algo_id] = add
- bot_dec_buttons[algo_id] = remove
- update_bot_reset_text(algo_id)
- add.pressed.connect(increase_bot_count.bind(algo_id))
- reset.pressed.connect(reset_bot_count.bind(algo_id))
- remove.pressed.connect(decrease_bot_count.bind(algo_id))
- h.add_child(remove)
- h.add_child(reset)
- h.add_child(add)
- bot_settings.add_child(h)
-
-func select_map(i: int):
- if i >= map_count:
- return
- selected_map = i
- var map_data: Dictionary = game.maps[i][1]
- map_name_label.text = map_data["name"]
- map_player_label.text = tr("c.map.players_recommended").format([roundi(map_data["players"])])
- map_difficulty_label.text = tr("c.map.difficulty.%d" % (map_data["difficulty"] - 1))
- selected_map_name = game.maps[i][0]
- if not game.menu.covered:
- map_list_container.get_child(i).grab_focus()
-
-func increase_bot_count(algo_id: String):
- bot_counts[algo_id] += 1
- update_bot_reset_text(algo_id)
-
-func decrease_bot_count(algo_id: String):
- bot_counts[algo_id] -= 1
- update_bot_reset_text(algo_id)
-
-func reset_bot_count(algo_id: String):
- if bot_counts[algo_id] == 0: bot_counts[algo_id] = 1
- else: bot_counts[algo_id] = 0
- update_bot_reset_text(algo_id)
-
-func update_bot_reset_text(algo_id: String):
- var display_name: String = tr("s.bot.%s" % algo_id)
- bot_reset_buttons[algo_id].text = "%s (%d)" % [display_name, bot_counts[algo_id]]
- game_menu.set_disabled(bot_reset_buttons[algo_id], false)
- game_menu.set_disabled(bot_inc_buttons[algo_id], not bot_counts[algo_id] < MAX_BOT_COUNT_PER_TYPE)
- game_menu.set_disabled(bot_dec_buttons[algo_id], not bot_counts[algo_id] > 0)
-
-func update_players(player_list: Dictionary):
- for i in player_container.get_children():
- i.queue_free()
-
- for i in player_list.keys():
- var p: PlayerTag = PLAYER.instantiate()
- player_container.add_child(p)
- p.setup(player_list[i].username)
-
-func _input(_event):
- if not visible:
- return
-
- if Input.is_action_just_pressed("previous") and not prev_map.disabled:
- prev_map.emit_signal("pressed")
- elif Input.is_action_just_pressed("next") and not next_map.disabled:
- next_map.emit_signal("pressed")
-
-func _on_left_pressed():
- selected_map = (selected_map - 1) % map_count
- select_map(selected_map)
-
-func _on_right_pressed():
- selected_map = (selected_map + 1) % map_count
- select_map(selected_map)
-
-func _on_start_pressed():
- if selected_map_name != null:
- var start_msg := "/start %s" % selected_map_name
-
- if bots_enabled:
- for k in bot_counts.keys():
- for i in range(bot_counts[k]):
- start_msg += "\ncreate-bot %s" % k
-
- game.mp.send_chat(game.my_player_id, start_msg)
-
-func _update_visible(_state: bool):
- visible = game.in_lobby and game.join_state == Game.JoinState.JOINED
-
-func _on_enable_bots_toggled(toggled_on):
- bots_enabled = toggled_on
- bot_settings_conainer.visible = toggled_on
- bots_container.size_flags_vertical = SIZE_EXPAND_FILL if toggled_on else SIZE_FILL
diff --git a/client/gui/overlays/lobby/lobby.gd.uid b/client/gui/overlays/lobby/lobby.gd.uid
deleted file mode 100644
index b92e8681..00000000
--- a/client/gui/overlays/lobby/lobby.gd.uid
+++ /dev/null
@@ -1 +0,0 @@
-uid://bssjvsu44l0fn
diff --git a/client/gui/overlays/lobby/lobby.tscn b/client/gui/overlays/lobby/lobby.tscn
deleted file mode 100644
index c78dcfa4..00000000
--- a/client/gui/overlays/lobby/lobby.tscn
+++ /dev/null
@@ -1,181 +0,0 @@
-[gd_scene format=3 uid="uid://bc50la65ntifb"]
-
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_u18ke"]
-[ext_resource type="Script" uid="uid://bssjvsu44l0fn" path="res://gui/overlays/lobby/lobby.gd" id="2_7657i"]
-[ext_resource type="StyleBox" uid="uid://de80aw86emnql" path="res://gui/resources/style/lobby_panel_override.tres" id="3_6iqoe"]
-[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="3_esmbx"]
-[ext_resource type="Texture2D" uid="uid://35rd5gamtyqm" path="res://gui/resources/icons/arrow.svg" id="3_jxleg"]
-[ext_resource type="Texture2D" uid="uid://j75dbytlbju" path="res://gui/resources/icons/arrow_pressed.svg" id="4_eapmn"]
-[ext_resource type="Script" uid="uid://cmncjc06kadpe" path="res://gui/components/blur_setup.gd" id="5_am8pt"]
-[ext_resource type="Texture2D" uid="uid://b33qmctbpf48g" path="res://gui/resources/icons/arrow_hover.svg" id="5_odwav"]
-[ext_resource type="Script" uid="uid://byshs20og68tn" path="res://gui/components/smart_margin_container.gd" id="6_7mu2u"]
-[ext_resource type="Texture2D" uid="uid://by3qsrpxnfq4w" path="res://gui/resources/icons/arrow_focus.svg" id="6_tulu3"]
-[ext_resource type="FontFile" uid="uid://5ixo6b3bd3km" path="res://gui/resources/fonts/font-josefin-sans.woff2" id="8_cwbpa"]
-[ext_resource type="Texture2D" uid="uid://bsx6fo7mv2u6a" path="res://gui/resources/icons/controller_x.svg" id="9_q14bw"]
-[ext_resource type="Script" uid="uid://bd7bylb2t2m0" path="res://gui/components/touch_scroll_container.gd" id="10_bgene"]
-[ext_resource type="Script" uid="uid://b1eomxildrq30" path="res://gui/components/controller_button.gd" id="12_7mu2u"]
-
-[sub_resource type="FontVariation" id="FontVariation_5xxr2"]
-base_font = ExtResource("8_cwbpa")
-variation_opentype = {
-2003265652: 700
-}
-
-[node name="Lobby" type="Control" unique_id=1565790216 groups=["no_auto_focus"]]
-layout_mode = 3
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-theme = ExtResource("1_u18ke")
-script = ExtResource("2_7657i")
-
-[node name="PlayerList" type="MarginContainer" parent="." unique_id=428931880]
-layout_mode = 1
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-mouse_filter = 2
-theme_override_constants/margin_left = 342
-theme_override_constants/margin_top = 8
-theme_override_constants/margin_right = 342
-theme_override_constants/margin_bottom = 8
-
-[node name="VBoxContainer" type="VBoxContainer" parent="PlayerList" unique_id=677543660]
-layout_mode = 2
-
-[node name="Players" type="HBoxContainer" parent="PlayerList/VBoxContainer" unique_id=823260063]
-layout_mode = 2
-alignment = 1
-
-[node name="Sidebar" type="HBoxContainer" parent="." unique_id=290400512]
-layout_mode = 1
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-alignment = 2
-
-[node name="Bottom" type="PanelContainer" parent="Sidebar" unique_id=1354830602]
-material = ExtResource("3_esmbx")
-layout_mode = 2
-theme_override_styles/panel = ExtResource("3_6iqoe")
-script = ExtResource("5_am8pt")
-
-[node name="MarginContainer" type="MarginContainer" parent="Sidebar/Bottom" unique_id=1216479959]
-layout_mode = 2
-script = ExtResource("6_7mu2u")
-
-[node name="VBoxContainer" type="VBoxContainer" parent="Sidebar/Bottom/MarginContainer" unique_id=1847639558]
-layout_mode = 2
-theme_override_constants/separation = 24
-
-[node name="HBoxContainer" type="HBoxContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer" unique_id=157723219]
-layout_direction = 2
-layout_mode = 2
-alignment = 1
-
-[node name="VBoxContainer" type="VBoxContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer" unique_id=824177041]
-layout_mode = 2
-alignment = 1
-
-[node name="Left" type="TextureButton" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer" unique_id=1422875980]
-custom_minimum_size = Vector2(19, 28)
-layout_mode = 2
-focus_mode = 0
-texture_normal = ExtResource("3_jxleg")
-texture_pressed = ExtResource("4_eapmn")
-texture_hover = ExtResource("5_odwav")
-texture_focused = ExtResource("6_tulu3")
-ignore_texture_size = true
-stretch_mode = 4
-flip_h = true
-
-[node name="Map" type="VBoxContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer" unique_id=1797195293]
-layout_mode = 2
-
-[node name="Name" type="Label" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/Map" unique_id=1669537853]
-custom_minimum_size = Vector2(200, 0)
-layout_mode = 2
-theme_override_fonts/font = SubResource("FontVariation_5xxr2")
-theme_override_font_sizes/font_size = 24
-text = "Map name"
-horizontal_alignment = 1
-vertical_alignment = 1
-
-[node name="Players" type="Label" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/Map" unique_id=1637269594]
-layout_mode = 2
-text = "Players"
-horizontal_alignment = 1
-vertical_alignment = 1
-
-[node name="Difficulty" type="Label" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/Map" unique_id=1816548843]
-layout_mode = 2
-text = "Difficulty"
-horizontal_alignment = 1
-vertical_alignment = 1
-
-[node name="VBoxContainer2" type="VBoxContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer" unique_id=1321493780]
-layout_mode = 2
-alignment = 1
-
-[node name="Right" type="TextureButton" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer2" unique_id=234475564]
-custom_minimum_size = Vector2(19, 28)
-layout_mode = 2
-focus_mode = 0
-texture_normal = ExtResource("3_jxleg")
-texture_pressed = ExtResource("4_eapmn")
-texture_hover = ExtResource("5_odwav")
-texture_focused = ExtResource("6_tulu3")
-ignore_texture_size = true
-stretch_mode = 4
-
-[node name="MapList" type="ScrollContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer" unique_id=392832076]
-layout_mode = 2
-size_flags_vertical = 3
-script = ExtResource("10_bgene")
-
-[node name="VBoxContainer" type="VBoxContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/MapList" unique_id=1238256777]
-layout_mode = 2
-size_flags_horizontal = 3
-
-[node name="Bots" type="VBoxContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer" unique_id=858278563]
-layout_mode = 2
-
-[node name="EnableBots" type="CheckButton" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/Bots" unique_id=1136395709]
-layout_mode = 2
-text = "c.menu.lobby.enable_bots"
-
-[node name="ScrollContainerCustom" type="ScrollContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/Bots" unique_id=473644111]
-visible = false
-layout_mode = 2
-size_flags_vertical = 3
-script = ExtResource("10_bgene")
-
-[node name="BotSettings" type="VBoxContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/Bots/ScrollContainerCustom" unique_id=1392457686]
-layout_mode = 2
-size_flags_horizontal = 3
-size_flags_vertical = 3
-
-[node name="VBoxContainer" type="VBoxContainer" parent="Sidebar/Bottom/MarginContainer/VBoxContainer" unique_id=1825997822]
-layout_mode = 2
-theme_override_constants/separation = 15
-alignment = 1
-
-[node name="Start" type="Button" parent="Sidebar/Bottom/MarginContainer/VBoxContainer/VBoxContainer" unique_id=1330854382]
-layout_mode = 2
-text = "c.menu.lobby.start"
-expand_icon = true
-script = ExtResource("12_7mu2u")
-controller_texture = ExtResource("9_q14bw")
-press_action = "start_game"
-metadata/_custom_type_script = "uid://b1eomxildrq30"
-
-[connection signal="pressed" from="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer/Left" to="." method="_on_left_pressed"]
-[connection signal="pressed" from="Sidebar/Bottom/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer2/Right" to="." method="_on_right_pressed"]
-[connection signal="toggled" from="Sidebar/Bottom/MarginContainer/VBoxContainer/Bots/EnableBots" to="." method="_on_enable_bots_toggled"]
-[connection signal="pressed" from="Sidebar/Bottom/MarginContainer/VBoxContainer/VBoxContainer/Start" to="." method="_on_start_pressed"]
diff --git a/client/gui/overlays/lobby/player.gd b/client/gui/overlays/lobby/player.gd
index 175d6341..5b2b50e4 100644
--- a/client/gui/overlays/lobby/player.gd
+++ b/client/gui/overlays/lobby/player.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/lobby/player.tscn b/client/gui/overlays/lobby/player.tscn
index 3b837bb4..4c924fb9 100644
--- a/client/gui/overlays/lobby/player.tscn
+++ b/client/gui/overlays/lobby/player.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://gmldnel4xbxy"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_flfqn"]
[ext_resource type="Material" uid="uid://beea1pc5nt67r" path="res://gui/resources/materials/dark_blur_material.tres" id="1_jy1rs"]
[ext_resource type="Texture2D" uid="uid://222w1wha75od" path="res://gui/resources/icons/user.webp" id="2_mnaqt"]
[ext_resource type="Script" uid="uid://buxb488rr2ncs" path="res://gui/overlays/lobby/player.gd" id="2_w3lyk"]
@@ -17,9 +16,8 @@ content_margin_right = 8.0
[node name="Player" type="PanelContainer" unique_id=141359444]
material = ExtResource("1_jy1rs")
-offset_right = 40.0
+offset_right = 98.0
offset_bottom = 40.0
-theme = ExtResource("1_flfqn")
theme_override_styles/panel = SubResource("StyleBoxFlat_1227j")
script = ExtResource("2_w3lyk")
diff --git a/client/gui/overlays/overlays.gd b/client/gui/overlays/overlays.gd
index b7d1bb21..fc24fb2d 100644
--- a/client/gui/overlays/overlays.gd
+++ b/client/gui/overlays/overlays.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/overlays.tscn b/client/gui/overlays/overlays.tscn
index 1a183806..158ec403 100644
--- a/client/gui/overlays/overlays.tscn
+++ b/client/gui/overlays/overlays.tscn
@@ -4,7 +4,6 @@
[ext_resource type="PackedScene" uid="uid://xcxbmynn8mhi" path="res://gui/overlays/chat.tscn" id="1_n4uhr"]
[ext_resource type="PackedScene" uid="uid://bpikve6wlsjfl" path="res://gui/overlays/score.tscn" id="2_whygm"]
[ext_resource type="PackedScene" uid="uid://dcrr1rwdwbkq8" path="res://gui/overlays/pinned_messages.tscn" id="3_dcvak"]
-[ext_resource type="PackedScene" uid="uid://bc50la65ntifb" path="res://gui/overlays/lobby/lobby.tscn" id="4_jwd7s"]
[ext_resource type="PackedScene" uid="uid://c7pykhpdhgs64" path="res://gui/overlays/announce_title.tscn" id="5_whygm"]
[ext_resource type="PackedScene" uid="uid://b21nrnkygiyjt" path="res://gui/overlays/popup_message/popup_message.tscn" id="7_jwd7s"]
[ext_resource type="PackedScene" uid="uid://3lytexnfrub6" path="res://gui/overlays/debug/debug.tscn" id="8_8ouu3"]
@@ -27,9 +26,6 @@ layout_mode = 1
[node name="ChatPreview" parent="." unique_id=1306284704 instance=ExtResource("1_n4uhr")]
layout_mode = 1
-[node name="Lobby" parent="." unique_id=568929732 instance=ExtResource("4_jwd7s")]
-layout_mode = 1
-
[node name="AnnounceTitle" parent="." unique_id=1456313117 instance=ExtResource("5_whygm")]
layout_mode = 1
anchors_preset = 15
diff --git a/client/gui/overlays/pinned_messages.gd b/client/gui/overlays/pinned_messages.gd
index fe82f904..1cca455d 100644
--- a/client/gui/overlays/pinned_messages.gd
+++ b/client/gui/overlays/pinned_messages.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/popup_message/popup_message.gd b/client/gui/overlays/popup_message/popup_message.gd
index 00b42a8b..0fd88b78 100644
--- a/client/gui/overlays/popup_message/popup_message.gd
+++ b/client/gui/overlays/popup_message/popup_message.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/popup_message/popup_message.tscn b/client/gui/overlays/popup_message/popup_message.tscn
index e75cd168..dd6c1bc8 100644
--- a/client/gui/overlays/popup_message/popup_message.tscn
+++ b/client/gui/overlays/popup_message/popup_message.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://b21nrnkygiyjt"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_a1566"]
[ext_resource type="Script" uid="uid://c2cx41lrgf5b0" path="res://gui/overlays/popup_message/popup_message.gd" id="2_sbew6"]
[ext_resource type="PackedScene" uid="uid://dq61p3a8og2b6" path="res://gui/overlays/popup_message/server_message.tscn" id="3_m3rok"]
@@ -26,7 +25,6 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
-theme = ExtResource("1_a1566")
[node name="VBox" type="VBoxContainer" parent="Static" unique_id=312654300]
layout_mode = 2
diff --git a/client/gui/overlays/popup_message/server_message.gd b/client/gui/overlays/popup_message/server_message.gd
index ad3c5ed3..a17ff612 100644
--- a/client/gui/overlays/popup_message/server_message.gd
+++ b/client/gui/overlays/popup_message/server_message.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -57,14 +57,12 @@ func build_message(m: MessageParser, use_monospace: bool) -> Control:
MessageParser.Kind.ITEM:
var r: Renderer = RENDERER.instantiate()
r.get_node("SubViewport").size = Vector2i.ONE * font_size(use_monospace) * 2
- r.mode = Renderer.Mode.ITEMS
- r.setup_object(m.result)
+ r.setup_item(m.result)
return r
MessageParser.Kind.TILE:
var r: Renderer = RENDERER.instantiate()
r.get_node("SubViewport").size = Vector2i.ONE * font_size(use_monospace) * 2
- r.mode = Renderer.Mode.TILES
- r.setup_object(m.result)
+ r.setup_tile([m.result])
return r
MessageParser.Kind.TEXT:
return build_label(m.result, use_monospace)
diff --git a/client/gui/overlays/score.gd b/client/gui/overlays/score.gd
index f66ae559..a5207746 100644
--- a/client/gui/overlays/score.gd
+++ b/client/gui/overlays/score.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/gui/overlays/score.tscn b/client/gui/overlays/score.tscn
index 566bdf8e..db3e8723 100644
--- a/client/gui/overlays/score.tscn
+++ b/client/gui/overlays/score.tscn
@@ -1,6 +1,5 @@
[gd_scene format=3 uid="uid://bpikve6wlsjfl"]
-[ext_resource type="Theme" uid="uid://b0qmvo504e457" path="res://gui/resources/theme/theme.tres" id="1_4kujw"]
[ext_resource type="Script" uid="uid://mcgg3q0l03dx" path="res://gui/overlays/score.gd" id="2_kbjds"]
[ext_resource type="Texture2D" uid="uid://chxkwohi56cxx" path="res://gui/resources/shaders/paper.tres" id="3_oum5g"]
[ext_resource type="FontFile" uid="uid://bo4vh5xkpvrh1" path="res://gui/resources/fonts/font-sansita-swashed.woff2" id="3_u54fv"]
@@ -16,7 +15,6 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
-theme = ExtResource("1_4kujw")
theme_override_styles/panel = SubResource("StyleBoxFlat_04ujj")
script = ExtResource("2_kbjds")
diff --git a/client/gui/resources/materials/dark_blur_material.tres b/client/gui/resources/materials/dark_blur_material.tres
index d0dcd075..e6a74077 100644
--- a/client/gui/resources/materials/dark_blur_material.tres
+++ b/client/gui/resources/materials/dark_blur_material.tres
@@ -5,7 +5,7 @@
[resource]
shader = ExtResource("1_cynu0")
shader_parameter/blur_amount = 3.5
-shader_parameter/mix_amount = 0.5
+shader_parameter/mix_amount = 0.6
shader_parameter/mix_amount_no_blur = 0.7
shader_parameter/color_over = Color(0, 0, 0, 1)
shader_parameter/enable_blur = false
diff --git a/client/gui/resources/shaders/blur_mix.gdshader b/client/gui/resources/shaders/blur_mix.gdshader
index 97686a54..fc29603f 100644
--- a/client/gui/resources/shaders/blur_mix.gdshader
+++ b/client/gui/resources/shaders/blur_mix.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -27,6 +27,7 @@ uniform bool enable_blur;
void fragment() {
if (enable_blur) {
vec4 blurred = textureLod(SCREEN_TEXTURE, SCREEN_UV, blur_amount);
+ blurred.a = 1.;
COLOR = mix(blurred, color_over, mix_amount);
} else {
COLOR = mix(texture(SCREEN_TEXTURE, SCREEN_UV), color_over, mix_amount_no_blur);
diff --git a/client/gui/resources/shaders/clouds_canvas_item.gdshader b/client/gui/resources/shaders/clouds_canvas_item.gdshader
index 475da409..b5868b74 100644
--- a/client/gui/resources/shaders/clouds_canvas_item.gdshader
+++ b/client/gui/resources/shaders/clouds_canvas_item.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/client/gui/resources/shaders/grayscale.gdshader b/client/gui/resources/shaders/grayscale.gdshader
index c058e7bf..61ae539c 100644
--- a/client/gui/resources/shaders/grayscale.gdshader
+++ b/client/gui/resources/shaders/grayscale.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/client/gui/resources/shaders/printed.gdshader b/client/gui/resources/shaders/printed.gdshader
index c24cb679..adcabb7b 100644
--- a/client/gui/resources/shaders/printed.gdshader
+++ b/client/gui/resources/shaders/printed.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/client/gui/resources/style/error_focus_style.tres b/client/gui/resources/style/error_focus_style.tres
index f7c44505..c1472895 100644
--- a/client/gui/resources/style/error_focus_style.tres
+++ b/client/gui/resources/style/error_focus_style.tres
@@ -5,13 +5,13 @@ content_margin_left = 10.0
content_margin_top = 10.0
content_margin_right = 10.0
content_margin_bottom = 10.0
-bg_color = Color(1, 0, 0, 0.12549)
+bg_color = Color(0.36078432, 0, 0, 0)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(1, 0.81804, 0.818076, 1)
-corner_radius_top_left = 5
-corner_radius_top_right = 5
-corner_radius_bottom_right = 5
-corner_radius_bottom_left = 5
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/client/gui/resources/style/error_normal_style.tres b/client/gui/resources/style/error_normal_style.tres
index f7bc48fb..a22ca2ee 100644
--- a/client/gui/resources/style/error_normal_style.tres
+++ b/client/gui/resources/style/error_normal_style.tres
@@ -5,9 +5,9 @@ content_margin_left = 10.0
content_margin_top = 10.0
content_margin_right = 10.0
content_margin_bottom = 10.0
-bg_color = Color(1, 0, 0, 0.12549)
+bg_color = Color(0.36078432, 0, 0, 0.2509804)
border_color = Color(1, 0.81804, 0.818076, 1)
-corner_radius_top_left = 5
-corner_radius_top_right = 5
-corner_radius_bottom_right = 5
-corner_radius_bottom_left = 5
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/client/gui/resources/style/focus_style.tres b/client/gui/resources/style/focus_style.tres
index a01843fe..a8169f9d 100644
--- a/client/gui/resources/style/focus_style.tres
+++ b/client/gui/resources/style/focus_style.tres
@@ -5,13 +5,13 @@ content_margin_left = 10.0
content_margin_top = 10.0
content_margin_right = 10.0
content_margin_bottom = 10.0
-bg_color = Color(1, 1, 1, 0.0627451)
+bg_color = Color(0.03137255, 0.03137255, 0.03137255, 0)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.818673, 0.926505, 1, 1)
-corner_radius_top_left = 5
-corner_radius_top_right = 5
-corner_radius_bottom_right = 5
-corner_radius_bottom_left = 5
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/client/gui/resources/style/hover_style.tres b/client/gui/resources/style/hover_style.tres
index 963ee2ca..2d65f39a 100644
--- a/client/gui/resources/style/hover_style.tres
+++ b/client/gui/resources/style/hover_style.tres
@@ -5,8 +5,8 @@ content_margin_left = 10.0
content_margin_top = 10.0
content_margin_right = 10.0
content_margin_bottom = 10.0
-bg_color = Color(1, 1, 1, 0.266667)
-corner_radius_top_left = 5
-corner_radius_top_right = 5
-corner_radius_bottom_right = 5
-corner_radius_bottom_left = 5
+bg_color = Color(1, 1, 1, 0.1254902)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/client/gui/resources/style/normal_style.tres b/client/gui/resources/style/normal_style.tres
index 96aecc8e..3d1da731 100644
--- a/client/gui/resources/style/normal_style.tres
+++ b/client/gui/resources/style/normal_style.tres
@@ -5,8 +5,8 @@ content_margin_left = 10.0
content_margin_top = 10.0
content_margin_right = 10.0
content_margin_bottom = 10.0
-bg_color = Color(1, 1, 1, 0.0352941)
-corner_radius_top_left = 5
-corner_radius_top_right = 5
-corner_radius_bottom_right = 5
-corner_radius_bottom_left = 5
+bg_color = Color(0.03137255, 0.03137255, 0.03137255, 0.2509804)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/client/gui/resources/style/panel_button_backround_style.tres b/client/gui/resources/style/panel_button_backround_style.tres
new file mode 100644
index 00000000..6b27ca1e
--- /dev/null
+++ b/client/gui/resources/style/panel_button_backround_style.tres
@@ -0,0 +1,8 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://d1xhwgrptnlli"]
+
+[resource]
+bg_color = Color(0, 0, 0, 0.6)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/client/gui/resources/style/panel_style.tres b/client/gui/resources/style/panel_style.tres
index d1f27667..f12ec8bf 100644
--- a/client/gui/resources/style/panel_style.tres
+++ b/client/gui/resources/style/panel_style.tres
@@ -2,7 +2,7 @@
[resource]
bg_color = Color(0, 0, 0, 0.6)
-corner_radius_top_left = 8
-corner_radius_top_right = 8
-corner_radius_bottom_right = 8
-corner_radius_bottom_left = 8
+corner_radius_top_left = 16
+corner_radius_top_right = 16
+corner_radius_bottom_right = 16
+corner_radius_bottom_left = 16
diff --git a/client/gui/resources/style/lobby_panel_override.tres b/client/gui/resources/style/square_panel_override.tres
index 04fd16b0..04fd16b0 100644
--- a/client/gui/resources/style/lobby_panel_override.tres
+++ b/client/gui/resources/style/square_panel_override.tres
diff --git a/client/gui/resources/theme/theme.tres b/client/gui/resources/theme/default.tres
index fdde8f70..2b3344b1 100644
--- a/client/gui/resources/theme/theme.tres
+++ b/client/gui/resources/theme/default.tres
@@ -33,9 +33,8 @@ variation_embolden = 0.7
[sub_resource type="FontVariation" id="FontVariation_lyo8w"]
base_font = ExtResource("1_f8qb0")
variation_opentype = {
-2003265652: 200
+2003265652: 500
}
-variation_embolden = 1.0
spacing_top = 5
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_or5ri"]
@@ -58,7 +57,7 @@ CheckButton/styles/pressed = ExtResource("2_8fwoi")
HSeparator/styles/separator = SubResource("StyleBoxLine_emtvk")
Label/font_sizes/font_size = 16
LineEdit/styles/focus = ExtResource("1_x88rs")
-LineEdit/styles/normal = SubResource("StyleBoxFlat_25x32")
+LineEdit/styles/normal = ExtResource("2_8fwoi")
LineEdit/styles/read_only = SubResource("StyleBoxFlat_25x32")
MarginContainer/constants/margin_bottom = 32
MarginContainer/constants/margin_left = 32
diff --git a/client/locale_book b/client/locale_book
deleted file mode 120000
index 89e799e8..00000000
--- a/client/locale_book
+++ /dev/null
@@ -1 +0,0 @@
-../book/locale \ No newline at end of file
diff --git a/client/map/auto_setup/environment_setup.gd b/client/map/auto_setup/environment_setup.gd
index 2dc5ea36..25d75b35 100644
--- a/client/map/auto_setup/environment_setup.gd
+++ b/client/map/auto_setup/environment_setup.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/auto_setup/light_setup.gd b/client/map/auto_setup/light_setup.gd
index e63a6ab9..3056998e 100644
--- a/client/map/auto_setup/light_setup.gd
+++ b/client/map/auto_setup/light_setup.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/auto_setup/sky_light_setup.gd b/client/map/auto_setup/sky_light_setup.gd
index cb59fbf1..f888f79d 100644
--- a/client/map/auto_setup/sky_light_setup.gd
+++ b/client/map/auto_setup/sky_light_setup.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/particles/effect.gd b/client/map/effects/angry/angry.gd
index 1b221248..151507cd 100644
--- a/client/player/particles/effect.gd
+++ b/client/map/effects/angry/angry.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,24 +13,14 @@
# 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/>.
#
-class_name Effect
-extends Node3D
+extends Effect
-@onready var success = $Success
@onready var failure = $Failure
@onready var angry_grunt: PlayRandom = $AngryGrunt
-@onready var stars = $Stars
-@onready var angry = $Angry
+@onready var particles = $Particles
-
-func set_effect(e: String):
- match e:
- "satisfied":
- stars.emitting = true
- success.play()
- "angry":
- angry.emitting = true
- angry_grunt.play_random()
- failure.play()
- _:
- push_warning("effect %s unknown" % e)
+func _ready() -> void:
+ delete_timer(5)
+ particles.emitting = true
+ angry_grunt.play_random()
+ failure.play()
diff --git a/client/map/effects/angry/angry.gd.uid b/client/map/effects/angry/angry.gd.uid
new file mode 100644
index 00000000..642ef1f2
--- /dev/null
+++ b/client/map/effects/angry/angry.gd.uid
@@ -0,0 +1 @@
+uid://dxiq14ilf5bk
diff --git a/client/map/effects/angry/angry.tscn b/client/map/effects/angry/angry.tscn
new file mode 100644
index 00000000..94c6473d
--- /dev/null
+++ b/client/map/effects/angry/angry.tscn
@@ -0,0 +1,63 @@
+[gd_scene format=3 uid="uid://cvty1rwt52anq"]
+
+[ext_resource type="Texture2D" uid="uid://unjbxplj845n" path="res://map/effects/angry/angry.webp" id="1_5op6v"]
+[ext_resource type="Script" uid="uid://dxiq14ilf5bk" path="res://map/effects/angry/angry.gd" id="1_m21dl"]
+[ext_resource type="AudioStream" uid="uid://cv4isy6po6pqd" path="res://gui/resources/sounds/failure.ogg" id="2_vlpct"]
+[ext_resource type="Script" uid="uid://n4jwod1jfuiv" path="res://audio/play_random.gd" id="3_o0kjt"]
+[ext_resource type="AudioStream" uid="uid://c3gatgrsb0npf" path="res://player/sounds/angry1.ogg" id="4_wfdot"]
+[ext_resource type="AudioStream" uid="uid://cty282m6ckt62" path="res://player/sounds/angry2.ogg" id="5_ym83o"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ysmnk"]
+transparency = 1
+no_depth_test = true
+shading_mode = 0
+vertex_color_use_as_albedo = true
+albedo_texture = ExtResource("1_5op6v")
+billboard_mode = 3
+billboard_keep_scale = true
+particles_anim_h_frames = 1
+particles_anim_v_frames = 1
+particles_anim_loop = false
+
+[sub_resource type="QuadMesh" id="QuadMesh_5nim7"]
+material = SubResource("StandardMaterial3D_ysmnk")
+
+[sub_resource type="Curve" id="Curve_0rju1"]
+_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.51927, 1), 0.0, 0.0, 0, 0]
+point_count = 2
+
+[sub_resource type="Gradient" id="Gradient_lmymu"]
+offsets = PackedFloat32Array(0, 0.711828, 1)
+colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
+
+[node name="Angry" type="Node3D" unique_id=1448163105]
+script = ExtResource("1_m21dl")
+
+[node name="Particles" type="CPUParticles3D" parent="." unique_id=691382738]
+emitting = false
+amount = 5
+lifetime = 2.0
+one_shot = true
+explosiveness = 1.0
+mesh = SubResource("QuadMesh_5nim7")
+direction = Vector3(0, 1, 0)
+spread = 30.0
+gravity = Vector3(0, 0, 0)
+initial_velocity_min = 1.0
+initial_velocity_max = 1.5
+scale_amount_curve = SubResource("Curve_0rju1")
+color_ramp = SubResource("Gradient_lmymu")
+
+[node name="Failure" type="AudioStreamPlayer" parent="." unique_id=1310258100]
+stream = ExtResource("2_vlpct")
+volume_db = -8.0
+
+[node name="AngryGrunt" type="Node3D" parent="." unique_id=681036661]
+script = ExtResource("3_o0kjt")
+volume_db = -8.0
+
+[node name="Angry1" type="AudioStreamPlayer3D" parent="AngryGrunt" unique_id=278427723]
+stream = ExtResource("4_wfdot")
+
+[node name="Angry2" type="AudioStreamPlayer3D" parent="AngryGrunt" unique_id=1539145236]
+stream = ExtResource("5_ym83o")
diff --git a/client/player/particles/angry/angry.webp b/client/map/effects/angry/angry.webp
index 866ba92c..866ba92c 100644
--- a/client/player/particles/angry/angry.webp
+++ b/client/map/effects/angry/angry.webp
Binary files differ
diff --git a/client/player/particles/angry/angry.webp.import b/client/map/effects/angry/angry.webp.import
index fe6b5449..eac70392 100644
--- a/client/player/particles/angry/angry.webp.import
+++ b/client/map/effects/angry/angry.webp.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://unjbxplj845n"
-path="res://.godot/imported/angry.webp-f649f66bf6a009b3b61480c4e451c61b.ctex"
+path="res://.godot/imported/angry.webp-fd51236e60c2d6e246b04296d67f5f19.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://player/particles/angry/angry.webp"
-dest_files=["res://.godot/imported/angry.webp-f649f66bf6a009b3b61480c4e451c61b.ctex"]
+source_file="res://map/effects/angry/angry.webp"
+dest_files=["res://.godot/imported/angry.webp-fd51236e60c2d6e246b04296d67f5f19.ctex"]
[params]
diff --git a/client/map/tiles/counter_base.gd b/client/map/effects/effect.gd
index d29953ad..a7b08bae 100644
--- a/client/map/tiles/counter_base.gd
+++ b/client/map/effects/effect.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,17 +13,10 @@
# 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/>.
#
-class_name CounterBase
-extends Counter
+class_name Effect
+extends Node3D
-func _init(ctx: TileFactory.TileCC, top):
- super(ctx)
- match kind:
- CounterKind.OUTER_CORNER:
- base.add_child(load("res://map/tiles/counter_outer_corner.tscn").instantiate())
- CounterKind.STRAIGHT:
- base.add_child(load("res://map/tiles/counter_straight.tscn").instantiate())
- CounterKind.STRAIGHT_BACKSPLASH:
- base.add_child(load("res://map/tiles/counter_straight_backsplash.tscn").instantiate())
- if top is PackedScene:
- base.add_child(top.instantiate())
+var data: Variant
+func delete_timer(delay: float):
+ await get_tree().create_timer(delay).timeout
+ queue_free()
diff --git a/client/player/particles/effect.gd.uid b/client/map/effects/effect.gd.uid
index b9ddd1ee..b9ddd1ee 100644
--- a/client/player/particles/effect.gd.uid
+++ b/client/map/effects/effect.gd.uid
diff --git a/client/map/effects/effect_factory.gd b/client/map/effects/effect_factory.gd
new file mode 100644
index 00000000..246aca1f
--- /dev/null
+++ b/client/map/effects/effect_factory.gd
@@ -0,0 +1,34 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2026 Hurry Curry! Contributors
+#
+# 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/>.
+#
+class_name EffectFactory
+extends Object
+
+const ANGRY = preload("res://map/effects/angry/angry.tscn")
+const SATISFIED = preload("res://map/effects/satisfied/satisfied.tscn")
+const POINTS = preload("res://map/effects/points/points.tscn")
+
+static func create_effect(p: Dictionary):
+ match p["effect"]:
+ "angry": return ANGRY.instantiate()
+ "satisfied": return SATISFIED.instantiate()
+ "points":
+ var points = POINTS.instantiate()
+ points.amount = p["amount"]
+ return points
+ _: push_error("unknown effect " + p["effect"])
+
+static func play_effect(target: Node3D, p: Dictionary):
+ target.add_child(create_effect(p))
diff --git a/client/map/effects/effect_factory.gd.uid b/client/map/effects/effect_factory.gd.uid
new file mode 100644
index 00000000..8a154ad0
--- /dev/null
+++ b/client/map/effects/effect_factory.gd.uid
@@ -0,0 +1 @@
+uid://w2l71qjjoshv
diff --git a/client/map/effects/points/points.gd b/client/map/effects/points/points.gd
new file mode 100644
index 00000000..35894902
--- /dev/null
+++ b/client/map/effects/points/points.gd
@@ -0,0 +1,30 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2026 Hurry Curry! Contributors
+#
+# 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/>.
+#
+extends Effect
+
+var amount: int
+
+@onready var points: CPUParticles3D = $Points
+@onready var amount_label: Label = $SubViewport/Control/AmountLabel
+
+func _ready():
+ delete_timer(5)
+ var num_string: String = str(amount)
+ if amount >= 0:
+ num_string = "+" + num_string
+ points.color = Color(.1,.6,.1) if amount >= 0 else Color(.6,.1,.1)
+ amount_label.text = num_string
+ points.emitting = true
diff --git a/client/map/effects/points/points.gd.uid b/client/map/effects/points/points.gd.uid
new file mode 100644
index 00000000..2bf5b594
--- /dev/null
+++ b/client/map/effects/points/points.gd.uid
@@ -0,0 +1 @@
+uid://528xphnsg62u
diff --git a/client/map/effects/points/points.tscn b/client/map/effects/points/points.tscn
new file mode 100644
index 00000000..57e11ddf
--- /dev/null
+++ b/client/map/effects/points/points.tscn
@@ -0,0 +1,90 @@
+[gd_scene format=3 uid="uid://x00adyx4umft"]
+
+[ext_resource type="Script" uid="uid://528xphnsg62u" path="res://map/effects/points/points.gd" id="1_wveft"]
+[ext_resource type="AudioStream" uid="uid://camy77x26mmpv" path="res://gui/resources/sounds/success.ogg" id="2_ixmsm"]
+[ext_resource type="FontFile" uid="uid://bo4vh5xkpvrh1" path="res://gui/resources/fonts/font-sansita-swashed.woff2" id="3_op1cb"]
+
+[sub_resource type="FontVariation" id="FontVariation_u8wnh"]
+base_font = ExtResource("3_op1cb")
+variation_opentype = {
+2003265652: 600
+}
+
+[sub_resource type="ViewportTexture" id="ViewportTexture_7hygc"]
+viewport_path = NodePath("SubViewport")
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_uepl5"]
+resource_local_to_scene = true
+render_priority = 1
+transparency = 1
+no_depth_test = true
+shading_mode = 0
+vertex_color_use_as_albedo = true
+albedo_texture = SubResource("ViewportTexture_7hygc")
+billboard_mode = 3
+billboard_keep_scale = true
+particles_anim_h_frames = 1
+particles_anim_v_frames = 1
+particles_anim_loop = false
+
+[sub_resource type="QuadMesh" id="QuadMesh_u8wnh"]
+resource_local_to_scene = true
+material = SubResource("StandardMaterial3D_uepl5")
+
+[sub_resource type="Curve" id="Curve_0rju1"]
+_data = [Vector2(0, 0), 0.0, 5.0, 0, 0, Vector2(0.49840245, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), -5.0, 0.0, 0, 0]
+point_count = 3
+
+[sub_resource type="Gradient" id="Gradient_uepl5"]
+offsets = PackedFloat32Array(0, 0.26672226, 0.4904026, 1)
+colors = PackedColorArray(1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
+
+[node name="Points" type="Node3D" unique_id=1800089616]
+script = ExtResource("1_wveft")
+
+[node name="Success" type="AudioStreamPlayer" parent="." unique_id=710129954]
+stream = ExtResource("2_ixmsm")
+
+[node name="SubViewport" type="SubViewport" parent="." unique_id=2117836549]
+transparent_bg = true
+size = Vector2i(256, 256)
+
+[node name="Control" type="Control" parent="SubViewport" unique_id=1691084866]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="AmountLabel" type="Label" parent="SubViewport/Control" unique_id=358131106]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_colors/font_outline_color = Color(0.1254902, 0.1254902, 0.1254902, 1)
+theme_override_constants/outline_size = 48
+theme_override_fonts/font = SubResource("FontVariation_u8wnh")
+theme_override_font_sizes/font_size = 165
+text = "+5"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="Points" type="CPUParticles3D" parent="." unique_id=1697923795]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+emitting = false
+amount = 1
+one_shot = true
+explosiveness = 1.0
+mesh = SubResource("QuadMesh_u8wnh")
+direction = Vector3(0, -1, 0)
+spread = 0.0
+gravity = Vector3(0, 0, 0)
+initial_velocity_min = 0.01
+initial_velocity_max = 0.01
+scale_amount_min = 0.65
+scale_amount_max = 0.65
+scale_amount_curve = SubResource("Curve_0rju1")
+color_ramp = SubResource("Gradient_uepl5")
diff --git a/client/map/effects/satisfied/satisfied.gd b/client/map/effects/satisfied/satisfied.gd
new file mode 100644
index 00000000..67cbb4d2
--- /dev/null
+++ b/client/map/effects/satisfied/satisfied.gd
@@ -0,0 +1,24 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2026 Hurry Curry! Contributors
+#
+# 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/>.
+#
+extends Effect
+
+@onready var success = $Success
+@onready var stars = $Stars
+
+func _ready():
+ delete_timer(5)
+ stars.emitting = true
+ success.play()
diff --git a/client/map/effects/satisfied/satisfied.gd.uid b/client/map/effects/satisfied/satisfied.gd.uid
new file mode 100644
index 00000000..904996ad
--- /dev/null
+++ b/client/map/effects/satisfied/satisfied.gd.uid
@@ -0,0 +1 @@
+uid://dsepgasa5ki43
diff --git a/client/player/particles/satisfied/stars.tscn b/client/map/effects/satisfied/satisfied.tscn
index 0518f973..bbd337c4 100644
--- a/client/player/particles/satisfied/stars.tscn
+++ b/client/map/effects/satisfied/satisfied.tscn
@@ -1,6 +1,8 @@
[gd_scene format=3 uid="uid://yaed1vnhd0aa"]
-[ext_resource type="Texture2D" uid="uid://b10goh4dsa3b0" path="res://player/particles/satisfied/star.webp" id="1_v8q3r"]
+[ext_resource type="AudioStream" uid="uid://camy77x26mmpv" path="res://gui/resources/sounds/success.ogg" id="1_rxp1p"]
+[ext_resource type="Script" uid="uid://dsepgasa5ki43" path="res://map/effects/satisfied/satisfied.gd" id="1_sujye"]
+[ext_resource type="Texture2D" uid="uid://b10goh4dsa3b0" path="res://map/effects/satisfied/star.webp" id="1_v8q3r"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_d8uy0"]
transparency = 1
@@ -25,7 +27,14 @@ point_count = 2
offsets = PackedFloat32Array(0, 0.711828, 1)
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
-[node name="Stars" type="CPUParticles3D" unique_id=1697923795]
+[node name="Satisfied" type="Node3D" unique_id=1800089616]
+script = ExtResource("1_sujye")
+
+[node name="Success" type="AudioStreamPlayer" parent="." unique_id=710129954]
+stream = ExtResource("1_rxp1p")
+
+[node name="Stars" type="CPUParticles3D" parent="." unique_id=1697923795]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
emitting = false
amount = 5
lifetime = 2.0
diff --git a/client/player/particles/satisfied/star.webp b/client/map/effects/satisfied/star.webp
index c1d2e8ff..c1d2e8ff 100644
--- a/client/player/particles/satisfied/star.webp
+++ b/client/map/effects/satisfied/star.webp
Binary files differ
diff --git a/client/player/particles/satisfied/star.webp.import b/client/map/effects/satisfied/star.webp.import
index 816f6af0..2ac435b7 100644
--- a/client/player/particles/satisfied/star.webp.import
+++ b/client/map/effects/satisfied/star.webp.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://b10goh4dsa3b0"
-path="res://.godot/imported/star.webp-4edd9a951e46ba686b105839f622c981.ctex"
+path="res://.godot/imported/star.webp-74beadf67879cd7f7736819dd8ef7e05.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://player/particles/satisfied/star.webp"
-dest_files=["res://.godot/imported/star.webp-4edd9a951e46ba686b105839f622c981.ctex"]
+source_file="res://map/effects/satisfied/star.webp"
+dest_files=["res://.godot/imported/star.webp-74beadf67879cd7f7736819dd8ef7e05.ctex"]
[params]
diff --git a/client/map/item_factory.gd b/client/map/item_factory.gd
index 66753a17..5fccb526 100644
--- a/client/map/item_factory.gd
+++ b/client/map/item_factory.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/basket.gd b/client/map/items/basket.gd
index 8b19243a..4da74aca 100644
--- a/client/map/items/basket.gd
+++ b/client/map/items/basket.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/bun.gd b/client/map/items/bun.gd
index 38fd582d..0423d7e6 100644
--- a/client/map/items/bun.gd
+++ b/client/map/items/bun.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/burned.gd b/client/map/items/burned.gd
index 2646a551..2ed456a6 100644
--- a/client/map/items/burned.gd
+++ b/client/map/items/burned.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/cuttable.gd b/client/map/items/cuttable.gd
index e106a96d..4eb48e19 100644
--- a/client/map/items/cuttable.gd
+++ b/client/map/items/cuttable.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/food_processor.gd b/client/map/items/food_processor.gd
index 6aabf11d..fab8bd48 100644
--- a/client/map/items/food_processor.gd
+++ b/client/map/items/food_processor.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/food_processor_fill.gd b/client/map/items/food_processor_fill.gd
index 25d634da..ed695a99 100644
--- a/client/map/items/food_processor_fill.gd
+++ b/client/map/items/food_processor_fill.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/generic_item.gd b/client/map/items/generic_item.gd
index 5f101646..e20385ef 100644
--- a/client/map/items/generic_item.gd
+++ b/client/map/items/generic_item.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/glass.gd b/client/map/items/glass.gd
index b9582a3a..8b0208e1 100644
--- a/client/map/items/glass.gd
+++ b/client/map/items/glass.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/glass_fill.gd b/client/map/items/glass_fill.gd
index 27cceb1f..4331965d 100644
--- a/client/map/items/glass_fill.gd
+++ b/client/map/items/glass_fill.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/glass_items.gd b/client/map/items/glass_items.gd
index 970b25d1..b3a5542b 100644
--- a/client/map/items/glass_items.gd
+++ b/client/map/items/glass_items.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/icecream.gd b/client/map/items/icecream.gd
index 19e3c91d..c29f6ef3 100644
--- a/client/map/items/icecream.gd
+++ b/client/map/items/icecream.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/item.gd b/client/map/items/item.gd
index d3e84e06..bad9c7cf 100644
--- a/client/map/items/item.gd
+++ b/client/map/items/item.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/mochi.gd b/client/map/items/mochi.gd
index 9ec98658..dc3a860d 100644
--- a/client/map/items/mochi.gd
+++ b/client/map/items/mochi.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/pan.gd b/client/map/items/pan.gd
index 95d6433a..99be1805 100644
--- a/client/map/items/pan.gd
+++ b/client/map/items/pan.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/pizza.gd b/client/map/items/pizza.gd
index fefd4fbc..a6c1a2ee 100644
--- a/client/map/items/pizza.gd
+++ b/client/map/items/pizza.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/plate.gd b/client/map/items/plate.gd
index f517d79a..e71b7d0a 100644
--- a/client/map/items/plate.gd
+++ b/client/map/items/plate.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/plate_fill.gd b/client/map/items/plate_fill.gd
index 057fc11f..fc2a3310 100644
--- a/client/map/items/plate_fill.gd
+++ b/client/map/items/plate_fill.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/pot.gd b/client/map/items/pot.gd
index c5039bd7..06b63f58 100644
--- a/client/map/items/pot.gd
+++ b/client/map/items/pot.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/pot_fill.gd b/client/map/items/pot_fill.gd
index 0a59038d..e8dfaa1e 100644
--- a/client/map/items/pot_fill.gd
+++ b/client/map/items/pot_fill.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/pot_fill_transparent.gd b/client/map/items/pot_fill_transparent.gd
index 77848cb6..f8585733 100644
--- a/client/map/items/pot_fill_transparent.gd
+++ b/client/map/items/pot_fill_transparent.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/rolled_dough.gd b/client/map/items/rolled_dough.gd
index 86c12062..3d0b5d25 100644
--- a/client/map/items/rolled_dough.gd
+++ b/client/map/items/rolled_dough.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/items/unknown_item.gd b/client/map/items/unknown_item.gd
index ec6cab1a..da43eb08 100644
--- a/client/map/items/unknown_item.gd
+++ b/client/map/items/unknown_item.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/kitchen_background.gd b/client/map/kitchen_background.gd
index a638ad5f..456aa598 100644
--- a/client/map/kitchen_background.gd
+++ b/client/map/kitchen_background.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/map.gd b/client/map/map.gd
index 51359359..00f0f7b3 100644
--- a/client/map/map.gd
+++ b/client/map/map.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -26,9 +26,6 @@ class TileInfo:
var interact_tile: Tile
const NEIGHBOR_OFFSETS: Array[Vector2i] = [Vector2i.UP, Vector2i.LEFT, Vector2i.DOWN, Vector2i.RIGHT]
-const TILE_COMBINATOR: Dictionary[Array, Array] = {
- ["counter", "sink"]: ["sink"]
- } # : Dictionary[Array[String], Array[String]]
var tile_by_pos: Dictionary[Vector2i, TileInfo] = {}
var autoflush = false
@@ -65,12 +62,6 @@ func set_tiles(pos: Vector2i, tiles: Array = [], pending_changes: Dictionary[Vec
if autoflush: flush()
func _add_tiles(pos: Vector2i, tiles: Array, pending_changes: Dictionary[Vector2i, Array], srv: Game.ServerContext) -> void:
- # Combinate tiles
- for k in TILE_COMBINATOR:
- if G.has_all(tiles, k):
- for item: String in k: tiles.erase(item)
- tiles.append_array(TILE_COMBINATOR[k])
-
# Find neighbor tile names
var neighbors: Array[Array] = [] # Array[Array[String]]
for offset: Vector2i in NEIGHBOR_OFFSETS:
@@ -80,17 +71,12 @@ func _add_tiles(pos: Vector2i, tiles: Array, pending_changes: Dictionary[Vector2
elif tile_by_pos.has(neighbor_pos):
neighbors.append(tile_by_pos[neighbor_pos])
else: neighbors.append([])
-
+
var tiles_parent = Node3D.new()
tiles_parent.name = str(pos)
- add_child(tiles_parent)
- var tile_instances: Array[Tile] = []
- for tile_name: String in tiles:
- var tile := tile_factory.produce(tile_name, pos, neighbors, tile_instances, srv)
- tile_instances.append(tile)
- tile.position = Vector3(pos.x, 0, pos.y)
- tiles_parent.add_child(tile)
+ var tile_instances = tile_factory.produce(tiles_parent, tiles, pos, neighbors, srv)
tile_by_pos[pos] = TileInfo.new(pos, tiles, tile_instances, tiles_parent)
+ add_child(tiles_parent)
func _remove_tile(pos: Vector2i):
var tile_info = tile_by_pos.get(pos)
diff --git a/client/player/particles/checkmark/checkmark.gd b/client/map/particles/checkmark/checkmark.gd
index 106b1cc7..8a5cc248 100644
--- a/client/player/particles/checkmark/checkmark.gd
+++ b/client/map/particles/checkmark/checkmark.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/particles/checkmark/checkmark.gd.uid b/client/map/particles/checkmark/checkmark.gd.uid
index 913fb945..913fb945 100644
--- a/client/player/particles/checkmark/checkmark.gd.uid
+++ b/client/map/particles/checkmark/checkmark.gd.uid
diff --git a/client/player/particles/checkmark/checkmark.svg b/client/map/particles/checkmark/checkmark.svg
index f8a31e29..f8a31e29 100644
--- a/client/player/particles/checkmark/checkmark.svg
+++ b/client/map/particles/checkmark/checkmark.svg
diff --git a/client/player/particles/checkmark/checkmark.svg.import b/client/map/particles/checkmark/checkmark.svg.import
index 58f42635..4011e4f8 100644
--- a/client/player/particles/checkmark/checkmark.svg.import
+++ b/client/map/particles/checkmark/checkmark.svg.import
@@ -3,8 +3,8 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://c10wjga8ni7eq"
-path.s3tc="res://.godot/imported/checkmark.svg-fa4b3748aa6561b9772a9b05d1a5b098.s3tc.ctex"
-path.etc2="res://.godot/imported/checkmark.svg-fa4b3748aa6561b9772a9b05d1a5b098.etc2.ctex"
+path.s3tc="res://.godot/imported/checkmark.svg-6dd7b104b667f47666846eb9f77215a8.s3tc.ctex"
+path.etc2="res://.godot/imported/checkmark.svg-6dd7b104b667f47666846eb9f77215a8.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
@@ -12,8 +12,8 @@ metadata={
[deps]
-source_file="res://player/particles/checkmark/checkmark.svg"
-dest_files=["res://.godot/imported/checkmark.svg-fa4b3748aa6561b9772a9b05d1a5b098.s3tc.ctex", "res://.godot/imported/checkmark.svg-fa4b3748aa6561b9772a9b05d1a5b098.etc2.ctex"]
+source_file="res://map/particles/checkmark/checkmark.svg"
+dest_files=["res://.godot/imported/checkmark.svg-6dd7b104b667f47666846eb9f77215a8.s3tc.ctex", "res://.godot/imported/checkmark.svg-6dd7b104b667f47666846eb9f77215a8.etc2.ctex"]
[params]
diff --git a/client/player/particles/checkmark/checkmark.tscn b/client/map/particles/checkmark/checkmark.tscn
index fb31a2cc..25e3d606 100644
--- a/client/player/particles/checkmark/checkmark.tscn
+++ b/client/map/particles/checkmark/checkmark.tscn
@@ -1,7 +1,7 @@
[gd_scene format=3 uid="uid://bdbw8whs3data"]
-[ext_resource type="Texture2D" uid="uid://c10wjga8ni7eq" path="res://player/particles/checkmark/checkmark.svg" id="1_co83x"]
-[ext_resource type="Script" uid="uid://bj1h0r3qvy6vm" path="res://player/particles/checkmark/checkmark.gd" id="2_ru6ov"]
+[ext_resource type="Texture2D" uid="uid://c10wjga8ni7eq" path="res://map/particles/checkmark/checkmark.svg" id="1_co83x"]
+[ext_resource type="Script" uid="uid://bj1h0r3qvy6vm" path="res://map/particles/checkmark/checkmark.gd" id="2_ru6ov"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ru6ov"]
transparency = 1
diff --git a/client/map/progress/progress.gd b/client/map/progress/progress.gd
index d8d714ba..be8de5ae 100644
--- a/client/map/progress/progress.gd
+++ b/client/map/progress/progress.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/progress/progress.gdshader b/client/map/progress/progress.gdshader
index 3c3557cd..cbf1a3d2 100644
--- a/client/map/progress/progress.gdshader
+++ b/client/map/progress/progress.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/client/map/tile_factory.gd b/client/map/tile_factory.gd
index 80f0df5e..d3d1ca8d 100644
--- a/client/map/tile_factory.gd
+++ b/client/map/tile_factory.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -33,6 +33,7 @@ class TileCC:
var floor_meshers: Dictionary[String, FloorMesher]
var server_context: Game.ServerContext
var below_tile_instances: Array[Tile]
+ var above_tile_names: Array[String]
var floor_meshers: Dictionary[String, FloorMesher] = {
"floor": FloorMesher.new(Floor.floor_mesh()),
@@ -41,7 +42,31 @@ var floor_meshers: Dictionary[String, FloorMesher] = {
"street": FloorMesher.new(Street.floor_mesh())
}
-func produce(raw_name: String, position: Vector2i, neighbors: Array, below_tile_instances: Array[Tile] = [], server_context: Game.ServerContext = null) -> Tile:
+const TILE_COMBINATOR: Dictionary[Array, Array] = { # : Dictionary[Array[String], Array[String]]
+ ["counter", "sink"]: ["sink"]
+}
+
+func produce(parent: Node3D, tile_names: Array, position: Vector2i, neighbors: Array, server_context: Game.ServerContext = null) -> Array[Tile]:
+ # Combinate tiles
+ tile_names = tile_names.duplicate()
+ for k in TILE_COMBINATOR:
+ if G.has_all(tile_names, k):
+ for item: String in k: tile_names.erase(item)
+ tile_names.append_array(TILE_COMBINATOR[k])
+
+ var tile_instances: Array[Tile] = []
+ for i in range(tile_names.size()):
+ var tile_name: String = tile_names[i]
+ var above_tile_names: Array[String] = []
+ above_tile_names.append_array(tile_names.slice(i+1, tile_names.size()))
+ var tile := produce_part(tile_name, position, neighbors, tile_instances, above_tile_names, server_context)
+ tile_instances.append(tile)
+ tile.position = Vector3(position.x, 0, position.y)
+ parent.add_child(tile)
+
+ return tile_instances
+
+func produce_part(raw_name: String, position: Vector2i, neighbors: Array, below_tile_instances: Array[Tile] = [], above_tile_names: Array[String] = [], server_context: Game.ServerContext = null) -> Tile:
var tile_name = TileName.new(raw_name)
var ctx := TileCC.new()
@@ -55,9 +80,10 @@ func produce(raw_name: String, position: Vector2i, neighbors: Array, below_tile_
ctx.floor_meshers = floor_meshers
ctx.server_context = server_context
ctx.below_tile_instances = below_tile_instances
+ ctx.above_tile_names = above_tile_names
match tile_name.name:
- "book": return CounterBase.new(ctx, preload("res://map/tiles/book.tscn"))
+ "book": return GenericTile.new(ctx, preload("res://map/tiles/book.tscn"))
"button": return Button_.new(ctx)
"button-base": return ButtonBase.new(ctx)
"ceiling-lamp": return GenericTile.new(ctx, preload("res://map/tiles/ceiling_lamp.tscn"))
@@ -66,7 +92,7 @@ func produce(raw_name: String, position: Vector2i, neighbors: Array, below_tile_
"conveyor": return Conveyor.new(ctx)
"counter-window": return CounterWindow.new(ctx)
"counter-window-conveyor": return CounterWindowConveyor.new(ctx)
- "counter": return CounterBase.new(ctx, null)
+ "counter": return Counter.new(ctx)
"cutting-board": return CuttingBoard.new(ctx)
"door": return Door.new(ctx)
"fence": return Fence.new(ctx)
diff --git a/client/map/tiles/active_interact_counter.gd b/client/map/tiles/active_interact_counter.gd
index fef23bba..b44c862a 100644
--- a/client/map/tiles/active_interact_counter.gd
+++ b/client/map/tiles/active_interact_counter.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/button/button.gd b/client/map/tiles/button/button.gd
index 16359d23..f47eba2d 100644
--- a/client/map/tiles/button/button.gd
+++ b/client/map/tiles/button/button.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/button/button_model.gd b/client/map/tiles/button/button_model.gd
index f571d599..41a4674f 100644
--- a/client/map/tiles/button/button_model.gd
+++ b/client/map/tiles/button/button_model.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/button_base.gd b/client/map/tiles/button_base.gd
index 8f6597e9..1ceee5e2 100644
--- a/client/map/tiles/button_base.gd
+++ b/client/map/tiles/button_base.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/chair.gd b/client/map/tiles/chair.gd
index 4cd28cf7..d5669dca 100644
--- a/client/map/tiles/chair.gd
+++ b/client/map/tiles/chair.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/conveyor.gd b/client/map/tiles/conveyor.gd
index 421bc411..e5e908b4 100644
--- a/client/map/tiles/conveyor.gd
+++ b/client/map/tiles/conveyor.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/conveyor_direction.gdshader b/client/map/tiles/conveyor_direction.gdshader
index d59fcd2d..7adef653 100644
--- a/client/map/tiles/conveyor_direction.gdshader
+++ b/client/map/tiles/conveyor_direction.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/client/map/tiles/conveyor_model.gd b/client/map/tiles/conveyor_model.gd
index 81ba1ecd..fcf36675 100644
--- a/client/map/tiles/conveyor_model.gd
+++ b/client/map/tiles/conveyor_model.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/counter.gd b/client/map/tiles/counter.gd
index f91c5cbc..b8e7531a 100644
--- a/client/map/tiles/counter.gd
+++ b/client/map/tiles/counter.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,67 +14,14 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
class_name Counter
-extends Tile
-
-const COUNTERS: Array = [
- "counter",
- "pan",
- "sink",
- "oven",
-]
-
-const FLOORS: Array = [
- "floor",
- "chandelier",
- "ceiling-lamp",
- "grass",
- "table",
- "chair"
-]
-
-enum CounterKind {
- OUTER_CORNER,
- STRAIGHT,
- STRAIGHT_BACKSPLASH
-}
-
-var kind: CounterKind = CounterKind.STRAIGHT
-
-static func interact_target(): # -> Vector3?
- return Vector3(0, 0.5, 0)
+extends CounterLike
func _init(ctx: TileFactory.TileCC):
super(ctx)
-
- var facing: int = 0
- var max_series: int = 0
- var max_idx: int = 0
- for start in range(4):
- var series = 0
- for i in range(4):
- if not is_attachable(ctx.neighbors[(start + i) % 4]):
- series += 1
- else:
- break
- if series > max_series:
- max_series = series
- max_idx = start
-
- # we can neither find out whether it is an inner corner nor an outer corner
- # backsplash
- facing = max_idx
- if max_series == 1:
- if G.has_one(WallTile.WALLS, ctx.neighbors[(max_idx + 2) % 4]):
- kind = CounterKind.STRAIGHT_BACKSPLASH
- else:
- kind = CounterKind.STRAIGHT
- elif max_series == 2:
- kind = CounterKind.OUTER_CORNER
-
- turn_facing(facing)
-
-static func is_attachable(neighbor: Array) -> bool:
- for tile: String in neighbor:
- if tile.ends_with("crate"): return true
- if COUNTERS.has(tile): return true
- return false
+ match kind:
+ CounterKind.OUTER_CORNER:
+ base.add_child(load("res://map/tiles/counter_outer_corner.tscn").instantiate())
+ CounterKind.STRAIGHT:
+ base.add_child(load("res://map/tiles/counter_straight.tscn").instantiate())
+ CounterKind.STRAIGHT_BACKSPLASH:
+ base.add_child(load("res://map/tiles/counter_straight_backsplash.tscn").instantiate())
diff --git a/client/map/tiles/counter.gd.uid b/client/map/tiles/counter.gd.uid
index 0fd45b66..395db459 100644
--- a/client/map/tiles/counter.gd.uid
+++ b/client/map/tiles/counter.gd.uid
@@ -1 +1 @@
-uid://bs61uem0427k6
+uid://bj8o06q70sek2
diff --git a/client/map/tiles/counter_base.gd.uid b/client/map/tiles/counter_base.gd.uid
deleted file mode 100644
index f46c79fa..00000000
--- a/client/map/tiles/counter_base.gd.uid
+++ /dev/null
@@ -1 +0,0 @@
-uid://dx116e5ebg1p4
diff --git a/client/map/tiles/counter_like.gd b/client/map/tiles/counter_like.gd
new file mode 100644
index 00000000..ebecbbfa
--- /dev/null
+++ b/client/map/tiles/counter_like.gd
@@ -0,0 +1,80 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2026 Hurry Curry! Contributors
+#
+# 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/>.
+#
+class_name CounterLike
+extends Tile
+
+const COUNTERS: Array = [
+ "counter",
+ "pan",
+ "sink",
+ "oven",
+]
+
+const FLOORS: Array = [
+ "floor",
+ "chandelier",
+ "ceiling-lamp",
+ "grass",
+ "table",
+ "chair"
+]
+
+enum CounterKind {
+ OUTER_CORNER,
+ STRAIGHT,
+ STRAIGHT_BACKSPLASH
+}
+
+var kind: CounterKind = CounterKind.STRAIGHT
+
+static func interact_target(): # -> Vector3?
+ return Vector3(0, 0.5, 0)
+
+func _init(ctx: TileFactory.TileCC):
+ super(ctx)
+
+ var facing: int = 0
+ var max_series: int = 0
+ var max_idx: int = 0
+ for start in range(4):
+ var series = 0
+ for i in range(4):
+ if not is_attachable(ctx.neighbors[(start + i) % 4]):
+ series += 1
+ else:
+ break
+ if series > max_series:
+ max_series = series
+ max_idx = start
+
+ # we can neither find out whether it is an inner corner nor an outer corner
+ # backsplash
+ facing = max_idx
+ if max_series == 1:
+ if G.has_one(WallTile.WALLS, ctx.neighbors[(max_idx + 2) % 4]):
+ kind = CounterKind.STRAIGHT_BACKSPLASH
+ else:
+ kind = CounterKind.STRAIGHT
+ elif max_series == 2:
+ kind = CounterKind.OUTER_CORNER
+
+ turn_facing(facing)
+
+static func is_attachable(neighbor: Array) -> bool:
+ for tile: String in neighbor:
+ if tile.ends_with("crate"): return true
+ if COUNTERS.has(tile): return true
+ return false
diff --git a/client/map/tiles/counter_like.gd.uid b/client/map/tiles/counter_like.gd.uid
new file mode 100644
index 00000000..0fd45b66
--- /dev/null
+++ b/client/map/tiles/counter_like.gd.uid
@@ -0,0 +1 @@
+uid://bs61uem0427k6
diff --git a/client/map/tiles/counter_window.gd b/client/map/tiles/counter_window.gd
index 8b01979d..d9cf6d24 100644
--- a/client/map/tiles/counter_window.gd
+++ b/client/map/tiles/counter_window.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/counter_window_conveyor.gd b/client/map/tiles/counter_window_conveyor.gd
index cd661298..e34dd2fd 100644
--- a/client/map/tiles/counter_window_conveyor.gd
+++ b/client/map/tiles/counter_window_conveyor.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/crate.gd b/client/map/tiles/crate.gd
index 24dada3c..bb96b919 100644
--- a/client/map/tiles/crate.gd
+++ b/client/map/tiles/crate.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
class_name Crate
-extends Counter
+extends CounterLike
static func interact_target(): # -> Vector3?
return Vector3(0, 0.25, 0)
diff --git a/client/map/tiles/cutting_board.gd b/client/map/tiles/cutting_board.gd
index 65487417..d9f95911 100644
--- a/client/map/tiles/cutting_board.gd
+++ b/client/map/tiles/cutting_board.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/deep_fryer.gd b/client/map/tiles/deep_fryer.gd
index 5318cd62..d730207f 100644
--- a/client/map/tiles/deep_fryer.gd
+++ b/client/map/tiles/deep_fryer.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/door.gd b/client/map/tiles/door.gd
index 0473c337..37dd012f 100644
--- a/client/map/tiles/door.gd
+++ b/client/map/tiles/door.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/exterior_tree.gd b/client/map/tiles/exterior_tree.gd
index faaede1c..82c457e2 100644
--- a/client/map/tiles/exterior_tree.gd
+++ b/client/map/tiles/exterior_tree.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/fence.gd b/client/map/tiles/fence.gd
index 328e115c..5d2dfdd8 100644
--- a/client/map/tiles/fence.gd
+++ b/client/map/tiles/fence.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/floor.gd b/client/map/tiles/floor.gd
index e0959708..6baae392 100644
--- a/client/map/tiles/floor.gd
+++ b/client/map/tiles/floor.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/floor.gdshader b/client/map/tiles/floor.gdshader
index a1567189..dd6f277f 100644
--- a/client/map/tiles/floor.gdshader
+++ b/client/map/tiles/floor.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/client/map/tiles/floor_like.gd b/client/map/tiles/floor_like.gd
index 228a95e9..fc9be052 100644
--- a/client/map/tiles/floor_like.gd
+++ b/client/map/tiles/floor_like.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/floor_mesher.gd b/client/map/tiles/floor_mesher.gd
index cb89cc0b..addd23d2 100644
--- a/client/map/tiles/floor_mesher.gd
+++ b/client/map/tiles/floor_mesher.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/freezer.gd b/client/map/tiles/freezer.gd
index 47ab89a9..72aa4660 100644
--- a/client/map/tiles/freezer.gd
+++ b/client/map/tiles/freezer.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
class_name Freezer
-extends Counter
+extends CounterLike
var freezer: FreezerModel = load("res://map/tiles/freezer.tscn").instantiate()
diff --git a/client/map/tiles/freezer_model.gd b/client/map/tiles/freezer_model.gd
index 4defda9c..b05c6959 100644
--- a/client/map/tiles/freezer_model.gd
+++ b/client/map/tiles/freezer_model.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/generic_tile.gd b/client/map/tiles/generic_tile.gd
index 33c01cdb..73ac3fac 100644
--- a/client/map/tiles/generic_tile.gd
+++ b/client/map/tiles/generic_tile.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/grass.gd b/client/map/tiles/grass.gd
index d9ebce7c..bc555b93 100644
--- a/client/map/tiles/grass.gd
+++ b/client/map/tiles/grass.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -16,6 +16,13 @@
class_name Grass
extends FloorLike
+func _init(ctx: TileFactory.TileCC):
+ super(ctx)
+ if not base_mesh:
+ var grass_mesher: GrassMesher = ctx.floor_meshers[fm_id()]
+ var use_no_grass: bool = ctx.above_tile_names.any(func (s): return s == "chair" or s == "table" or s == "conveyor")
+ grass_mesher.add_tile(ctx.position, use_no_grass)
+
static func fm_id() -> String:
return "grass"
diff --git a/client/map/tiles/grass_mesher.gd b/client/map/tiles/grass_mesher.gd
index 466c3c1e..f157c84c 100644
--- a/client/map/tiles/grass_mesher.gd
+++ b/client/map/tiles/grass_mesher.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -19,10 +19,21 @@ extends FloorMesher
const GRASS_MESH: Mesh = preload("res://map/tiles/grass_side.tres")
var multimesh_instance := MultiMeshInstance3D.new()
+var no_grass_tiles: Dictionary[String, Vector2i] = {}
+
func _init(mesh: MeshInstance3D) -> void:
super(mesh)
mesh_instance.add_child(multimesh_instance)
+func add_tile(pos: Vector2i, use_no_grass: bool = false):
+ super(pos)
+ if use_no_grass:
+ no_grass_tiles[str(pos)] = pos
+
+func remove_tile(pos: Vector2i):
+ super(pos)
+ no_grass_tiles.erase(str(pos))
+
func flush() -> void:
super()
var random = RandomNumberGenerator.new()
@@ -33,8 +44,10 @@ func flush() -> void:
multimesh.transform_format = MultiMesh.TRANSFORM_3D
multimesh.instance_count = tiles.size() * Settings.read("graphics.grass_amount")
var t := tiles.values()
+ var no_grass := no_grass_tiles.values()
for i in multimesh.instance_count:
- var p = t[i / Settings.read("graphics.grass_amount")]
+ var p: Vector2i = t[i / Settings.read("graphics.grass_amount")]
+ if p in no_grass: continue
var origin := Vector3(random.randf_range(-.5, .5), 0.25, random.randf_range(-.5, .5)) + Vector3(p.x + 0.5, 0.0, p.y + 0.5)
var basis_ := (Basis(Vector3(0, 1, 0), random.randf_range(0, PI)) * Basis(Vector3(1, 0, 0), PI/2)).scaled(Vector3(0.75, 0.5, 0.75))
multimesh.set_instance_transform(i, Transform3D(basis_, origin))
diff --git a/client/map/tiles/house_balcony.gd b/client/map/tiles/house_balcony.gd
index 98fc63ac..210faaa5 100644
--- a/client/map/tiles/house_balcony.gd
+++ b/client/map/tiles/house_balcony.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/house_door.gd b/client/map/tiles/house_door.gd
index bcd35254..96c14459 100644
--- a/client/map/tiles/house_door.gd
+++ b/client/map/tiles/house_door.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/house_oriel.gd b/client/map/tiles/house_oriel.gd
index a659267b..828663f7 100644
--- a/client/map/tiles/house_oriel.gd
+++ b/client/map/tiles/house_oriel.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/house_roof.gd b/client/map/tiles/house_roof.gd
index 9e3e7b5c..30dfb517 100644
--- a/client/map/tiles/house_roof.gd
+++ b/client/map/tiles/house_roof.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/house_roof_chimney.gd b/client/map/tiles/house_roof_chimney.gd
index d46686d5..4d178788 100644
--- a/client/map/tiles/house_roof_chimney.gd
+++ b/client/map/tiles/house_roof_chimney.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/house_side.gd b/client/map/tiles/house_side.gd
index bca0317a..3fbaae9d 100644
--- a/client/map/tiles/house_side.gd
+++ b/client/map/tiles/house_side.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/house_tile.gd b/client/map/tiles/house_tile.gd
index cd0ff6c3..aba81f80 100644
--- a/client/map/tiles/house_tile.gd
+++ b/client/map/tiles/house_tile.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/house_wall.gd b/client/map/tiles/house_wall.gd
index fea2515d..e01fda08 100644
--- a/client/map/tiles/house_wall.gd
+++ b/client/map/tiles/house_wall.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/item_portal.gd b/client/map/tiles/item_portal.gd
index 6d292bde..23e14caf 100644
--- a/client/map/tiles/item_portal.gd
+++ b/client/map/tiles/item_portal.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,9 +14,10 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
class_name ItemPortal
-extends CounterBase
+extends Tile
var model := preload("res://map/tiles/portal.tscn")
func _init(ctx: TileFactory.TileCC, type: float):
- super(ctx, model)
+ super(ctx)
+ base.add_child(model.instantiate())
model.configure(0.2, type)
diff --git a/client/map/tiles/lamp.gd b/client/map/tiles/lamp.gd
index d315f737..d99949ee 100644
--- a/client/map/tiles/lamp.gd
+++ b/client/map/tiles/lamp.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/light_tile.gd b/client/map/tiles/light_tile.gd
index 2476d8eb..2187ec6f 100644
--- a/client/map/tiles/light_tile.gd
+++ b/client/map/tiles/light_tile.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/oven.gd b/client/map/tiles/oven.gd
index ef47047f..14ff0758 100644
--- a/client/map/tiles/oven.gd
+++ b/client/map/tiles/oven.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
class_name Oven
-extends Counter
+extends CounterLike
var oven: OvenModel = load("res://map/tiles/oven.tscn").instantiate()
diff --git a/client/map/tiles/oven_model.gd b/client/map/tiles/oven_model.gd
index 9ad66bd6..644213ff 100644
--- a/client/map/tiles/oven_model.gd
+++ b/client/map/tiles/oven_model.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/path.gd b/client/map/tiles/path.gd
index 1127c0bf..cfb598ca 100644
--- a/client/map/tiles/path.gd
+++ b/client/map/tiles/path.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/player_portal.gd b/client/map/tiles/player_portal.gd
index c78bcf0a..a9bd7fa0 100644
--- a/client/map/tiles/player_portal.gd
+++ b/client/map/tiles/player_portal.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/portal.gd b/client/map/tiles/portal.gd
index dbb882c5..dc7d8700 100644
--- a/client/map/tiles/portal.gd
+++ b/client/map/tiles/portal.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -30,3 +30,8 @@ func _process(delta: float) -> void:
if abs(target_size - current_size) > 0.01:
current_size = G.interpolate(current_size, target_size, delta * 5.)
mat.set_shader_parameter("size", current_size)
+
+func configure(size: float, type: bool):
+ mat = $Mesh.get_active_material(0)
+ target_type = 1. if type else -1.
+ target_size = size
diff --git a/client/map/tiles/portal.gdshader b/client/map/tiles/portal.gdshader
index c5bc0be4..e3093b07 100644
--- a/client/map/tiles/portal.gdshader
+++ b/client/map/tiles/portal.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/client/map/tiles/rolling_board.gd b/client/map/tiles/rolling_board.gd
index 00c28ba1..93db2989 100644
--- a/client/map/tiles/rolling_board.gd
+++ b/client/map/tiles/rolling_board.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/screen/screen.gd b/client/map/tiles/screen/screen.gd
index 3e059fc1..41667830 100644
--- a/client/map/tiles/screen/screen.gd
+++ b/client/map/tiles/screen/screen.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/screen/screen_model.gd b/client/map/tiles/screen/screen_model.gd
index 2a495dae..9f63fc3f 100644
--- a/client/map/tiles/screen/screen_model.gd
+++ b/client/map/tiles/screen/screen_model.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/sink.gd b/client/map/tiles/sink.gd
index 055cc353..4f9fffdf 100644
--- a/client/map/tiles/sink.gd
+++ b/client/map/tiles/sink.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
class_name Sink
-extends Counter
+extends CounterLike
var particles: SinkParticles = preload("res://map/tiles/sink_particles.tscn").instantiate()
var running: AudioStreamPlayer3D = AudioStreamPlayer3D.new()
diff --git a/client/map/tiles/sink_particles.gd b/client/map/tiles/sink_particles.gd
index 415b1d64..6674c21d 100644
--- a/client/map/tiles/sink_particles.gd
+++ b/client/map/tiles/sink_particles.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/stove.gd b/client/map/tiles/stove.gd
index 3adb6c91..13ae2c7e 100644
--- a/client/map/tiles/stove.gd
+++ b/client/map/tiles/stove.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
class_name Stove
-extends Counter
+extends CounterLike
func _init(ctx: TileFactory.TileCC):
super(ctx)
diff --git a/client/map/tiles/street.gd b/client/map/tiles/street.gd
index 38fb61f4..1ed21111 100644
--- a/client/map/tiles/street.gd
+++ b/client/map/tiles/street.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/table.gd b/client/map/tiles/table.gd
index 72352dac..4fe10323 100644
--- a/client/map/tiles/table.gd
+++ b/client/map/tiles/table.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/tile.gd b/client/map/tiles/tile.gd
index cd41ecc9..df7aa38d 100644
--- a/client/map/tiles/tile.gd
+++ b/client/map/tiles/tile.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -94,7 +94,6 @@ func set_item(i: Item):
item.remove()
item = i
if i != null:
- printt("I am", self, "and item", i, "now has item target", item_base.global_position)
i.owned_by = item_base
i.position_target = item_base.global_position
diff --git a/client/map/tiles/trash.gd b/client/map/tiles/trash.gd
index 36918b52..68e7f7d1 100644
--- a/client/map/tiles/trash.gd
+++ b/client/map/tiles/trash.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
class_name Trash
-extends Counter
+extends CounterLike
func _init(ctx: TileFactory.TileCC):
super(ctx)
diff --git a/client/map/tiles/unknown_tile.gd b/client/map/tiles/unknown_tile.gd
index f3524834..aa6eee79 100644
--- a/client/map/tiles/unknown_tile.gd
+++ b/client/map/tiles/unknown_tile.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/wall.gd b/client/map/tiles/wall.gd
index 25289635..1c14de11 100644
--- a/client/map/tiles/wall.gd
+++ b/client/map/tiles/wall.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/wall_tile.gd b/client/map/tiles/wall_tile.gd
index 244b2294..01522609 100644
--- a/client/map/tiles/wall_tile.gd
+++ b/client/map/tiles/wall_tile.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/map/tiles/window.gd b/client/map/tiles/window.gd
index f8444fff..9f8670a7 100644
--- a/client/map/tiles/window.gd
+++ b/client/map/tiles/window.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/message_parser.gd b/client/message_parser.gd
index d9c08f88..c7ef2e1a 100644
--- a/client/message_parser.gd
+++ b/client/message_parser.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/multiplayer.gd b/client/multiplayer.gd
index 08c2493c..9bd735fa 100644
--- a/client/multiplayer.gd
+++ b/client/multiplayer.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -199,6 +199,21 @@ func send_ready():
"type": "ready"
})
+func send_start_game_vote(player_id: float, map: String, hand_count = 1, bots = []):
+ var config := {
+ "map": map
+ }
+ if hand_count != null: config["hand_count"] = hand_count
+ if bots != null: config["bots"] = bots
+ send_packet({
+ "type": "initiate_vote",
+ "player": player_id,
+ "subject": {
+ "action": "start_game",
+ "config": config
+ }
+ })
+
func send_keep_alive() -> void:
send_packet({
"type": "keepalive"
diff --git a/client/player/camera_recorder.gd b/client/player/camera_recorder.gd
index a599af7b..896666e9 100644
--- a/client/player/camera_recorder.gd
+++ b/client/player/camera_recorder.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/character/character.gd b/client/player/character/character.gd
index 14a66930..262ffa22 100644
--- a/client/player/character/character.gd
+++ b/client/player/character/character.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/character/headwear/cat_ears.gd b/client/player/character/headwear/cat_ears.gd
index aa19d554..41c777e7 100644
--- a/client/player/character/headwear/cat_ears.gd
+++ b/client/player/character/headwear/cat_ears.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/chat_bubble.gd b/client/player/chat_bubble.gd
index abeacc66..6052eb50 100644
--- a/client/player/chat_bubble.gd
+++ b/client/player/chat_bubble.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/controllable_player.gd b/client/player/controllable_player.gd
index 7c30e915..a48892a7 100644
--- a/client/player/controllable_player.gd
+++ b/client/player/controllable_player.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/follow_camera.gd b/client/player/follow_camera.gd
index 60b7cc6d..c4e4d657 100644
--- a/client/player/follow_camera.gd
+++ b/client/player/follow_camera.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -43,9 +43,6 @@ var ground: Vector3
var camera_distance: float = 10.
var camera_distance_target: float = camera_distance
-var disable_input_menu := false
-var disable_input_lobby := false
-
var _disable_input := false
func _ready():
@@ -131,14 +128,6 @@ func follow(delta):
if target.get_parent() is ControllablePlayer:
target.get_parent().input_rotation = -angle_target
-func set_ingame(state: bool, in_lobby: bool):
+func set_ingame(state: bool, _in_lobby: bool):
# Disable input in lobby
- if state and in_lobby:
- reset()
- disable_input_lobby = true
- else:
- disable_input_lobby = false
- update_disable_input()
-
-func update_disable_input():
- _disable_input = disable_input_lobby or disable_input_menu
+ if state: reset()
diff --git a/client/player/item_bubble.gd b/client/player/item_bubble.gd
index 92e6da87..39872d5d 100644
--- a/client/player/item_bubble.gd
+++ b/client/player/item_bubble.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/marker/interact_marker.gdshader b/client/player/marker/interact_marker.gdshader
index c3bc498b..66377cb8 100644
--- a/client/player/marker/interact_marker.gdshader
+++ b/client/player/marker/interact_marker.gdshader
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/client/player/marker/marker.gd b/client/player/marker/marker.gd
index 7f9616c2..70bffe16 100644
--- a/client/player/marker/marker.gd
+++ b/client/player/marker/marker.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/mirror.gd b/client/player/mirror.gd
index 6ef2b483..218a0715 100644
--- a/client/player/mirror.gd
+++ b/client/player/mirror.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/onscreen_controls/controls.gd b/client/player/onscreen_controls/controls.gd
index a1dc3e32..310a35b7 100644
--- a/client/player/onscreen_controls/controls.gd
+++ b/client/player/onscreen_controls/controls.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -36,9 +36,6 @@ func apply_touch(touch_controls: String):
func apply_hand_count(count: int):
interact_left.visible = count >= 2
-func in_lobby_updated(in_lobby: bool):
- $Buttons.visible = not in_lobby
-
func _on_boost_pressed():
Input.action_press("boost")
boost.modulate = modulate_color
diff --git a/client/player/onscreen_controls/virtual_joystick.gd b/client/player/onscreen_controls/virtual_joystick.gd
index cb16a435..44a13b05 100644
--- a/client/player/onscreen_controls/virtual_joystick.gd
+++ b/client/player/onscreen_controls/virtual_joystick.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/player/particles/angry/angry.tscn b/client/player/particles/angry/angry.tscn
deleted file mode 100644
index 336798f8..00000000
--- a/client/player/particles/angry/angry.tscn
+++ /dev/null
@@ -1,41 +0,0 @@
-[gd_scene format=3 uid="uid://cvty1rwt52anq"]
-
-[ext_resource type="Texture2D" uid="uid://unjbxplj845n" path="res://player/particles/angry/angry.webp" id="1_5op6v"]
-
-[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ysmnk"]
-transparency = 1
-no_depth_test = true
-shading_mode = 0
-vertex_color_use_as_albedo = true
-albedo_texture = ExtResource("1_5op6v")
-billboard_mode = 3
-billboard_keep_scale = true
-particles_anim_h_frames = 1
-particles_anim_v_frames = 1
-particles_anim_loop = false
-
-[sub_resource type="QuadMesh" id="QuadMesh_5nim7"]
-material = SubResource("StandardMaterial3D_ysmnk")
-
-[sub_resource type="Curve" id="Curve_0rju1"]
-_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.51927, 1), 0.0, 0.0, 0, 0]
-point_count = 2
-
-[sub_resource type="Gradient" id="Gradient_lmymu"]
-offsets = PackedFloat32Array(0, 0.711828, 1)
-colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
-
-[node name="Angry" type="CPUParticles3D" unique_id=691382738]
-emitting = false
-amount = 5
-lifetime = 2.0
-one_shot = true
-explosiveness = 1.0
-mesh = SubResource("QuadMesh_5nim7")
-direction = Vector3(0, 1, 0)
-spread = 30.0
-gravity = Vector3(0, 0, 0)
-initial_velocity_min = 1.0
-initial_velocity_max = 1.5
-scale_amount_curve = SubResource("Curve_0rju1")
-color_ramp = SubResource("Gradient_lmymu")
diff --git a/client/player/particles/effect.tscn b/client/player/particles/effect.tscn
deleted file mode 100644
index 6d3ae511..00000000
--- a/client/player/particles/effect.tscn
+++ /dev/null
@@ -1,36 +0,0 @@
-[gd_scene format=3 uid="uid://dn2ne30t81ame"]
-
-[ext_resource type="Script" uid="uid://ecsoi03822i5" path="res://player/particles/effect.gd" id="1_aqsk6"]
-[ext_resource type="PackedScene" uid="uid://yaed1vnhd0aa" path="res://player/particles/satisfied/stars.tscn" id="2_shb5l"]
-[ext_resource type="PackedScene" uid="uid://cvty1rwt52anq" path="res://player/particles/angry/angry.tscn" id="3_bnidm"]
-[ext_resource type="AudioStream" uid="uid://camy77x26mmpv" path="res://gui/resources/sounds/success.ogg" id="3_favyn"]
-[ext_resource type="AudioStream" uid="uid://cv4isy6po6pqd" path="res://gui/resources/sounds/failure.ogg" id="4_j38qf"]
-[ext_resource type="Script" uid="uid://n4jwod1jfuiv" path="res://audio/play_random.gd" id="5_t2upj"]
-[ext_resource type="AudioStream" uid="uid://c3gatgrsb0npf" path="res://player/sounds/angry1.ogg" id="6_ou7uy"]
-[ext_resource type="AudioStream" uid="uid://cty282m6ckt62" path="res://player/sounds/angry2.ogg" id="7_r21iy"]
-
-[node name="Effect" type="Node3D" unique_id=274733637]
-script = ExtResource("1_aqsk6")
-
-[node name="Stars" parent="." unique_id=2112825244 instance=ExtResource("2_shb5l")]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
-
-[node name="Angry" parent="." unique_id=484123621 instance=ExtResource("3_bnidm")]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
-
-[node name="Success" type="AudioStreamPlayer" parent="." unique_id=1434258799]
-stream = ExtResource("3_favyn")
-
-[node name="Failure" type="AudioStreamPlayer" parent="." unique_id=1540740404]
-stream = ExtResource("4_j38qf")
-volume_db = -8.0
-
-[node name="AngryGrunt" type="Node3D" parent="." unique_id=1341875576]
-script = ExtResource("5_t2upj")
-volume_db = -8.0
-
-[node name="Angry1" type="AudioStreamPlayer3D" parent="AngryGrunt" unique_id=2051870150]
-stream = ExtResource("6_ou7uy")
-
-[node name="Angry2" type="AudioStreamPlayer3D" parent="AngryGrunt" unique_id=801605331]
-stream = ExtResource("7_r21iy")
diff --git a/client/player/player.gd b/client/player/player.gd
index a72104b5..90671924 100644
--- a/client/player/player.gd
+++ b/client/player/player.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -18,7 +18,6 @@ extends Node3D
const CHAT_BUBBLE: PackedScene = preload("res://player/chat_bubble.tscn")
const ITEM_BUBBLE: PackedScene = preload("res://player/item_bubble.tscn")
-const EFFECT: PackedScene = preload("res://player/particles/effect.tscn")
const PLAYER_SIZE: float = 0.4
const PLAYER_ACCELERATION = 10
@@ -43,11 +42,9 @@ var movement_base: Node3D = Node3D.new()
var character: Character = preload("res://player/character/character.tscn").instantiate()
var chat_bubble: ChatBubble = null
var item_bubble: ItemBubble = null
-var effect: Effect = null
var clear_chat_timer: Timer = Timer.new()
var clear_item_timer: Timer = Timer.new()
-var clear_effect_timer: Timer = Timer.new()
var is_despawning: bool = false
@@ -106,10 +103,8 @@ func _init(_id: int, name_: String, pos: Vector2, character_style_: Dictionary,
clear_chat_timer.one_shot = true
clear_item_timer.one_shot = true
- clear_effect_timer.one_shot = true
add_child(clear_chat_timer)
add_child(clear_item_timer)
- add_child(clear_effect_timer)
is_customer = player_class == "customer"
is_chef = player_class == "chef"
@@ -121,7 +116,6 @@ func _ready():
clear_chat_timer.timeout.connect(clear_text_message)
clear_item_timer.timeout.connect(clear_item_message)
- clear_effect_timer.timeout.connect(clear_effect)
Settings.hook_changed_init("gameplay.usernames", self, update_username_tag)
@@ -280,15 +274,3 @@ func text_message(message: Game.TextMessage):
func clear_text_message():
if chat_bubble != null:
chat_bubble.queue_free()
-
-func effect_message(effect_name: String):
- if effect != null:
- effect.queue_free()
- effect = EFFECT.instantiate()
- movement_base.add_child(effect)
- effect.set_effect(effect_name)
- clear_effect_timer.start(5)
-
-func clear_effect():
- if effect != null:
- effect.queue_free()
diff --git a/client/project.godot b/client/project.godot
index 0fecf73d..6dd69367 100644
--- a/client/project.godot
+++ b/client/project.godot
@@ -49,6 +49,10 @@ display_server/driver.linuxbsd="wayland"
movie_writer/movie_file="/tmp/output.avi"
movie_writer/mjpeg_quality=0.9
+[gui]
+
+theme/custom="uid://b0qmvo504e457"
+
[input]
ui_accept={
@@ -57,6 +61,7 @@ ui_accept={
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":74,"key_label":0,"unicode":106,"location":0,"echo":false,"script":null)
]
}
ui_cancel={
@@ -71,6 +76,7 @@ ui_left={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
ui_right={
@@ -78,6 +84,7 @@ ui_right={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
ui_up={
@@ -85,6 +92,7 @@ ui_up={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
ui_down={
@@ -92,6 +100,7 @@ ui_down={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
]
}
ui_menu={
diff --git a/client/service/discover.gd b/client/service/discover.gd
index 8ebd52da..6e8fd462 100644
--- a/client/service/discover.gd
+++ b/client/service/discover.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/service/editor.gd b/client/service/editor.gd
index 2197a5e1..a503ada2 100644
--- a/client/service/editor.gd
+++ b/client/service/editor.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/service/server.gd b/client/service/server.gd
index 9a314aca..bf1b7059 100644
--- a/client/service/server.gd
+++ b/client/service/server.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/service/service.gd b/client/service/service.gd
index 80c37f79..e2d1ed49 100644
--- a/client/service/service.gd
+++ b/client/service/service.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/system/cli.gd b/client/system/cli.gd
index befd9f10..f4d0674b 100644
--- a/client/system/cli.gd
+++ b/client/system/cli.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -33,6 +33,8 @@ static var OPTIONS := [
Option.new(null, "render-tiles", Mode.OPTION, "Render tiles from text file to images"),
Option.new(null, "render-resolution", Mode.OPTION, "Resolution for rendering items or tiles"),
Option.new(null, "render-output", Mode.OPTION, "Output directory for rendering items or tiles"),
+ Option.new(null, "render-format", Mode.OPTION, "Render output format (possible values: png, gltf)"),
+ Option.new(null, "render-include-tool-base", Mode.FLAG, "Add counters to cutting board and such"),
Option.new(null, "record-camera", Mode.OPTION, "Record camera movement to file"),
Option.new(null, "replay-camera", Mode.OPTION, "Replay camera movement"),
Option.new(null, "timescale", Mode.OPTION, "Time scaling factor for replay playback and camera recorder"),
diff --git a/client/system/disable_wrong_joypads.gd b/client/system/disable_wrong_joypads.gd
index 029768b3..aec80aa0 100644
--- a/client/system/disable_wrong_joypads.gd
+++ b/client/system/disable_wrong_joypads.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/system/gltf_apply_visibility.gd b/client/system/gltf_apply_visibility.gd
index 5635bf6e..8a2bed01 100644
--- a/client/system/gltf_apply_visibility.gd
+++ b/client/system/gltf_apply_visibility.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -28,8 +28,6 @@ func _export_post(state: GLTFState) -> Error:
return Error.OK
func hide_node(nodes: Array, node: Dictionary):
- if node.has("mesh"): print("clear mesh")
node.erase("mesh")
for child_index in node.get("children", []):
- print("clear child child_index")
hide_node(nodes, nodes[child_index])
diff --git a/client/system/profile.gd b/client/system/profile.gd
index dd4da430..5cc6acce 100644
--- a/client/system/profile.gd
+++ b/client/system/profile.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/system/render_tool.gd b/client/system/render_tool.gd
index f8695669..47875c9f 100644
--- a/client/system/render_tool.gd
+++ b/client/system/render_tool.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -15,17 +15,15 @@
#
extends Menu
-@onready var renderer = $Renderer
+@onready var renderer: Renderer = $Renderer
func _ready():
super()
var input_file: String
if Cli.opts.has("render-items"):
- renderer.mode = renderer.Mode.ITEMS
input_file = Cli.opts["render-items"]
elif Cli.opts.has("render-tiles"):
- renderer.mode = renderer.Mode.TILES
input_file = Cli.opts["render-tiles"]
else:
push_error("cannot open render_tool menu without corresponding cli options")
@@ -37,9 +35,23 @@ func _ready():
$Renderer/SubViewport.size = resolution
for object_name in object_name_list:
- renderer.setup_object(object_name)
- await RenderingServer.frame_post_draw
- var path = Cli.opts.get("render-output", ".").path_join(object_name + ".png")
- $Renderer/SubViewport.get_texture().get_image().save_png(path)
-
+ print(object_name)
+ if Cli.opts.has("render-items"):
+ renderer.setup_item(object_name)
+ else:
+ var parts = [object_name]
+ if Cli.opts.has("render-include-tool-base") and object_name in BookMenu.TOOLS_WITH_COUNTER: parts.insert(0, "counter")
+ renderer.setup_tile(parts)
+ match Cli.opts.get("render-format", "png"):
+ "png":
+ await RenderingServer.frame_post_draw
+ var path = Cli.opts.get("render-output", ".").path_join(object_name + ".png")
+ $Renderer/SubViewport.get_texture().get_image().save_png(path)
+ "gltf":
+ await get_tree().process_frame
+ var path = Cli.opts.get("render-output", ".").path_join(object_name + ".glb")
+ var doc := GLTFDocument.new()
+ var state := GLTFState.new()
+ doc.append_from_scene($Renderer/SubViewport/Node3D, state)
+ doc.write_to_filesystem(state, path)
exit()
diff --git a/client/system/server_list.gd b/client/system/server_list.gd
index a7e83729..985afab5 100644
--- a/client/system/server_list.gd
+++ b/client/system/server_list.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/system/settings.gd b/client/system/settings.gd
index ba1d2615..c8f4ef79 100644
--- a/client/system/settings.gd
+++ b/client/system/settings.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/client/system/translation_manager.gd b/client/system/translation_manager.gd
index c2e5421a..fab5af80 100644
--- a/client/system/translation_manager.gd
+++ b/client/system/translation_manager.gd
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/.gitignore b/data/.gitignore
deleted file mode 100644
index 08711f05..00000000
--- a/data/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/book.json
diff --git a/data/index.yaml b/data/index.yaml
index 947f6b5f..9c8b23bf 100644
--- a/data/index.yaml
+++ b/data/index.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -22,29 +22,29 @@
# 5 Unplayable
maps:
+ - 5star
+ - anticurry
+ - bbq
+ - duplex
+ - junior
+ - ring
+ - salad
+ - senior
# "campaign/lobby": { name: "Campaign Lobby", players: 1, difficulty: 0 }
- 5star: { name: "5 Star", players: 4, difficulty: 3 }
- anticurry: { name: "Anti Curry!", players: 3, difficulty: 4 }
- auto_sushi: { name: "AutoSushi", players: 2, difficulty: 2 }
- bar: { name: "Bar", players: 3, difficulty: 2 }
- bbq: { name: "BBQ", players: 2, difficulty: 1 }
- burgers_inc: { name: "Burgers, Inc.", players: 2, difficulty: 2 }
- bus: { name: "Bus", players: 5, difficulty: 3 }
- ctf: { name: "Capture the Curry!", players: 4, difficulty: 3 }
- duplex: { name: "Duplex", players: 2, difficulty: 3 }
- junior: { name: "Junior", players: 3, difficulty: 1 }
- line: { name: "Line", players: 2, difficulty: 1 }
- paris: { name: "Paris", players: 2, difficulty: 1 }
- ring: { name: "Ring", players: 3, difficulty: 2 }
- salad: { name: "Salad Store", players: 2, difficulty: 1 }
- scattered: { name: "Scattered", players: 4, difficulty: 3 }
- senior: { name: "Senior", players: 3, difficulty: 1 }
- sophomore: { name: "Sophomore", players: 1, difficulty: 1 }
- station: { name: "Station", players: 3, difficulty: 3 }
- streetfood: { name: "Street Food", players: 2, difficulty: 1 }
- sushibar: { name: "Sushi Bar", players: 2, difficulty: 2 }
- teeny: { name: "Teeny", players: 3, difficulty: 2 }
- zigzag: { name: "Zig-zag", players: 3, difficulty: 3 }
+ # auto_sushi: { name: "AutoSushi", players: 2, difficulty: 2 }
+ # bar: { name: "Bar", players: 3, difficulty: 2 }
+ # burgers_inc: { name: "Burgers, Inc.", players: 2, difficulty: 2 }
+ # bus: { name: "Bus", players: 5, difficulty: 3 }
+ # ctf: { name: "Capture the Curry!", players: 4, difficulty: 3 }
+ # line: { name: "Line", players: 2, difficulty: 1 }
+ # paris: { name: "Paris", players: 2, difficulty: 1 }
+ # scattered: { name: "Scattered", players: 4, difficulty: 3 }
+ # sophomore: { name: "Sophomore", players: 1, difficulty: 1 }
+ # station: { name: "Station", players: 3, difficulty: 3 }
+ # streetfood: { name: "Street Food", players: 2, difficulty: 1 }
+ # sushibar: { name: "Sushi Bar", players: 2, difficulty: 2 }
+ # teeny: { name: "Teeny", players: 3, difficulty: 2 }
+ # zigzag: { name: "Zig-zag", players: 3, difficulty: 3 }
recipes:
- none
diff --git a/data/maps/5star.yaml b/data/maps/5star.yaml
index 515616d2..3d3445d6 100644
--- a/data/maps/5star.yaml
+++ b/data/maps/5star.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,6 +13,9 @@
# 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/>.
#
+name: "5 Star"
+players: 4
+difficulty: 3
score_baseline: 200
hand_count: 2
use_palettes: [default]
diff --git a/data/maps/anticurry.yaml b/data/maps/anticurry.yaml
index f6fe96dc..d64eb36d 100644
--- a/data/maps/anticurry.yaml
+++ b/data/maps/anticurry.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,6 +13,9 @@
# 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/>.
#
+name: "Anti Curry!"
+players: 3
+difficulty: 4
score_baseline: 200
hand_count: 2
recipes: anticurry
diff --git a/data/maps/auto_sushi.yaml b/data/maps/auto_sushi.yaml
index 90a92066..73245959 100644
--- a/data/maps/auto_sushi.yaml
+++ b/data/maps/auto_sushi.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/bar.yaml b/data/maps/bar.yaml
index 378f4920..0e164b93 100644
--- a/data/maps/bar.yaml
+++ b/data/maps/bar.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/bbq.yaml b/data/maps/bbq.yaml
index 7291f874..cf60df07 100644
--- a/data/maps/bbq.yaml
+++ b/data/maps/bbq.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,46 +13,31 @@
# 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/>.
#
+name: "BBQ"
+players: 2
+difficulty: 1
score_baseline: 200
+use_palettes: [default]
map:
- "*'''*'''''''''*''*"
- "'*''''''''''''''*'"
- "*''''''''''''''''*"
- "''██████████████''"
- - "''ppSSfCCfooppsb''"
+ - "''ppbbfeefooppsb''"
- "''''''''''''''''''"
- "'''cccc'__'cccc'''"
- "''Xtttt'__'ttttX''"
- "'''cccc'__'cccc'''"
- "''''''''__''''''''"
- - "'''LLRR'__'TTFW'''"
+ - "'''IIAA'__'FFGJ'''"
- "*'''''''~_''''''''"
- "''''''''__''''''''"
- "'**'''''__'''*'''*"
tiles:
- "#": counter
- "f": counter -i=foodprocessor
- "p": counter -i=plate
- "t": table
- "s": sink
- "o": oven -x
- "S": stove -i=pan
- "C": cutting-board
- "R": crate:steak -x
- "T": crate:tomato -x
- "F": crate:flour -x
- "L": crate:lettuce -x
- "W": crate:cheese -x
- "X": trash -x
- "b": book --book
-
- "c": chair -w
- "'": grass -w
- "*": tree -c
- "~": path -w --customer-spawn --chef-spawn
- "_": path -w
- "â–ˆ": wall:red -c
+ "~": path --customer-spawn --chef-spawn
+ "_": path
+ "â–ˆ": wall:red
entities:
- !customers
diff --git a/data/maps/burgers_inc.yaml b/data/maps/burgers_inc.yaml
index dcb8b6eb..33d938bf 100644
--- a/data/maps/burgers_inc.yaml
+++ b/data/maps/burgers_inc.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/bus.yaml b/data/maps/bus.yaml
index fa1cf605..aa7f80e3 100644
--- a/data/maps/bus.yaml
+++ b/data/maps/bus.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/campaign/01.yaml b/data/maps/campaign/01.yaml
index 780c6d57..0edc392a 100644
--- a/data/maps/campaign/01.yaml
+++ b/data/maps/campaign/01.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
score_baseline: 15
-default_timer: 60
+timer: 60
map:
- "*''''*'''*'''''*'''"
- "'''*''''*'*'*'''*'*"
diff --git a/data/maps/campaign/02.yaml b/data/maps/campaign/02.yaml
index 9df7b3f7..c262e474 100644
--- a/data/maps/campaign/02.yaml
+++ b/data/maps/campaign/02.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
score_baseline: 30
-default_timer: 120
+timer: 120
map:
- "*''''*'''*'''''*'''"
- "'''*''''*'*'*'''*'*"
diff --git a/data/maps/campaign/lobby.yaml b/data/maps/campaign/lobby.yaml
index 27350267..9276377f 100644
--- a/data/maps/campaign/lobby.yaml
+++ b/data/maps/campaign/lobby.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/ctf.yaml b/data/maps/ctf.yaml
index 24b22990..65783507 100644
--- a/data/maps/ctf.yaml
+++ b/data/maps/ctf.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
score_baseline: 100000
-default_timer: 120
+timer: 120
recipes: none
map:
- "BBBBBBB......^o^..^."
diff --git a/data/maps/debug.yaml b/data/maps/debug.yaml
index a78b3995..f21147ad 100644
--- a/data/maps/debug.yaml
+++ b/data/maps/debug.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
score_baseline: 100000
-default_timer: 1000000
+timer: 1000000
map:
- "........................"
- "........................"
diff --git a/data/maps/debug2.yaml b/data/maps/debug2.yaml
index 878c1d41..52883843 100644
--- a/data/maps/debug2.yaml
+++ b/data/maps/debug2.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/duplex.yaml b/data/maps/duplex.yaml
index e0e7a2db..5e6af348 100644
--- a/data/maps/duplex.yaml
+++ b/data/maps/duplex.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,6 +13,9 @@
# 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/>.
#
+name: "Duplex"
+players: 2
+difficulty: 3
score_baseline: 175
use_palettes: [default]
map:
@@ -24,9 +27,9 @@ map:
- "''c-c'â–ˆp...â–ˆ====â–’...pâ–’'c-c''"
- "''t-t'â–ˆp...â–ˆ''''â–’...pâ–’'t-t''"
- "''c-cX█p..#‹<<<<«<..p▒Xc-c''"
- - "'''---|...>›>>>>»#...@---'''"
+ - "'''---|...→›>>>>»â†...@---'''"
- "'''---|...#‹<<<<«<...@---'''"
- - "''c-c'█a..>›>>>>»#..o▒'c-c''"
+ - "''c-c'â–ˆa..→›>>>>»â†..oâ–’'c-c''"
- "''t-t'â–ˆa...â–ˆ''''â–’...oâ–’'t-t''"
- "''c-c'â–ˆa...â–ˆ====â–’...oâ–’'c-c''"
- "'''-''â–ˆ#PBGâ–ˆ''''â–’EFG#â–’''-'''"
@@ -39,7 +42,9 @@ map:
tiles:
"P": floor counter -i=pot
">": grass conveyor --conveyor=1,0
+ "→": floor conveyor --conveyor=1,0
"<": grass conveyor --conveyor=-1,0
+ "â†": floor conveyor --conveyor=-1,0
"‹": floor counter-window-conveyor:blue --conveyor=-1,0
"›": floor counter-window-conveyor:blue --conveyor=1,0
"«": floor counter-window-conveyor:red --conveyor=-1,0
diff --git a/data/maps/junior.yaml b/data/maps/junior.yaml
index 48f9ca58..18d77e9d 100644
--- a/data/maps/junior.yaml
+++ b/data/maps/junior.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,6 +13,9 @@
# 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/>.
#
+name: "Junior"
+players: 3
+difficulty: 1
score_baseline: 200
use_palettes: [default]
map:
diff --git a/data/maps/line.yaml b/data/maps/line.yaml
index 71ef6866..6df57b13 100644
--- a/data/maps/line.yaml
+++ b/data/maps/line.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/lobby.yaml b/data/maps/lobby.yaml
index c80c81df..c3b59579 100644
--- a/data/maps/lobby.yaml
+++ b/data/maps/lobby.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,6 +13,7 @@
# 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/>.
#
+name: Lobby
score_baseline: 200
map:
- "'*''''''''''''''''''''*''"
@@ -83,7 +84,7 @@ tiles:
"⌷": floor counter
"â–ˆ": wall
- "<": conveyor "--conveyor=-1,0"
- ">": conveyor "--conveyor=1,0"
+ "<": floor conveyor "--conveyor=-1,0"
+ ">": floor conveyor "--conveyor=1,0"
"‹": counter-window-conveyor --demand-sink
"›": counter-window-conveyor --demand-sink
diff --git a/data/maps/paris.yaml b/data/maps/paris.yaml
index 2005382d..609c5af5 100644
--- a/data/maps/paris.yaml
+++ b/data/maps/paris.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/ring.yaml b/data/maps/ring.yaml
index d7ed5b61..aebaf622 100644
--- a/data/maps/ring.yaml
+++ b/data/maps/ring.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,23 +13,27 @@
# 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/>.
#
+name: "Ring"
+players: 3
+difficulty: 2
score_baseline: 350
hand_count: 2
+use_palettes: [default]
map:
- " *''*'''''*'''*''' "
- " '''''''''''''*'''''' "
- "'''█████▒██▒█████''*' "
- - "'''â–ˆ##ppp##aaa##â–ˆ'''' "
+ - "'''â–ˆ##aaa##bbb##â–ˆ'''' "
- "'''â–ˆ#..........oâ–ˆ''*' "
- " ''â–ˆe..tc..ct..oâ–ˆ'''' "
- " ''â–ˆe..tc..ct..oâ–ˆ'''' "
- " ''â–ˆr....,.....#â–ˆ'''' "
- " ''â–ˆr..........fâ–ˆ'''' "
- " ''â–ˆ#..tc..ct..fâ–ˆ'''' "
- - " ''â–ˆl..tc..ct..fâ–ˆ'''' "
- - "'''â–ˆl..........#â–ˆ'''' "
- - "'''â–ˆXABCD..EFGHXâ–ˆ'''' "
- - "'''██████dd██████'''' "
+ - " ''â–ˆs..tc..ct..fâ–ˆ'''' "
+ - "'''â–ˆs..........#â–ˆ'''' "
+ - "'''â–ˆXABJI..EFGHXâ–ˆ'''' "
+ - "'''██████||██████'''' "
- "'''''''''--'''''''''''"
- "'''''''''--'''''''''''"
- "_____________________'"
@@ -38,37 +42,8 @@ map:
- " ''''''''''''''''''' "
tiles:
- "#": counter
- "f": counter -i=foodprocessor
- "l": sink
- "o": oven -x
- "p": stove -i=pot
- "a": stove -i=pan
- "e": cutting-board
- "r": rolling-board
- "X": trash -x
+ "t": floor table -i=plate
- "A": crate:steak -x
- "B": crate:coconut -x
- "C": crate:cheese -x
- "D": crate:lettuce -x
- "E": crate:rice -x
- "F": crate:tomato -x
- "G": crate:flour -x
- "H": crate:leek -x
-
- ".": floor -w
- ",": floor -w --chef-spawn
- "'": grass -w
- "t": table -i=plate
- "c": chair -w
- "*": tree -c
- "!": street -w --customer-spawn
- "-": path -w
- "_": street -w
- "d": door -w
- "â–ˆ": wall -c
- "â–’": wall-window -c
entities:
- !customers
diff --git a/data/maps/salad.yaml b/data/maps/salad.yaml
index a7deb171..c107ad2d 100644
--- a/data/maps/salad.yaml
+++ b/data/maps/salad.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,53 +13,31 @@
# 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/>.
#
+name: "Salad Store"
+players: 2
+difficulty: 1
score_baseline: 200
+use_palettes: [default]
map:
- "*'''''''*''''''*'*"
- "''''''*''''**'*'''"
- "'''''███▒▒███'''''"
- - "*''''█⌷CCCC⌷█'''*'"
- - "'''''â–ˆp....Lâ–ˆ''''*"
- - "*''''â–’p....Tâ–’'''''"
- - "'''''â–’p....Lâ–’'''''"
- - "*''''â–ˆs....Tâ–ˆ''''*"
+ - "*''''â–ˆ#eeee#â–ˆ'''*'"
+ - "'''''â–ˆp....Iâ–ˆ''''*"
+ - "*''''â–’p....Fâ–’'''''"
+ - "'''''â–’p....Iâ–’'''''"
+ - "*''''â–ˆs....Fâ–ˆ''''*"
- "'''''â–ˆb....pâ–ˆ'''''"
- - "'''''███dd███'''''"
+ - "'''''███||███'''''"
- "'''''â–ˆtc..ctâ–ˆ'''''"
- "*''''â–’c....câ–’'''''"
- "'*'''â–ˆ......â–ˆ'''''"
- - "'''''â–ˆc....câ–ˆ''*''"
- - "'''''â–’tc.~ctâ–’'''''"
+ - "'''''â–ˆc..,.câ–ˆ''*''"
+ - "'''''â–’tc..ctâ–’'''''"
- "*''''â–ˆc....câ–ˆ'''''"
- - "'''''█▒█dd█▒█''''*"
+ - "'''''█▒█||█▒█''''*"
- "'''''''X__''''''''"
- "*'*'''''!_'''**'''"
-tiles:
- "⌷": counter
- "p": counter -i=plate
- "t": table
- "s": sink
- "C": cutting-board
- "T": crate:tomato -x
- "L": crate:lettuce -x
- "X": trash -x
- "b": book --book
-
- "c": chair -w
- "~": floor -w --chef-spawn
- ".": floor -w
- "'": grass -w
- "*": tree -c
- "!": path -w --customer-spawn
- "_": path -w
- "d": door -w
- "â–’": wall-window -c
- "â–ˆ": wall -c
-
-items:
- "w": plate
- "p": plate
-
entities:
- !customers
diff --git a/data/maps/scattered.yaml b/data/maps/scattered.yaml
index 088dd868..bd1f0893 100644
--- a/data/maps/scattered.yaml
+++ b/data/maps/scattered.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/senior.yaml b/data/maps/senior.yaml
index d26732a0..7b11718d 100644
--- a/data/maps/senior.yaml
+++ b/data/maps/senior.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -13,36 +13,29 @@
# 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/>.
#
+name: "Senior"
+players: 3
+difficulty: 1
score_baseline: 300
+use_palettes: [default]
map:
- "*''''*'''*'''''*'''*'''*''''*'"
- "'''*''''*'*'**'''*''**''*'*'''"
- "''██▒██▒██▒███▒███▒████▒█▒█'*'"
- "''â–ˆctc.ctc.ctc.ctc.ctcâ–ˆsspâ–ˆ'**"
- "''â–ˆ.....c.............â–ˆ..pâ–ˆ'''"
- - "'*▒c...c...████www██|██.AB█'*'"
- - "*'â–ˆtc.ctc..â–ˆhgg...ee.d....â–ˆ'''"
- - "''â–’c...c...w.....,...d..CDâ–ˆ''*"
- - "*'â–ˆc.......w.#f#.#f##â–ˆ..EFâ–ˆ'*'"
- - "'*â–’tc......w.........d....â–ˆ'''"
- - "''â–ˆc.....ctâ–ˆ#aaa##oooâ–ˆX#GHâ–ˆ'*'"
- - "*'████dd██████████▒██████▒█'*'"
+ - "'*▒c...c...████www██|██.EB█'*'"
+ - "*'â–ˆtc.ctc..â–ˆhgg...ee.|....â–ˆ'''"
+ - "''â–’c...c...w.....,...|..FGâ–ˆ''*"
+ - "*'â–ˆc.......w.#f#.#f##â–ˆ..HJâ–ˆ'*'"
+ - "'*â–’tc......w.........|....â–ˆ'''"
+ - "''â–ˆc.....ctâ–ˆ#aaa##oooâ–ˆX#IKâ–ˆ'*'"
+ - "*'████||██████████▒██████▒█'*'"
- "'''*''--''''''''''''''''''''*'"
- "*'''*'------------------------"
- - "'*'*''---------------------!--"
+ - "'*'*''---------------------?--"
- "*''*''''''''''''''''''''''''''"
-tiles:
- "A": [floor, crate:rice]
- "B": [floor, crate:coconut]
- "C": [floor, crate:tomato]
- "D": [floor, crate:flour]
- "E": [floor, crate:leek]
- "F": [floor, crate:cheese]
- "G": [floor, crate:lettuce]
- "H": [floor, crate:mushroom]
- "!": [path, --customer-spawn]
-
entities:
- !customers
scaling_factor: 0.3
diff --git a/data/maps/sophomore.yaml b/data/maps/sophomore.yaml
index 7a86abb3..fce330eb 100644
--- a/data/maps/sophomore.yaml
+++ b/data/maps/sophomore.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/star.yaml b/data/maps/star.yaml
index 6bdca807..18aaf505 100644
--- a/data/maps/star.yaml
+++ b/data/maps/star.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/station.yaml b/data/maps/station.yaml
index fcd257b2..e3376b3f 100644
--- a/data/maps/station.yaml
+++ b/data/maps/station.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/streetfood.yaml b/data/maps/streetfood.yaml
index 36ba3ee3..d71ce6b9 100644
--- a/data/maps/streetfood.yaml
+++ b/data/maps/streetfood.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/sushibar.yaml b/data/maps/sushibar.yaml
index b746e1c7..e5377a6a 100644
--- a/data/maps/sushibar.yaml
+++ b/data/maps/sushibar.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/tag.yaml b/data/maps/tag.yaml
index 1ad5b83b..6d32b854 100644
--- a/data/maps/tag.yaml
+++ b/data/maps/tag.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,7 +14,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
score_baseline: 100000
-default_timer: 120
+timer: 120
recipes: none
map:
- "BBBBBBB......^o^..^."
diff --git a/data/maps/teeny.yaml b/data/maps/teeny.yaml
index 4443bc3c..6e194891 100644
--- a/data/maps/teeny.yaml
+++ b/data/maps/teeny.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/maps/zigzag.yaml b/data/maps/zigzag.yaml
index b75aa562..7bb61c16 100644
--- a/data/maps/zigzag.yaml
+++ b/data/maps/zigzag.yaml
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
diff --git a/data/palettes.yaml b/data/palettes.yaml
index c86039df..c29b5ff6 100644
--- a/data/palettes.yaml
+++ b/data/palettes.yaml
@@ -30,7 +30,7 @@ default:
"L": floor crate:potato
"X": floor trash
- "=": fence
+ "=": grass fence
".": floor
",": floor --chef-spawn
"'": grass
diff --git a/data/recipes/anticurry.sed b/data/recipes/anticurry.sed
index 01e442de..b8d43f45 100755..100644
--- a/data/recipes/anticurry.sed
+++ b/data/recipes/anticurry.sed
@@ -1,4 +1,3 @@
-#!/usr/bin/env -S sed -f
s/inputs/TEMP/g
s/outputs/inputs/g
s/TEMP/outputs/g
diff --git a/data/recipes/default.js b/data/recipes/default.js
index 00d66864..c4359f4f 100644
--- a/data/recipes/default.js
+++ b/data/recipes/default.js
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/data/tiles.yaml b/data/tiles.yaml
index e9ef0528..965f8a92 100644
--- a/data/tiles.yaml
+++ b/data/tiles.yaml
@@ -2,11 +2,13 @@
# c | collider
# a | all items placable
# x | exclusive to items used in recipes
+# W | avoid walking over them (bots)
book: e
button-base: c
conveyor: ac
counter-window: ac
+wall-window: c
counter: ac
crate: cx
deep-fryer: x
@@ -14,9 +16,11 @@ freezer: cx
lamp: c
oven: cx
screen: c
-stove: c
-table: c
+stove: ac
+table: ac
trash: cx
tree: c
wall: c
-fence: c \ No newline at end of file
+fence: c
+chair: W
+grass: W
diff --git a/locale/ar.ini b/locale/ar.ini
index 6e9db337..f87cedfd 100644
--- a/locale/ar.ini
+++ b/locale/ar.ini
@@ -42,8 +42,8 @@ c.menu.ingame.quit=خروج
c.menu.ingame.reconnect=إعادة الاتصال
c.menu.ingame.resume=استمرار
c.menu.ingame.spectate=مشاهدين
-c.menu.lobby.mapname = اسم المرحلة
-c.menu.lobby.players = اللاعبين
+c.menu.map_selector.mapname = اسم المرحلة
+c.menu.map_selector.players = اللاعبين
c.menu.my_chef=طهاتي
c.menu.play.connect=اشبك
c.menu.play.quick_connect=الاتصال السريع
diff --git a/locale/bar.ini b/locale/bar.ini
index aaebdae7..6aaa86b5 100644
--- a/locale/bar.ini
+++ b/locale/bar.ini
@@ -60,12 +60,12 @@ c.menu.ingame.quit = Spui ausmochn
c.menu.ingame.reconnect = No amoi einsteing
c.menu.ingame.resume = Weida
c.menu.ingame.spectate = Zuaschaung
-c.menu.lobby.enable_bots = Bots einschoitn
+c.menu.map_selector.enable_bots = Bots einschoitn
c.menu.ingame.leave = Zuaschaung
-c.menu.lobby.mapname = Name von d' Koadn
-c.menu.lobby.players = Spieler
+c.menu.map_selector.mapname = Name von d' Koadn
+c.menu.map_selector.players = Spieler
c.menu.my_chef = Mei Koch
-c.menu.lobby.start = Spui stoatn
+c.menu.map_selector.start = Spui stoatn
c.menu.play.fetching_list = Server-Listn hoin…
c.menu.play.list_item = {0} ({1} Leit)
c.menu.play.no_servers = Server-Wiesn is leer.
diff --git a/locale/de.ini b/locale/de.ini
index 0e886f74..17ff26a8 100644
--- a/locale/de.ini
+++ b/locale/de.ini
@@ -53,10 +53,10 @@ c.menu.ingame.quit=Spiel beenden
c.menu.ingame.reconnect=Erneut verbinden
c.menu.ingame.resume=Fortsetzen
c.menu.ingame.spectate=Zuschauen
-c.menu.lobby.enable_bots = Bots aktivieren
-c.menu.lobby.mapname = Name der Karte
-c.menu.lobby.players = Spieler
-c.menu.lobby.start = Spiel starten
+c.menu.map_selector.enable_bots = Bots aktivieren
+c.menu.map_selector.mapname = Name der Karte
+c.menu.map_selector.players = Spieler
+c.menu.map_selector.start = Spiel starten
c.menu.my_chef=Mein Koch
c.menu.play.allow_query_registry = Um die öffentliche Serverliste anzuzeigen, muss ein Verzeichnissserver kontaktiert werden. Der Verzeichnissserver ist {0}.
c.menu.play.connect=Verbinden
diff --git a/locale/el.ini b/locale/el.ini
index 5578deae..a45a1bf2 100644
--- a/locale/el.ini
+++ b/locale/el.ini
@@ -59,8 +59,8 @@ c.hint.movement = ΧÏησιμοποίησε το {0} για να κινηθεί
c.hint.reset_camera = Πάτα {0} για να επαναφέÏεις την κάμεÏα στην αÏχική της κατάσταση
c.hint.rotate = ΧÏησιμοποίησε το {0} για να πεÏιστÏέψεις την κάμεÏα
c.hint.username_tags = ΜποÏείς να κÏÏψεις ή να εμφανίσεις τις ταμπέλες των ονομάτων του κάθε παίκτη στις Ïυθμίσεις
-c.menu.lobby.players = Παίκτες
-c.menu.lobby.start = ΈναÏξη παιχνιδιοÏ
+c.menu.map_selector.players = Παίκτες
+c.menu.map_selector.start = ΈναÏξη παιχνιδιοÏ
c.menu.my_chef = Ο Σεφ μου
c.menu.play.allow_query_registry =Για την εμφάνιση λίστας δημόσιων server, Ï€Ïέπει να γίνει τακτική επικοινωνία με τον server μητÏώου. Το server μητÏώου είναι το {0}.
c.menu.ingame.join = Μπες στο παιχνίδι
@@ -70,8 +70,8 @@ c.menu.ingame.main_menu = ΑÏχικό μενοÏ
c.menu.ingame.quit = Έξοδος παιχνιδιοÏ
c.menu.ingame.resume = Συνέχεια
c.menu.ingame.spectate = ΠαÏακολοÏθηση
-c.menu.lobby.enable_bots = ΕνεÏγοποίηση bot
-c.menu.lobby.mapname = Όνομα χάÏτη
+c.menu.map_selector.enable_bots = ΕνεÏγοποίηση bot
+c.menu.map_selector.mapname = Όνομα χάÏτη
c.menu.play.server_failed_tooltip = Το server κÏάσαÏε ή σταμάτησε με άλλο Ï„Ïόπο.%nΠÏοσπάθησε να το ξεκινήσεις μέσω της γÏαμμής εντολών.
c.menu.play.server_binary_not_found = Δεν βÏέθηκε το Ï€ÏόγÏαμμα server. Θα Ï€Ïέπει να κατεβάσετε το server ξεχωÏιστά.
c.menu.play.server_start = Εκκίνηση server
diff --git a/locale/en.ini b/locale/en.ini
index bed8b4b5..acbe1707 100644
--- a/locale/en.ini
+++ b/locale/en.ini
@@ -61,6 +61,7 @@ c.map.difficulty.3=Unplayable
c.map.difficulty.4=Very hard
c.map.difficulty=Difficulty
c.map.players_recommended={0} players recommended
+c.map.select=Select map
c.menu.about.credits=Credits
c.menu.about.legal=Legal
c.menu.about.report_issue=Report Issues
@@ -81,10 +82,11 @@ c.menu.ingame.quit=Quit game
c.menu.ingame.reconnect=Reconnect
c.menu.ingame.resume=Resume
c.menu.ingame.spectate=Spectate
-c.menu.lobby.enable_bots=Enable bots
-c.menu.lobby.mapname=Map name
-c.menu.lobby.players=Players
-c.menu.lobby.start=Start game
+c.menu.map_selector.enable_bots=Enable bots
+c.menu.map_selector.mapname=Map name
+c.menu.map_selector.players=Players
+c.menu.map_selector.start=Start game
+c.menu.map_selector.two_handed=Play with both hands
c.menu.play.allow_query_registry=To show a public server list a registry service is contacted regularly. The registry service is {0}.
c.menu.play.connect=Connect
c.menu.play.editor_failed=Editor (failed)
@@ -295,6 +297,7 @@ s.bot.waiter=Waiter
s.campaign.condition.stars=Reach at least {0} stars in {1}
s.campaign.list_helper=- {0}%n - {1}
s.campaign.unlock_condition=To unlock: %n%n{0}
+s.custom_game_warn=This game will not be recorded to the scoreboard.
s.disconnect_reason.channel_overflow=Your connection was too weak. Please reconnect.
s.disconnect_reason.invalid_packet=Your client sent an invalid packet: {0}
s.disconnect_reason.keepalive_timer=Your connection was interrupted. Please reconnect.
@@ -306,6 +309,7 @@ s.error.interacting_too_far=interacting too far from player
s.error.item_not_found=The item "{0}" does not exist.
s.error.map_load=Map failed to load: {0}
s.error.must_be_alone=You must be alone in this server to reload
+s.error.no_effect=No such effect
s.error.no_hand=Hand does not exist.
s.error.no_info=No information available.
s.error.no_player=Player does not exist.
@@ -346,6 +350,8 @@ s.tool.map_linter.walkable=Tile {0} has no collision.
s.tutorial.accept_order=Approach the customer take their order
s.tutorial.active_cutting_board=Cut the item to slices here
s.tutorial.active=Interact here for {0}s
+s.tutorial.button.book=Interact with this book to see all recipes.
+s.tutorial.button.map_selector=Interact here to start a game.
s.tutorial.clear_tile=Clear this tile
s.tutorial.error=Tutorial code cannot handle this recipe yet.
s.tutorial.finished=Tutorial finished!
diff --git a/locale/es.ini b/locale/es.ini
index 96895d97..2e686e88 100644
--- a/locale/es.ini
+++ b/locale/es.ini
@@ -51,10 +51,10 @@ c.menu.ingame.quit=Salir del juego
c.menu.ingame.reconnect=Reconectar
c.menu.ingame.resume=Continuar
c.menu.ingame.spectate=Ver como espectador
-c.menu.lobby.enable_bots = Activar bots
-c.menu.lobby.mapname = Nombre del mapa
-c.menu.lobby.players = Jugadores
-c.menu.lobby.start = Empezar partida
+c.menu.map_selector.enable_bots = Activar bots
+c.menu.map_selector.mapname = Nombre del mapa
+c.menu.map_selector.players = Jugadores
+c.menu.map_selector.start = Empezar partida
c.menu.my_chef=Mi cocinero
c.menu.play.connect=Conectar
c.menu.play.fetching_list = Obteniendo lista de servidores…
diff --git a/locale/eu.ini b/locale/eu.ini
index 0f196943..30f1ed3a 100644
--- a/locale/eu.ini
+++ b/locale/eu.ini
@@ -50,10 +50,10 @@ c.menu.ingame.quit=Jokotik irten
c.menu.ingame.reconnect=Berriz konektatu
c.menu.ingame.resume=Jarraitu
c.menu.ingame.spectate=Ikusle
-c.menu.lobby.enable_bots = Gaitu bot-ak
-c.menu.lobby.mapname = Maparen izena
-c.menu.lobby.players = Jokalariak
-c.menu.lobby.start = Hasi jokoa
+c.menu.map_selector.enable_bots = Gaitu bot-ak
+c.menu.map_selector.mapname = Maparen izena
+c.menu.map_selector.players = Jokalariak
+c.menu.map_selector.start = Hasi jokoa
c.menu.my_chef=Nire Chef-a
c.menu.play.connect=Konektatu
c.menu.play.fetching_list = Zerbitzari zerrenda lortzen…
diff --git a/locale/fi.ini b/locale/fi.ini
index c1d25cd3..bfd86207 100644
--- a/locale/fi.ini
+++ b/locale/fi.ini
@@ -24,10 +24,10 @@ c.menu.ingame.quit = Lopeta peli
c.menu.ingame.reconnect = Yhdistä uudelleen
c.menu.ingame.resume = Jatka
c.menu.ingame.spectate = Näytelmä
-c.menu.lobby.enable_bots = Ota botit käyttöön
-c.menu.lobby.mapname = Kartan nimi
-c.menu.lobby.players = Pelaajat
-c.menu.lobby.start = Aloita peli
+c.menu.map_selector.enable_bots = Ota botit käyttöön
+c.menu.map_selector.mapname = Kartan nimi
+c.menu.map_selector.players = Pelaajat
+c.menu.map_selector.start = Aloita peli
c.settings.audio = Audio
c.settings.gameplay.interpolate_camera_rotation = Tasainen kameran kierto
c.settings.gameplay.invert_camera = Käännä kameran liike päinvastaiseksi
diff --git a/locale/fr.ini b/locale/fr.ini
index c12a5f1b..db697ee3 100644
--- a/locale/fr.ini
+++ b/locale/fr.ini
@@ -174,10 +174,10 @@ unknown752=Faites défiler vers le bas
c.menu.play.no_servers = Pas de serveur disponible.
c.menu.play.allow_query_registry = Pour afficher une liste des serveurs publiques, le service registre {0} doit être appelé régulièrement.
c.menu.ingame.leave = Quitter la partie
-c.menu.lobby.enable_bots = Activer les robots
-c.menu.lobby.mapname = Nom de la carte
-c.menu.lobby.start = Lancer la partie
-c.menu.lobby.players = Personnes
+c.menu.map_selector.enable_bots = Activer les robots
+c.menu.map_selector.mapname = Nom de la carte
+c.menu.map_selector.start = Lancer la partie
+c.menu.map_selector.players = Personnes
c.hint.join_while_running = Cliquez sur {0} puis sur « Rejoindre » pour entrer dans une partie en cours
c.hint.rotate = Utilisez {0} pour modifier la vue caméra
c.hint.username_tags = Les pseudos peuvent être activés/désactivés dans les options
diff --git a/locale/it.ini b/locale/it.ini
index 44322251..527a9c3d 100644
--- a/locale/it.ini
+++ b/locale/it.ini
@@ -26,9 +26,9 @@ c.menu.deny = Rifiuta
c.menu.ingame.cancel = Annulla la partita
c.menu.ingame.main_menu = Menù principale
c.menu.ingame.reconnect = Riconnettiti
-c.menu.lobby.mapname = Nome della mappa
-c.menu.lobby.players = Giocatori
-c.menu.lobby.start = Avvia la partita
+c.menu.map_selector.mapname = Nome della mappa
+c.menu.map_selector.players = Giocatori
+c.menu.map_selector.start = Avvia la partita
c.menu.my_chef = Il Mio Cuoco
c.menu.play = Gioca
c.menu.play.connect = Connettiti
@@ -119,7 +119,7 @@ c.settings.audio.music_volume = Volume della musica
c.menu.play.server_failed_tooltip = Il server è terminato improvvisamente.%nProva ad avviare il server dalla riga di comando.
c.settings.graphics.debug_info = Mostra informazioni di debug (FPS, ecc.)
c.menu.ingame.spectate = Osserva
-c.menu.lobby.enable_bots = Attiva i bot
+c.menu.map_selector.enable_bots = Attiva i bot
c.hint.join_while_running = Premi {0} e clicca "Entra" per unirti alla partita mentre è in corso
c.map.difficulty.2 = Media
c.hint.zoom_camera = Usa {0} per ingrandire/rimpicciolire
diff --git a/locale/ja.ini b/locale/ja.ini
index 57352b43..324ff5bb 100644
--- a/locale/ja.ini
+++ b/locale/ja.ini
@@ -169,10 +169,10 @@ c.settings.gameplay.vibration = ãƒã‚¤ãƒ–レーションを有効ã«ã™ã‚‹
c.menu.ingame.leave = 観戦ã™ã‚‹
c.error.version_mismatch = サーãƒãƒ¼ã¨ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒä¸€è‡´ã—ã¾ã›ã‚“。サーãƒãƒ¼: {0}.{1}ã€ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆ: {2}.{3}。%nã‚²ãƒ¼ãƒ ã¯æœ€æ–°ã§ã™ã‹?
c.error.websocket.unavailable = 無効
-c.menu.lobby.enable_bots = ボットを有効ã«ã™ã‚‹
-c.menu.lobby.mapname = マップå
-c.menu.lobby.players = プレイヤー
-c.menu.lobby.start = ゲームを開始
+c.menu.map_selector.enable_bots = ボットを有効ã«ã™ã‚‹
+c.menu.map_selector.mapname = マップå
+c.menu.map_selector.players = プレイヤー
+c.menu.map_selector.start = ゲームを開始
c.settings.graphics.aa.fx = FXAA (計算コストã¯ä½Žã„ãŒã€ã‚¢ãƒ¼ãƒ†ã‚£ãƒ•ァクトãŒç”Ÿæˆã•れる)
c.settings.graphics.aa.ms2x = MSAA 2x
c.settings.graphics.aa.ms4x = MSAA 4x (正確ã ãŒè¨ˆç®—コストãŒé«˜ã„)
diff --git a/locale/nl.ini b/locale/nl.ini
index 910ab65e..5155ec73 100644
--- a/locale/nl.ini
+++ b/locale/nl.ini
@@ -48,10 +48,10 @@ c.menu.ingame.quit = Afsluiten
c.menu.ingame.reconnect = Opnieuw verbinden
c.menu.ingame.resume = Hervatten
c.menu.ingame.spectate = Toeschouwen
-c.menu.lobby.enable_bots = Bots inschakelen
-c.menu.lobby.mapname = Kaartnaam
-c.menu.lobby.players = Spelers
-c.menu.lobby.start = Spel starten
+c.menu.map_selector.enable_bots = Bots inschakelen
+c.menu.map_selector.mapname = Kaartnaam
+c.menu.map_selector.players = Spelers
+c.menu.map_selector.start = Spel starten
c.menu.my_chef = Mijn chef-kok
c.menu.play = Spelen
c.menu.play.connect = Verbinden
diff --git a/locale/pl.ini b/locale/pl.ini
index e6674923..f387f623 100644
--- a/locale/pl.ini
+++ b/locale/pl.ini
@@ -43,10 +43,10 @@ c.menu.ingame.quit=Wyjdź z gry
c.menu.ingame.reconnect=Połącz ponownie
c.menu.ingame.resume=Wznów
c.menu.ingame.spectate=OglÄ…daj
-c.menu.lobby.enable_bots = Włącz boty
-c.menu.lobby.mapname = Nazwa mapy
-c.menu.lobby.players = Gracze
-c.menu.lobby.start = Rozpocznij grÄ™
+c.menu.map_selector.enable_bots = Włącz boty
+c.menu.map_selector.mapname = Nazwa mapy
+c.menu.map_selector.players = Gracze
+c.menu.map_selector.start = Rozpocznij grÄ™
c.menu.my_chef=Mój kucharz
c.menu.play.connect=Połącz
c.menu.play.fetching_list = Pobieranie listy serwerów...
diff --git a/locale/ru.ini b/locale/ru.ini
index 757796f4..43fa7030 100644
--- a/locale/ru.ini
+++ b/locale/ru.ini
@@ -13,7 +13,7 @@ c.menu.ingame.leave = Ðаблюдать
c.menu.ingame.main_menu = Главное меню
c.menu.ingame.quit = Выйти из игры
c.menu.ingame.reconnect = ПереподключитьÑÑ
-c.menu.lobby.mapname = Ðазвание карты
+c.menu.map_selector.mapname = Ðазвание карты
c.settings.ui.language = Язык
c.settings.ui.language.system = По умолчанию
c.settings.ui.scale_factor = МаÑштаб интерфейÑа
@@ -24,7 +24,7 @@ c.setup.compensation = 7. [b]Оплата.[/b] Вам будет ежемеÑÑÑ
c.setup.contract_desc = ÐаÑтоÑщий договор ÑвлÑетÑÑ ÑŽÑ€Ð¸Ð´Ð¸Ñ‡ÐµÑки обÑзывающем Ñоглашением между вами (Ñотрудником) и компанией Musterfoods Ltd. (работодателем) о работе в должноÑти повара или официанта.
c.setup.contract_title = ДОГОВОР ОБ ОКÐЗÐÐИИ УСЛУГ
c.menu.ingame.resume = Возобновить
-c.menu.lobby.players = Игроки
+c.menu.map_selector.players = Игроки
c.settings.graphics = Графика
c.menu.play.connect = ПодключитьÑÑ
c.menu.play.fetching_list = Получение ÑпиÑка Ñерверов…
@@ -193,8 +193,8 @@ c.map.difficulty.4 = Очень Ñложный
c.map.difficulty = СложноÑть
c.map.players_recommended = РекомендуетÑÑ {0} игроков
c.menu.ingame.spectate = Ðаблюдать
-c.menu.lobby.enable_bots = Включить ботов
-c.menu.lobby.start = Ðачать игру
+c.menu.map_selector.enable_bots = Включить ботов
+c.menu.map_selector.start = Ðачать игру
c.menu.my_chef = Мой Шеф
c.menu.play.allow_query_registry = Чтобы отобразить ÑпиÑок публичных Ñерверов, выполнÑетÑÑ Ñ€ÐµÐ³ÑƒÐ»Ñрное обращение к Ñлужбе рееÑтра. Служба рееÑтра: {0}.
c.menu.play.server_binary_not_found = ИÑполнÑемый файл Ñервера не найден. ПожалуйÑта уÑтановите Ñервер отдельно.
diff --git a/locale/sv.ini b/locale/sv.ini
index 331e7d61..2682b03a 100644
--- a/locale/sv.ini
+++ b/locale/sv.ini
@@ -64,10 +64,10 @@ c.menu.ingame.quit = Avsluta spelet
c.menu.ingame.reconnect = Ã…teranslut
c.menu.ingame.resume = Ã…teruppta
c.menu.ingame.spectate = Titta på
-c.menu.lobby.enable_bots = Aktivera bottar
-c.menu.lobby.mapname = Kartnamn
-c.menu.lobby.players = Spelare
-c.menu.lobby.start = Starta spelet
+c.menu.map_selector.enable_bots = Aktivera bottar
+c.menu.map_selector.mapname = Kartnamn
+c.menu.map_selector.players = Spelare
+c.menu.map_selector.start = Starta spelet
c.menu.play.allow_query_registry = För att visa en offentlig serverlista kontaktas en registreringstjänst regelbundet. Registreringstjänsten är {0}.
c.menu.play.connect = Anslut
c.menu.play.editor_failed = Redigerare (misslyckades)
diff --git a/locale/tr.ini b/locale/tr.ini
index e05080e2..f85f94cc 100644
--- a/locale/tr.ini
+++ b/locale/tr.ini
@@ -37,10 +37,10 @@ c.menu.my_chef = Benim Åžefim
c.menu.ingame.reconnect = Yeniden BaÄŸlan
c.menu.ingame.resume = Devam et
c.menu.ingame.spectate = İzleyici
-c.menu.lobby.enable_bots = Botları etkinleştirin
-c.menu.lobby.mapname = Harita adı
-c.menu.lobby.players = Oyuncular
-c.menu.lobby.start = Oyunu baÅŸlat
+c.menu.map_selector.enable_bots = Botları etkinleştirin
+c.menu.map_selector.mapname = Harita adı
+c.menu.map_selector.players = Oyuncular
+c.menu.map_selector.start = Oyunu baÅŸlat
c.error.select_hairstyle = Bir saç modeli seçmelisiniz.
c.menu.play.connect = BaÄŸlan
c.menu.play.quick_connect = Hızlı Bağlantı
diff --git a/locale/vi.ini b/locale/vi.ini
index d779d106..d71959ea 100644
--- a/locale/vi.ini
+++ b/locale/vi.ini
@@ -78,10 +78,10 @@ c.menu.ingame.quit = Thoát khá»i trò chÆ¡i
c.menu.ingame.reconnect = Kết nối lại
c.menu.ingame.resume = Bản tóm tắt
c.menu.ingame.spectate = Quan sát
-c.menu.lobby.enable_bots = Bật bot
-c.menu.lobby.mapname = Tên bản đồ
-c.menu.lobby.players = Ngưá»i chÆ¡i
-c.menu.lobby.start = Bắt đầu trò chơi
+c.menu.map_selector.enable_bots = Bật bot
+c.menu.map_selector.mapname = Tên bản đồ
+c.menu.map_selector.players = Ngưá»i chÆ¡i
+c.menu.map_selector.start = Bắt đầu trò chơi
c.menu.play.allow_query_registry = Äể hiển thị danh sách máy chá»§ công cá»™ng, dịch vụ đăng ký sẽ được liên hệ thưá»ng xuyên. Dịch vụ đăng ký là {0}.
c.menu.play.connect = Kết nối
c.menu.play.editor_failed = Biên tập viên (thất bại)
diff --git a/locale/zh_Hans.ini b/locale/zh_Hans.ini
index 8839df3f..87676ee1 100644
--- a/locale/zh_Hans.ini
+++ b/locale/zh_Hans.ini
@@ -54,10 +54,10 @@ c.menu.ingame.quit=退出游æˆ
c.menu.ingame.reconnect=釿–°è¿žæŽ¥
c.menu.ingame.resume=釿–°å¼€å§‹
c.menu.ingame.spectate=æ—è§‚
-c.menu.lobby.enable_bots = å¯ç”¨æœºå™¨äºº
-c.menu.lobby.mapname = 地图åç§°
-c.menu.lobby.players = 玩家
-c.menu.lobby.start = 开始游æˆ
+c.menu.map_selector.enable_bots = å¯ç”¨æœºå™¨äºº
+c.menu.map_selector.mapname = 地图åç§°
+c.menu.map_selector.players = 玩家
+c.menu.map_selector.start = 开始游æˆ
c.menu.my_chef=我的厨师
c.menu.play.allow_query_registry = 为了显示公共æœåŠ¡å™¨åˆ—è¡¨ï¼Œä¼šå®šæœŸè”系注册表æœåŠ¡ã€‚æ³¨å†Œè¡¨æœåŠ¡ä¸º {0}。
c.menu.play.connect=连接
diff --git a/locale/zh_Hant.ini b/locale/zh_Hant.ini
index 0fbdfe7e..edfb063a 100644
--- a/locale/zh_Hant.ini
+++ b/locale/zh_Hant.ini
@@ -52,10 +52,10 @@ c.menu.ingame.quit=é€€å‡ºéŠæˆ²
c.menu.ingame.reconnect=釿–°é€£æŽ¥
c.menu.ingame.resume=æ¢å¾©
c.menu.ingame.spectate=æ—è§€
-c.menu.lobby.enable_bots = 啟用機器人
-c.menu.lobby.mapname = 地圖å稱
-c.menu.lobby.players = 玩家
-c.menu.lobby.start = é–‹å§‹éŠæˆ²
+c.menu.map_selector.enable_bots = 啟用機器人
+c.menu.map_selector.mapname = 地圖å稱
+c.menu.map_selector.players = 玩家
+c.menu.map_selector.start = é–‹å§‹éŠæˆ²
c.menu.my_chef=我的廚師
c.menu.play.connect=連接
c.menu.play.list_item = {0}({1} å玩家)
diff --git a/makefile b/makefile
index 8329192c..48d1f729 100644
--- a/makefile
+++ b/makefile
@@ -1,5 +1,5 @@
# Hurry Curry! - a game about cooking
-# Copyright (C) 2025 Hurry Curry! contributors
+# Copyright (C) 2026 Hurry Curry! Contributors
#
# 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
@@ -14,71 +14,174 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
-JSR = deno run
-GODOT = godot
+INSTALL_PREFIX = /usr/local
+HURRYCURRY_DATA_PATH = $(INSTALL_PREFIX)/share/hurrycurry
+HURRYCURRY_DISTRIBUTION = unknown
-CLIENT = $(GODOT) --path client --
+search-PATH = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH)))))
+version-match = $(shell $(1) --version | grep -q "$(2)")
-TOOLS = target/release/hurrycurry-tools
-BOOK_EXPORT = target/release/hurrycurry-book-export
-LOCALE_EXPORT = target/release/hurrycurry-locale-export
-SERVER = target/release/hurrycurry-server
-REPLAYTOOL = target/release/hurrycurry-replaytool
-DISCOVER = target/release/hurrycurry-discover
-EDITOR = target/release/hurrycurry-editor
+ifndef JSR
+ ifneq (,$(call search-PATH,deno))
+ JSR = DENO_NO_UPDATE_CHECK=1 deno run
+ else ifneq (,$(call search-PATH,node))
+ JSR = node
+ else
+ $(error "Couldn't detect JSR, please specify or install deno or node")
+ endif
+endif
-ALL_TESTCLIENT = test-client/main.js $(patsubst locale/%.ini,test-client/locale/%.json,$(wildcard locale/*.ini))
-ALL_BOOK = target/book/book.html $(patsubst locale/%.ini,target/book/book.%.html,$(wildcard locale/*.ini))
-ALL_SERVER = $(SERVER) $(TOOLS) $(REPLAYTOOL) $(EDITOR) $(DISCOVER) $(LOCALE_EXPORT)
-ALL_CLIENT = client/.godot/import-finished client/icons/adaptive-background.png client/icons/adaptive-foreground.png
-ALL_DATA = data/recipes/none.yaml data/recipes/anticurry.yaml data/recipes/default.yaml
-ALL = $(ALL_BOOK) $(ALL_SERVER) $(ALL_CLIENT) $(ALL_DATA)
-.PHONY: all clean all_client all_server all_book all_testclient all_data
-all: $(ALL)
-all_client: $(ALL_CLIENT)
-all_server: $(ALL_SERVER)
-all_book: $(ALL_BOOK)
-all_data: $(ALL_DATA)
-all_testclient: $(ALL_TESTCLIENT)
-clean:
- rm -rf target client/.godot
+ifndef GODOT
+ ifneq (,$(call search-PATH,godot))
+ GODOT = godot
+ else ifneq (,$(call search-PATH,godot-editor))
+ GODOT = godot-editor
+ else
+ $(error "Couldn't detect GODOT, please specify or install godot 4")
+ endif
+endif
+ifneq (,$(call version-match,$(GODOT),"\b4\\."))
+ $(error "GODOT is not godot 4, please install godot 4 instead")
+endif
-target/release/%: $(shell find server -type f -name '*.rs')
- cargo build --release --bin $(patsubst target/release/%,%,$@); touch $@
+RUN_CLIENT_WITH_GRAPHICS = $(GODOT) --path client --
+RUN_CLIENT = $(GODOT) --path client --display-driver headless --
-target/book/tiles/list: $(TOOLS) $(ALL_DATA)
- @mkdir -p $(dir $@); $(TOOLS) map-tiles 5star > $@~ && cp $@~ $@
-target/book/items/list: $(TOOLS) $(ALL_DATA)
- @mkdir -p $(dir $@); $(TOOLS) map-items 5star > $@~ && cp $@~ $@
-target/book/tiles/done: target/book/tiles/list client/.godot/import-finished
- $(CLIENT) --render-tiles ../$< --render-output ../$(dir $@) --render-resolution 512; touch $@
-target/book/items/done: target/book/items/list client/.godot/import-finished
- $(CLIENT) --render-items ../$< --render-output ../$(dir $@) --render-resolution 512; touch $@
-target/book/book.%.html: target/book/tiles/done target/book/items/done $(BOOK_EXPORT) $(ALL_DATA) locale/en.ini locale/%.ini
- $(BOOK_EXPORT) -o $@ -l $(shell basename -s .html $@ | cut -d '.' -f 2) -f html -m 5star
+ESBUILD_FLAGS = --bundle --format=esm --target=esnext --loader:.ini=text --minify --sourcemap
+
+BIN_BOOK_EXPORT = target/release/hurrycurry-book-export
+BIN_DISCOVER = target/release/hurrycurry-discover
+BIN_EDITOR = target/release/hurrycurry-editor
+BIN_LOCALE_EXPORT = target/release/hurrycurry-locale-export
+BIN_REPLAYTOOL = target/release/hurrycurry-replaytool
+BIN_SERVER = target/release/hurrycurry-server
+BIN_TOOLS = target/release/hurrycurry-tools
+
+ALL_SERVER = \
+ $(BIN_DISCOVER) \
+ $(BIN_EDITOR) \
+ $(BIN_REPLAYTOOL) \
+ $(BIN_SERVER) \
+ $(BIN_TOOLS)
+ALL_BOOK = \
+ target/book/book.html \
+ target/book/assets/fonts/josefin-sans.woff2 \
+ $(patsubst locale/%.ini,target/book/book.%.html,$(wildcard locale/*.ini))
+ALL_DATA = \
+ data/recipes/none.yaml \
+ data/recipes/anticurry.yaml \
+ data/recipes/default.yaml
+ALL_CLIENT = \
+ client/.godot/import-finished \
+ client/client.pck \
+ client/icons/adaptive-background.png \
+ client/icons/adaptive-foreground.png
+ALL_TEST_CLIENT = \
+ test-client/main.js
+ALL = \
+ $(ALL_SERVER) \
+ $(ALL_BOOK) \
+ $(ALL_DATA) \
+ $(ALL_CLIENT)
+
+all: $(ALL) PHONY
+all_server: $(ALL_SERVER) PHONY
+all_book: $(ALL_BOOK) PHONY
+all_data: $(ALL_DATA) PHONY
+all_client: $(ALL_CLIENT) PHONY
+all_test_client: $(ALL_TEST_CLIENT) PHONY
+
+target/release/%: $(shell find server -type f -name "*.rs" -or -name "Cargo.*" -or -name '*.css') $(shell ls Cargo.*)
+ +env \
+ HURRYCURRY_DATA_PATH=$(HURRYCURRY_DATA_PATH) \
+ HURRYCURRY_DISTRIBUTION=$(HURRYCURRY_DISTRIBUTION) \
+ cargo build --release --bin $(patsubst target/release/%,%,$@)
+ touch $@
+
+target/book/tiles/list: $(BIN_TOOLS) $(ALL_DATA)
+ @mkdir -p $(dir $@)
+ $(BIN_TOOLS) all-tiles > $@
+target/book/items/list: $(BIN_TOOLS) $(ALL_DATA)
+ @mkdir -p $(dir $@)
+ $(BIN_TOOLS) all-items > $@
+target/book/%/done_glb_export: target/book/%/list client/.godot/import-finished
+ $(RUN_CLIENT) --render-$(shell basename $(dir $@)) ../$< --render-output ../$(dir $@) --render-format gltf --render-include-tool-base
+ touch $@
+ifndef BOOK_USE_BLENDER_RENDER
+target/book/%/done: target/book/%/list client/.godot/import-finished
+ $(RUN_CLIENT_WITH_GRAPHICS) --render-$(shell basename $(dir $@)) ../$< --render-output ../$(dir $@) --render-resolution 512 --render-include-tool-base
+ touch $@
+else
+target/book/%/done: target/book/%/list target/book/%/done_glb_export
+ blender -b -P server/book-export/src/bpy_glb_render.py -- $(dir $@) $< 512
+ touch $@
+endif
+target/book/book.%.html: target/book/tiles/done target/book/items/done $(BIN_BOOK_EXPORT) $(ALL_DATA) locale/en.ini locale/%.ini
+ $(BIN_BOOK_EXPORT) -o $@ -l $(shell basename -s .html $@ | cut -d '.' -f 2) -f html -m 5star
target/book/book.html: target/book/book.en.html
cp -v $< $@
+target/book/assets/fonts/josefin-sans.woff2: client/gui/resources/fonts/font-josefin-sans.woff2
+ @mkdir -p $(dir $@)
+ cp -v $< $@
data/recipes/none.yaml:
echo > $@
data/recipes/anticurry.yaml: data/recipes/default.yaml
- data/recipes/anticurry.sed < $< > $@~ && cp $@~ $@
+ sed --file=data/recipes/anticurry.sed < $< > $@
data/recipes/default.yaml: data/recipes/default.js
- DENO_NO_UPDATE_CHECK=1 $(JSR) $< > $@~ && cp $@~ $@
+ $(JSR) $< > $@
data/recipes/%.gv.txt: data/recipes/%.yaml
- $(TOOLS) graph > $@~ && cp $@~ $@
+ $(BIN_TOOLS) graph > $@
data/recipes/%.svg: data/recipes/%.gv.txt
- dot -Tsvg -Kdot < $< > $@~ && cp $@~ $@
+ dot -Tsvg -Kdot < $< > $@
client/.godot/import-finished: $(shell find client -name '*.gd' -or -name '*.res')
- $(GODOT) --headless --import client/project.godot; touch $@
+ $(GODOT) --headless --import client/project.godot
+ touch $@
client/icons/adaptive-background.png:
ffmpeg -f lavfi -i "color=color=#E28142,scale=432x432" -frames:v 1 -y $@
client/icons/adaptive-foreground.png: client/icons/main.png
ffmpeg -f image2 -i $< -vf "scale=280x280,pad=432:432:(ow-iw)/2:(oh-ih)/2:0x00000000" -frames:v 1 -y $@
+client/client.pck: client/.godot/import-finished client/icons/adaptive-background.png client/icons/adaptive-foreground.png
+ $(GODOT) --headless --export-pack wasm32-unknown-unknown client.pck client/project.godot
-test-client/main.js: test-client/main.ts $(wildcard test-client/*.ts)
- esbuild $< --bundle --outfile=$@ --target=esnext --format=esm
-test-client/locale/%.json: $(LOCALE_EXPORT) locale/%.ini locale/en.ini
+test-client/main.js: test-client/main.ts test-client/assets/locale.json.gz $(wildcard test-client/*.ts)
+ esbuild $< --outfile=$@ $(ESBUILD_FLAGS)
+test-client/assets/locale.json: $(BIN_LOCALE_EXPORT) $(wildcard locale/*.ini)
@mkdir -p test-client/locale
- $(LOCALE_EXPORT) export-json $@ $(wordlist 2,99,$(^))
+ $(BIN_LOCALE_EXPORT) export-json-pack $@ $(wordlist 2,99,$(^))
+test-client/assets/locale.json.gz: test-client/assets/locale.json
+ gzip -9kf $<
+watch_test_client: all_test_client PHONY
+ esbuild test-client/main.ts \
+ $(ESBUILD_FLAGS) \
+ --outfile=test-client/main.js \
+ --watch --servedir=test-client --serve=127.0.0.1:8080
+
+install: install_common install_server install_client PHONY
+install_common: $(ALL_DATA) PHONY
+ install -Dt$(HURRYCURRY_DATA_PATH)/ -m644 data/*.yaml
+ install -Dt$(HURRYCURRY_DATA_PATH)/maps/ -m644 data/maps/*.yaml
+ install -Dt$(HURRYCURRY_DATA_PATH)/recipes/ -m644 data/recipes/*.yaml
+install_server: $(ALL_SERVER) PHONY
+ install -Dt$(INSTALL_PREFIX)/bin/ -m755 $(BIN_DISCOVER) $(BIN_EDITOR) $(BIN_REPLAYTOOL) $(BIN_SERVER) $(BIN_TOOLS)
+install_client: $(ALL_CLIENT) PHONY
+ echo '#!/bin/sh' >$(INSTALL_PREFIX)/bin/hurrycurry
+ echo 'exec godot --main-pack $(INSTALL_PREFIX)/lib/hurrycurry/client.pck "$$@"' >>$(INSTALL_PREFIX)/bin/hurrycurry
+ chmod 755 $(INSTALL_PREFIX)/bin/hurrycurry
+ install -Dt$(INSTALL_PREFIX)/lib/hurrycurry/ -m644 client/client.pck
+
+clean: PHONY
+ rm -rf client/.godot/
+ rm -f client/icons/adaptive-background.png*
+ rm -f client/icons/adaptive-foreground.png*
+ rm -f client/client.pck
+ rm -f data/recipes/none.yaml
+ rm -f data/recipes/anticurry.yaml
+ rm -f data/recipes/default.yaml
+ rm -rf target/
+ rm -rf test-client/locale/
+ rm -f test-client/*.js
+
+.DELETE_ON_ERROR:
+.PHONY: PHONY
diff --git a/manifest.scm b/manifest.scm
new file mode 100644
index 00000000..75f6edb6
--- /dev/null
+++ b/manifest.scm
@@ -0,0 +1,40 @@
+(use-modules (guix channels)
+ (guix inferior)
+ (guix profiles)
+ (guix ui)
+ (srfi srfi-11))
+
+(define channels
+ (list
+ (channel
+ (name 'guix)
+ (url "https://git.guix.gnu.org/guix.git")
+ (branch "master")
+ (commit
+ "71ffb948b32b361d02ec0781e25f1e8f8f5aea75")
+ (introduction
+ (make-channel-introduction
+ "9edb3f66fd807b096b48283debdcddccfea34bad"
+ (openpgp-fingerprint
+ "BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA"))))))
+
+(define inferior
+ (inferior-for-channels channels))
+
+(define (pkg spec)
+ (let-values (((name version output)
+ (package-specification->name+version+output spec)))
+ (list (car (lookup-inferior-packages inferior name version))
+ output)))
+(define (pkgs . args)
+ (map pkg args))
+
+(packages->manifest
+ (pkgs "bash"
+ "coreutils" "findutils" "sed" "grep"
+ "make"
+ "rust" "rust:cargo" "rust:tools" "gcc-toolchain"
+ "rust-analyzer"
+ "node" "esbuild"
+ "godot" "ffmpeg" "graphviz"
+ "nss-certs"))
diff --git a/protocol.md b/protocol.md
index a014d17e..3f3506f0 100644
--- a/protocol.md
+++ b/protocol.md
@@ -10,11 +10,11 @@ The protocol schema is defined in [`protocol.ts`](./test-client/protocol.ts)
`joined`.
5. Run the game loop
- Send a `keepalive` packet every 1000ms.
- - Send your position via `movement` every 20ms - 40ms.
+ - Send your position and inputs via `movement` whenever inputs change.
- Send `interact` when the player interacts with a tile.
- Receive packets
6. The Game ends. The server will remove all players and tiles, then send
- `set_ingame(state=false)`. Then continue at step 4.
+ `set_ingame(state=false)`. Then continue at step 3.
## Ports
@@ -24,16 +24,6 @@ The protocol schema is defined in [`protocol.ts`](./test-client/protocol.ts)
- 27034: Lobby Server Websocket
- 27035: Map Editor Server Websocket
-<!-- ## Binary Protocol
-
-Servers might also support the binary protocol. It uses
-[Bincode](https://github.com/bincode-org/bincode) to encode packets. If a server
-advertises bincode support with the `init` packet, you are free to use the
-binary protocol. By default the server will send JSON. For every packet you
-send, you can choose between bincode and JSON. After a client sent the first
-Bincode packet, the server might start to reply using Bincode aswell. Bincoded
-packets are sent with WebSocket "Binary" messages. -->
-
## Protocol Versioning
The `init` packet sends minor and major version numbers of the protocol is use
@@ -56,6 +46,5 @@ latency. For this reason it implemented three times:
The server currently enforces the following network limits:
- Server inbound json packets are at most 8196 bytes in size
-- Server inbound binary packets are at most 4096 bytes in size
- Player names are no longer than 32 characters
- Player names are no longer than 64 bytes
diff --git a/server/book-export/src/book_html.css b/server/book-export/src/book_html.css
index 97f0702c..6b40d866 100644
--- a/server/book-export/src/book_html.css
+++ b/server/book-export/src/book_html.css
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -15,6 +15,16 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+
+@font-face {
+ font-family: "Josefin Sans";
+ src: url("assets/fonts/josefin-sans.woff2");
+}
+
+:root {
+ font-family: "Josefin Sans", sans-serif;
+}
+
body {
margin: 0px;
background-color: rgb(74, 74, 74);
@@ -43,4 +53,18 @@ body {
svg {
width: 100%;
height: 100%;
-} \ No newline at end of file
+}
+
+ul.grid {
+ display: block;
+ padding: 0px;
+}
+ul.grid li {
+ display: inline-block;
+ text-align: center;
+}
+.icon {
+ display: block;
+ width: 128px;
+ height: 128px;
+}
diff --git a/server/book-export/src/book_html.rs b/server/book-export/src/book_html.rs
index 0cbb42e9..a41bb514 100644
--- a/server/book-export/src/book_html.rs
+++ b/server/book-export/src/book_html.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -36,7 +36,7 @@ markup::define! {
html {
head {
meta[charset="UTF-8"];
- style { @include_str!("book_html.css").replace(" ", "").replace("\n", "") }
+ style { @markup::raw(include_str!("book_html.css").replace("\n", "")) }
title { "Recipe Book - Hurry Curry!" }
}
body {
@@ -54,10 +54,13 @@ markup::define! {
div.page {}
div.page {}
}
- BookPage::Recipe { title, description, diagram } => {
+ BookPage::Recipe { title, description, diagram, points } => {
div.page {
h1 { @MessageR { data, locale, message: title } }
p { @MessageR { data, locale, message: description } }
+ ul.grid { @for (item, pts) in points {
+ li { img.icon[src=format!("items/{}.png", data.item_name(*item))]; @format!(" {pts} points") }
+ } }
}
div.page {
@DiagramR { data, diagram }
diff --git a/server/book-export/src/bpy_glb_render.py b/server/book-export/src/bpy_glb_render.py
new file mode 100644
index 00000000..37a7dcaa
--- /dev/null
+++ b/server/book-export/src/bpy_glb_render.py
@@ -0,0 +1,30 @@
+import bpy
+from sys import argv
+
+directory = argv[-3].removesuffix("/")
+list_file = argv[-2]
+resolution = int(argv[-1])
+
+for name in open(list_file):
+ name = name.strip()
+ print(f"-- {name}")
+ input_glb = f"{directory}/{name}.glb"
+ output_png = f"{directory}/{name}.png"
+ print(input_glb, output_png)
+
+ bpy.ops.object.select_all(action='SELECT')
+ bpy.ops.object.delete(use_global=False, confirm=False)
+
+ bpy.ops.import_scene.gltf(filepath=input_glb)
+
+ bpy.context.scene.camera = bpy.data.objects["Camera3D"]
+ bpy.context.scene.render.film_transparent = True
+ bpy.context.scene.render.resolution_x = resolution
+ bpy.context.scene.render.resolution_y = resolution
+ bpy.context.scene.render.image_settings.file_format = "PNG"
+ bpy.context.scene.render.engine = 'CYCLES'
+ bpy.context.scene.cycles.samples = 512
+ bpy.context.scene.view_settings.view_transform = 'Khronos PBR Neutral'
+
+ bpy.ops.render.render()
+ bpy.data.images["Render Result"].save_render(output_png)
diff --git a/server/book-export/src/diagram_svg.rs b/server/book-export/src/diagram_svg.rs
index 290db072..2e45368b 100644
--- a/server/book-export/src/diagram_svg.rs
+++ b/server/book-export/src/diagram_svg.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/book-export/src/main.rs b/server/book-export/src/main.rs
index 4bd8935d..7cb3f964 100644
--- a/server/book-export/src/main.rs
+++ b/server/book-export/src/main.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -23,6 +23,7 @@ use anyhow::Result;
use clap::{Parser, ValueEnum};
use hurrycurry_data::{book::book, build_gamedata};
use hurrycurry_locale::Locale;
+use hurrycurry_protocol::GameConfig;
use std::{fs::File, io::Write, path::PathBuf};
#[derive(Parser)]
@@ -49,7 +50,14 @@ fn main() -> Result<()> {
env_logger::init_from_env("LOG");
let args = Args::parse();
- let (data, serverdata) = build_gamedata("data".as_ref(), &args.map, true)?;
+ let (data, serverdata) = build_gamedata(
+ "data".as_ref(),
+ &GameConfig {
+ map: args.map,
+ ..Default::default()
+ },
+ true,
+ )?;
let book = book(&data, &serverdata)?;
diff --git a/server/bot/Cargo.toml b/server/bot/Cargo.toml
index e73be6e9..a443e204 100644
--- a/server/bot/Cargo.toml
+++ b/server/bot/Cargo.toml
@@ -14,5 +14,5 @@ clap = { version = "4.5.47", features = ["derive"] }
rand = "0.10.0"
[features]
-# default = ["debug_events"]
+default = ["debug_events"]
debug_events = []
diff --git a/server/bot/src/algos/customer.rs b/server/bot/src/algos/customer.rs
index d4284ea8..2f1c60f2 100644
--- a/server/bot/src/algos/customer.rs
+++ b/server/bot/src/algos/customer.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -22,7 +22,8 @@ use crate::{
};
use hurrycurry_game_core::Game;
use hurrycurry_protocol::{
- DemandIndex, Hand, ItemLocation, Message, PacketS, PlayerClass, PlayerID, Score, TileIndex,
+ DebugEvent, DemandIndex, Effect, Hand, ItemLocation, Message, PacketS, PlayerClass, PlayerID,
+ Score, TileIndex,
glam::{IVec2, Vec2},
};
use log::debug;
@@ -97,6 +98,10 @@ impl CustomerState {
return;
};
let pos = playerdata.movement.position;
+ #[cfg(feature = "debug_events")]
+ {
+ out.push(PacketS::Debug(self.debug(me)));
+ }
match self {
CustomerState::New => {
if !game.data.demands.is_empty() {
@@ -213,14 +218,19 @@ impl CustomerState {
player: me,
pin: Some(false),
});
+ let points = -1;
out.push(PacketS::ApplyScore(Score {
- points: -1,
+ points,
demands_failed: 1,
..Default::default()
}));
out.push(PacketS::Effect {
- name: "angry".to_string(),
- player: me,
+ effect: Effect::Angry,
+ location: ItemLocation::Player(me, Hand(0)),
+ });
+ out.push(PacketS::Effect {
+ effect: Effect::Points { amount: points },
+ location: ItemLocation::Player(me, Hand(0)),
});
*self = CustomerState::Exiting { path };
return;
@@ -254,7 +264,7 @@ impl CustomerState {
}
}
- let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y]
+ let mut demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y]
.into_iter()
.find_map(|off| {
let pos = *chair + off;
@@ -274,6 +284,25 @@ impl CustomerState {
None
}
});
+
+ // Check for other customer with more urgent order for the item
+ if let Some(pos) = demand_pos {
+ game.players_spatial_index.query(
+ pos.as_vec2() + Vec2::splat(0.5),
+ 1.5,
+ |p, _| {
+ if let Some((Message::Item(item), t)) =
+ &game.players[&p].communicate_persist
+ && p != me
+ && t.remaining < *timeout
+ && *item == demand_data.input
+ {
+ demand_pos = None;
+ }
+ },
+ );
+ }
+
if let Some(table) = demand_pos {
debug!("{me:?} -> eating");
let points = game.data.demands[demand.0].points;
@@ -290,8 +319,12 @@ impl CustomerState {
pin: Some(true),
});
out.push(PacketS::Effect {
- name: "satisfied".to_string(),
- player: me,
+ effect: Effect::Satisfied,
+ location: ItemLocation::Player(me, Hand(0)),
+ });
+ out.push(PacketS::Effect {
+ effect: Effect::Points { amount: points },
+ location: ItemLocation::Player(me, Hand(0)),
});
out.push(PacketS::Interact {
target: Some(ItemLocation::Tile(table)),
@@ -400,4 +433,41 @@ impl CustomerState {
CustomerState::Exited => (),
}
}
+
+ #[cfg(feature = "debug_events")]
+ pub fn debug(&self, me: PlayerID) -> DebugEvent {
+ use crate::debug_player_color;
+ use hurrycurry_protocol::{DebugEvent, DebugEventDisplay};
+
+ let text = match self {
+ CustomerState::New => format!("New"),
+ CustomerState::Entering { .. } => format!("Entering"),
+ CustomerState::Waiting {
+ demand,
+ timeout,
+ pinned,
+ ..
+ } => format!(
+ "Waiting\nD={} t={timeout:.01} pin={}",
+ demand.0, *pinned as u8
+ ),
+ CustomerState::Eating {
+ demand, progress, ..
+ } => format!("Eating\nD={} pr={progress:.02}", demand.0),
+ CustomerState::Finishing { cooldown, .. } => {
+ format!("Finishing\nc={cooldown:.01}")
+ }
+ CustomerState::Exiting { .. } => format!("Exiting"),
+ CustomerState::Exited => format!("Exited"),
+ };
+ DebugEvent {
+ key: format!("player-{me}"),
+ timeout: 0.1,
+ color: debug_player_color(me),
+ display: DebugEventDisplay::Label {
+ pos: ItemLocation::Player(me, Hand(0)),
+ text,
+ },
+ }
+ }
}
diff --git a/server/bot/src/algos/dishwasher.rs b/server/bot/src/algos/dishwasher.rs
index 563e8c54..f72804bb 100644
--- a/server/bot/src/algos/dishwasher.rs
+++ b/server/bot/src/algos/dishwasher.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -32,7 +32,7 @@ impl DishWasher {
pub fn new(me: PlayerID) -> Self {
Self {
me,
- step: StepState::new_idle(me),
+ step: StepState::new_idle(me, "idle".to_string()),
dirty_plate: None,
}
}
@@ -53,7 +53,8 @@ impl BotAlgo for DishWasher {
game,
me: self.me,
state: self,
- recursion_abort: 0,
+ complexity_abort: 0,
+ stack: Vec::new(),
}
.update()
.ok();
@@ -64,23 +65,21 @@ impl State for DishWasher {
fn queue_step(&mut self, step: StepState) {
self.step = step;
}
- fn get_empty_tile_priority(&self) -> &'static [&'static str] {
- &["counter-window", "counter"]
- }
}
impl Context<'_, DishWasher> {
fn update(&mut self) -> LogicRes {
- if let Some(sink) = self.find_empty_interactable_tile_by_name("sink") {
+ if let Some(sink) = self.find_free_tool_by_name("sink") {
if self.is_hand_item(self.state.dirty_plate.unwrap()) {
// TODO clear sink first but dont "steal" items from others working there
- self.interact_with(sink, 2.0)?;
+ self.interact_with(sink, 2.0, "wash")?;
} else {
self.assert_hand_is_clear()?;
}
- if let Some(pos) = self.find_item_on_map(self.state.dirty_plate.unwrap()) {
- self.assert_tile_is_clear(pos)?;
+ if let Some(pos) = self.find_item_on_map(false, self.state.dirty_plate.unwrap()) {
+ self.interact_with(pos, 0., "pick dirty plate")?;
}
+ self.wait(0.5, "idle")?;
} else {
self.assert_hand_is_clear()?;
}
diff --git a/server/bot/src/algos/frank.rs b/server/bot/src/algos/frank.rs
index 156d655d..68130038 100644
--- a/server/bot/src/algos/frank.rs
+++ b/server/bot/src/algos/frank.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/bot/src/algos/mod.rs b/server/bot/src/algos/mod.rs
index 178ecec2..4abe40ca 100644
--- a/server/bot/src/algos/mod.rs
+++ b/server/bot/src/algos/mod.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/bot/src/algos/simple.rs b/server/bot/src/algos/simple.rs
index 5d19ed58..85dba85e 100644
--- a/server/bot/src/algos/simple.rs
+++ b/server/bot/src/algos/simple.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -18,20 +18,24 @@
use crate::{BotAlgo, PacketSink, step::StepState};
use hurrycurry_game_core::Game;
use hurrycurry_protocol::{
- Hand, ItemIndex, Message, PlayerID, Recipe, RecipeIndex, TileIndex, glam::IVec2,
+ Hand, ItemIndex, ItemLocation, Message, PlayerID, Recipe, RecipeIndex, TileIndex,
+ glam::{IVec2, Vec2},
};
use log::{debug, warn};
+use std::fmt::Write;
pub struct Simple {
step: StepState,
me: PlayerID,
+ choosen_order: Option<ItemIndex>,
}
pub struct Context<'a, State> {
pub game: &'a Game,
pub me: PlayerID,
pub state: &'a mut State,
- pub recursion_abort: usize,
+ pub complexity_abort: usize,
+ pub stack: Vec<ItemIndex>,
}
type LogicRes<Out = ()> = Result<Out, ()>;
@@ -40,7 +44,8 @@ impl Simple {
pub fn new(me: PlayerID) -> Self {
Self {
me,
- step: StepState::new_idle(me),
+ step: StepState::new_idle(me, "idle".to_string()),
+ choosen_order: None,
}
}
}
@@ -54,7 +59,8 @@ impl BotAlgo for Simple {
game,
me: self.me,
state: self,
- recursion_abort: 0,
+ complexity_abort: 0,
+ stack: vec![],
}
.update()
.ok();
@@ -63,15 +69,11 @@ impl BotAlgo for Simple {
pub trait State {
fn queue_step(&mut self, step: StepState);
- fn get_empty_tile_priority(&self) -> &'static [&'static str];
}
impl State for Simple {
fn queue_step(&mut self, step: StepState) {
self.step = step;
}
- fn get_empty_tile_priority(&self) -> &'static [&'static str] {
- &["counter", "counter-window"]
- }
}
impl<S> Context<'_, S> {
@@ -111,6 +113,21 @@ impl<S> Context<'_, S> {
})
.collect()
}
+ pub fn find_burning(&self) -> Option<IVec2> {
+ self.game
+ .item_locations_index
+ .iter()
+ .filter_map(|p| match p {
+ ItemLocation::Tile(p) => Some(*p),
+ _ => None,
+ })
+ .find(|t| {
+ self.game.tiles[t]
+ .item
+ .as_ref()
+ .is_some_and(|i| i.active.as_ref().is_some_and(|i| i.warn && i.speed > 0.))
+ })
+ }
pub fn find_recipe_with_output(&self, item: ItemIndex) -> Option<RecipeIndex> {
self.game
.data
@@ -120,45 +137,80 @@ impl<S> Context<'_, S> {
.find(|(_, r)| r.outputs().any(|i| i == item))
.map(|(i, _)| RecipeIndex(i))
}
- pub fn find_item_on_map(&self, item: ItemIndex) -> Option<IVec2> {
+ pub fn find_item_on_map(&self, allow_table: bool, item: ItemIndex) -> Option<IVec2> {
self.game
- .tiles
+ .item_locations_index
.iter()
- .find(|(_, t)| t.item.as_ref().is_some_and(|t| t.kind == item))
- .map(|(p, _)| *p)
+ .filter_map(|p| match p {
+ ItemLocation::Tile(p) => Some(*p),
+ _ => None,
+ })
+ .filter(|p| {
+ allow_table
+ || !self.game.tiles[p]
+ .parts
+ .iter()
+ .any(|t| self.game.data.tile_name(*t) == "table")
+ })
+ .find(|p| {
+ self.game.tiles[p]
+ .item
+ .as_ref()
+ .is_some_and(|t| t.kind == item)
+ })
}
pub fn find_tile(&self, tile: TileIndex) -> Option<IVec2> {
+ let pos = self.my_position().as_ivec2();
self.game
- .tiles
+ .tile_index
+ .get(&tile)?
.iter()
- .find(|(_, t)| t.parts.contains(&tile)) // TODO opt use tile index
- .map(|(p, _)| *p)
+ .min_by_key(|p| p.manhattan_distance(pos))
+ .copied()
}
- pub fn find_occupied_table_or_floor(&self) -> Option<IVec2> {
+ pub fn find_occupied_table(&self) -> Option<IVec2> {
+ let pos = self.my_position().as_ivec2();
+ let tile = self.game.data.get_tile_by_name("table")?;
self.game
- .tiles
+ .tile_index
+ .get(&tile)?
.iter()
- .find(|(_, t)| {
- t.item.is_some()
- && t.parts.iter().any(|p| {
- matches!(self.game.data.tile_names[p.0].as_str(), "table" | "floor")
- })
- })
- .map(|(p, _)| *p)
+ .filter(|pos| self.game.tiles[pos].item.is_some())
+ .min_by_key(|p| p.manhattan_distance(pos))
+ .copied()
}
- pub fn find_empty_interactable_tile_by_name(&self, name: &str) -> Option<IVec2> {
+ pub fn my_position(&self) -> Vec2 {
+ self.game.players[&self.me].movement.position
+ }
+ pub fn find_free_tool_by_name(&self, name: &str) -> Option<IVec2> {
+ let pos = self.my_position().as_ivec2();
+ let tile = self.game.data.get_tile_by_name(name)?;
self.game
- .tiles
+ .tile_index
+ .get(&tile)?
+ .iter()
+ .filter(|pos| self.game.tiles[pos].item.is_none())
+ .min_by_key(|p| p.manhattan_distance(pos))
+ .copied()
+ }
+ pub fn find_stash_tile_by_name(&self, name: &str) -> Option<IVec2> {
+ let pos = self.my_position().as_ivec2();
+ let tile = self.game.data.get_tile_by_name(name)?;
+ self.game
+ .tile_index
+ .get(&tile)?
.iter()
- .find(|(_, t)| {
- t.item.is_none()
- && t.parts
+ .filter(|pos| {
+ self.game.tiles[pos].item.is_none()
+ // ensure there is no tool or other part that blocks access
+ && self.game.tiles[pos]
+ .parts
.iter()
- .any(|i| self.game.data.tile_names[i.0] == name) // TODO use index
+ .all(|p| matches!(self.game.data.tile_name(*p), "counter" | "counter-window" | "floor"))
})
- .map(|(p, _)| *p)
+ .min_by_key(|p| p.manhattan_distance(pos))
+ .copied()
}
-
pub fn is_tile_occupied(&self, pos: IVec2) -> bool {
self.game
.tiles
@@ -168,11 +220,12 @@ impl<S> Context<'_, S> {
}
}
impl<S: State> Context<'_, S> {
- pub fn find_empty_interactable_tile(&self) -> Option<IVec2> {
- for p in self.state.get_empty_tile_priority() {
- if let Some(t) = self.find_empty_interactable_tile_by_name(p) {
- return Some(t);
- }
+ pub fn find_stash_tile(&self, prefer_cw: bool) -> Option<IVec2> {
+ if prefer_cw && let Some(t) = self.find_stash_tile_by_name("counter-window") {
+ return Some(t);
+ }
+ if let Some(t) = self.find_stash_tile_by_name("counter") {
+ return Some(t);
}
warn!("all counters filled up");
None
@@ -180,7 +233,7 @@ impl<S: State> Context<'_, S> {
pub fn clear_tile(&mut self, pos: IVec2) -> LogicRes {
debug!("clear tile {pos}");
self.assert_hand_is_clear()?;
- self.interact_with(pos, 0.)
+ self.interact_with(pos, 0., "clear tile")
}
pub fn assert_tile_is_clear(&mut self, pos: IVec2) -> LogicRes {
if self.is_tile_occupied(pos) {
@@ -190,57 +243,77 @@ impl<S: State> Context<'_, S> {
}
pub fn assert_hand_is_clear(&mut self) -> LogicRes {
if self.is_hand_occupied() {
- self.dispose_hand()?;
+ self.dispose_hand(false, "clear hand")?;
}
Ok(())
}
- pub fn dispose_hand(&mut self) -> LogicRes {
+ pub fn dispose_hand(&mut self, prefer_cw: bool, label: &str) -> LogicRes {
debug!("dispose hand");
- if let Some(pos) = self.find_empty_interactable_tile() {
- self.interact_with(pos, 0.)?;
- warn!("no path to empty space ");
+ if let Some(pos) = self.find_stash_tile(prefer_cw) {
+ self.interact_with(pos, 0., label)?;
+ warn!("no path to empty space");
Err(())
} else {
warn!("no empty space left");
Err(())
}
}
- pub fn interact_with(&mut self, tile: IVec2, duration: f32) -> LogicRes {
- if let Some(step) = StepState::new_segment(self.game, self.me, Hand(0), tile, duration) {
+ pub fn interact_with(&mut self, tile: IVec2, duration: f32, label: &str) -> LogicRes {
+ if let Some(step) = StepState::new_segment(
+ self.game,
+ self.me,
+ Hand(0),
+ tile,
+ duration,
+ self.stack_label(label),
+ ) {
self.state.queue_step(step);
Err(())
} else {
Ok(())
}
}
+ pub fn wait(&mut self, delay: f32, label: &str) -> LogicRes {
+ self.state
+ .queue_step(StepState::new_wait(self.me, delay, self.stack_label(label)));
+ return Err(());
+ }
+ fn stack_label(&self, inner: &str) -> String {
+ let mut o = String::new();
+ for &i in &self.stack {
+ writeln!(o, "-> {}", self.game.data.item_name(i)).unwrap();
+ }
+ write!(o, "{inner}").unwrap();
+ o
+ }
}
impl Context<'_, Simple> {
pub fn aquire_placed_item(&mut self, item: ItemIndex) -> LogicRes<IVec2> {
debug!("aquire placed item {:?}", self.game.data.item_names[item.0]);
- if let Some(pos) = self.find_item_on_map(item) {
+ if let Some(pos) = self.find_item_on_map(false, item) {
return Ok(pos);
}
self.aquire_item(item)?;
- self.dispose_hand()?;
+ self.dispose_hand(false, "put down item")?;
Err(())
}
pub fn aquire_item(&mut self, item: ItemIndex) -> LogicRes {
+ self.stack.push(item);
debug!("aquire item {:?}", self.game.data.item_names[item.0]);
- self.recursion_abort += 1;
- if self.recursion_abort > 32 {
- warn!("too much recursion");
- return Err(());
+ self.complexity_abort += 1;
+ if self.complexity_abort > 32 {
+ warn!("too much complexity");
+ self.wait(0.5, "complexity limit")?;
}
if self.is_hand_item(item) {
+ self.stack.pop();
return Ok(());
}
- if let Some(pos) = self.find_item_on_map(item) {
+ if let Some(pos) = self.find_item_on_map(false, item) {
self.assert_hand_is_clear()?;
- self.interact_with(pos, 0.)?;
- return Ok(());
- }
- if let Some(recipe) = self.find_recipe_with_output(item) {
+ self.interact_with(pos, 0., "pick item")?;
+ } else if let Some(recipe) = self.find_recipe_with_output(item) {
let r = &self.game.data.recipes[recipe.0];
match r {
Recipe::Instant {
@@ -251,7 +324,7 @@ impl Context<'_, Simple> {
if let Some(pos) = self.find_tile(*tile) {
self.assert_tile_is_clear(pos)?;
self.assert_hand_is_clear()?;
- self.interact_with(pos, 0.)?;
+ self.interact_with(pos, 0., "instant from empty")?;
}
}
Recipe::Instant {
@@ -261,7 +334,7 @@ impl Context<'_, Simple> {
} => {
let apos = self.aquire_placed_item(*a)?;
self.aquire_item(*b)?;
- self.interact_with(apos, 0.)?;
+ self.interact_with(apos, 0., "instant (B)")?;
}
Recipe::Instant {
tile: None,
@@ -269,8 +342,8 @@ impl Context<'_, Simple> {
..
} => {
self.aquire_item(*input)?;
- if let Some(pos) = self.find_empty_interactable_tile() {
- self.interact_with(pos, 0.)?;
+ if let Some(pos) = self.find_stash_tile(false) {
+ self.interact_with(pos, 0., "instant (A)")?;
} else {
warn!("no empty space left")
}
@@ -284,7 +357,7 @@ impl Context<'_, Simple> {
if let Some(pos) = self.find_tile(*tile) {
self.assert_tile_is_clear(pos)?;
self.aquire_item(*input)?;
- self.interact_with(pos, 1. / speed + 0.2)?;
+ self.interact_with(pos, 1. / speed + 0.2, "active")?;
}
}
Recipe::Passive {
@@ -296,14 +369,13 @@ impl Context<'_, Simple> {
if let Some(item) = &self.game.tiles.get(&pos).unwrap().item {
if item.kind == *input {
debug!("waiting for passive to finish at {pos}");
- self.state.queue_step(StepState::new_wait(self.me, 0.5));
- return Err(()); // waiting for it to finish
+ self.wait(0.5, "waiting for passive")?;
} else {
self.assert_tile_is_clear(pos)?;
}
}
self.aquire_item(*input)?;
- self.interact_with(pos, 0.)?;
+ self.interact_with(pos, 0., "pick passive output")?;
}
}
Recipe::Passive {
@@ -311,26 +383,39 @@ impl Context<'_, Simple> {
} => {
self.aquire_placed_item(*input)?;
debug!("waiting for passive to finish");
- self.state.queue_step(StepState::new_wait(self.me, 0.5));
- return Err(());
+ self.wait(0.5, "wait for passive")?;
}
_ => warn!("recipe too hard {r:?}"),
}
+ warn!(
+ "stuck at making item {:?} via {r:?}",
+ self.game.data.item_names[item.0]
+ );
}
- warn!(
- "stuck at making item {:?}",
- self.game.data.item_names[item.0]
- );
Err(())
}
pub fn update(&mut self) -> LogicRes {
- if let Some((item, table, _)) = self.find_demands().pop() {
- if self.game.data.item_name(item) == "unknown-order" {
- self.interact_with(table, 0.)?;
+ if let Some(pos) = self.find_burning() {
+ self.assert_hand_is_clear()?;
+ self.interact_with(pos, 0., "prevent burning")?;
+ } else if let Some(item) = self.state.choosen_order {
+ if !self.find_demands().iter().any(|(i, _, _)| *i == item) {
+ self.state.choosen_order = None;
+ }
+ self.aquire_item(item)?;
+ self.state.choosen_order = None;
+ self.dispose_hand(true, "drop for delivery")?;
+ } else {
+ if let Some((item, _, _)) = self
+ .find_demands()
+ .iter()
+ .filter(|(i, _, _)| self.game.data.item_name(*i) != "unknown-order")
+ .filter(|(i, _, _)| self.find_item_on_map(false, *i).is_none())
+ .min_by_key(|(_, _, d)| (*d * 1000.) as i64)
+ {
+ self.state.choosen_order = Some(*item)
} else {
- self.assert_tile_is_clear(table)?;
- self.aquire_item(item)?;
- self.interact_with(table, 0.)?;
+ self.wait(1., "waiting for orders")?;
}
}
Ok(())
diff --git a/server/bot/src/algos/waiter.rs b/server/bot/src/algos/waiter.rs
index b1400ed2..a845fd1f 100644
--- a/server/bot/src/algos/waiter.rs
+++ b/server/bot/src/algos/waiter.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -18,8 +18,7 @@
use super::simple::State;
use crate::{BotAlgo, PacketSink, algos::simple::Context, step::StepState};
use hurrycurry_game_core::Game;
-use hurrycurry_protocol::{ItemIndex, PlayerID};
-use log::debug;
+use hurrycurry_protocol::PlayerID;
pub struct Waiter {
step: StepState,
@@ -31,7 +30,7 @@ type LogicRes<Out = ()> = Result<Out, ()>;
impl Waiter {
pub fn new(me: PlayerID) -> Self {
Self {
- step: StepState::new_idle(me),
+ step: StepState::new_idle(me, "idle".to_string()),
me,
}
}
@@ -46,7 +45,8 @@ impl BotAlgo for Waiter {
game,
me: self.me,
state: self,
- recursion_abort: 0,
+ complexity_abort: 0,
+ stack: Vec::new(),
}
.update()
.ok();
@@ -57,39 +57,41 @@ impl State for Waiter {
fn queue_step(&mut self, step: StepState) {
self.step = step;
}
- fn get_empty_tile_priority(&self) -> &'static [&'static str] {
- &["counter-window", "counter"]
- }
}
impl Context<'_, Waiter> {
- fn aquire_item(&mut self, item: ItemIndex) -> LogicRes<bool> {
- debug!("aquire item {:?}", self.game.data.item_names[item.0]);
- if self.is_hand_item(item) {
- return Ok(true);
- }
- if let Some(pos) = self.find_item_on_map(item) {
- self.assert_hand_is_clear()?;
- self.interact_with(pos, 0.)?;
- return Ok(true);
- }
- Ok(false)
- }
fn update(&mut self) -> LogicRes {
- if let Some(pos) = self.find_occupied_table_or_floor() {
- self.assert_tile_is_clear(pos)?;
- }
let mut dems = self.find_demands();
dems.sort_by_key(|(_, _, x)| (*x * 1000.) as i32);
+ if dems.is_empty() {
+ self.wait(0.5, "no orders")?;
+ }
for (item, table, _) in dems {
if self.game.data.item_name(item) == "unknown-order" {
- self.interact_with(table, 0.)?;
+ self.interact_with(table, 0., "accept order")?;
+ }
+ if self.is_hand_item(item) {
+ if self.is_tile_occupied(table) {
+ self.dispose_hand(true, "return order (table clear)")?;
+ }
+ self.interact_with(table, 1., "deliver order")?;
}
- if self.aquire_item(item)? {
- self.interact_with(table, 0.)?;
+ if let Some(pos) = self.find_item_on_map(true, item) {
+ if self.is_hand_occupied() {
+ self.dispose_hand(true, "return dirty")?;
+ }
+ if self.is_tile_occupied(table) {
+ self.interact_with(table, 0., "clear table (order)")?;
+ }
+ self.interact_with(pos, 0., "pick up order")?;
}
}
- self.assert_hand_is_clear()?;
+ if self.is_hand_occupied() {
+ self.dispose_hand(true, "return dirty")?;
+ }
+ if let Some(pos) = self.find_occupied_table() {
+ self.interact_with(pos, 0., "clear table (idle)")?;
+ }
Ok(())
}
}
diff --git a/server/bot/src/lib.rs b/server/bot/src/lib.rs
index 31f2132a..fa3c6697 100644
--- a/server/bot/src/lib.rs
+++ b/server/bot/src/lib.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -25,7 +25,7 @@ use hurrycurry_protocol::PacketS;
#[cfg(feature = "debug_events")]
use hurrycurry_protocol::{PlayerID, glam::Vec3};
use rand::{Rng, RngExt};
-use std::{collections::VecDeque};
+use std::collections::VecDeque;
pub struct PacketSink<'a> {
buf: &'a mut VecDeque<PacketS>,
@@ -61,10 +61,5 @@ fn random_usize<T: Rng>(rng: &mut T) -> usize {
#[cfg(feature = "debug_events")]
fn debug_player_color(d: PlayerID) -> Vec3 {
- use std::f32::consts::TAU;
- Vec3::new(
- (d.0 as f32 + TAU / 3. * 0.).sin(),
- (d.0 as f32 + TAU / 3. * 1.).sin(),
- (d.0 as f32 + TAU / 3. * 2.).sin(),
- )
+ Vec3::new(0.5, 0.3, d.0 as f32 * 57.29577)
}
diff --git a/server/bot/src/main.rs b/server/bot/src/main.rs
index fff25d63..a4c7b7b9 100644
--- a/server/bot/src/main.rs
+++ b/server/bot/src/main.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/bot/src/pathfinding.rs b/server/bot/src/pathfinding.rs
index f9f213fa..ab40eb35 100644
--- a/server/bot/src/pathfinding.rs
+++ b/server/bot/src/pathfinding.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -21,12 +21,12 @@ use hurrycurry_game_core::Game;
use hurrycurry_protocol::{DebugEvent, DebugEventDisplay};
use hurrycurry_protocol::{
PacketS, PlayerID,
- glam::{IVec2, Vec2},
+ glam::{IVec2, Vec2, ivec2},
};
use log::{debug, trace};
use std::{
cmp::Ordering,
- collections::{BinaryHeap, HashMap},
+ collections::{BinaryHeap, HashMap, HashSet},
time::Instant,
};
@@ -71,7 +71,7 @@ impl Path {
if let Some(next) = self.segments.last().copied() {
trace!("next {next}");
self.seg_time += dt;
- if next.distance(position) < if self.segments.len() == 1 { 0.1 } else { 0.6 } {
+ if next.distance(position) < if self.segments.len() == 1 { 0.1 } else { 0.3 } {
self.seg_time = 0.;
self.segments.pop();
}
@@ -97,6 +97,7 @@ impl Path {
key: format!("path-{id}"),
color: debug_player_color(id),
display: DebugEventDisplay::Path {
+ player: id,
points: self.segments.clone(),
},
timeout: 0.1,
@@ -137,7 +138,6 @@ pub fn find_path(game: &Game, from: IVec2, to: IVec2) -> Option<Path> {
debug!("planning route from {from} to {to}");
let start = Instant::now();
- let chair = game.data.get_tile_by_name("chair");
let mut visited = HashMap::new();
let mut open = BinaryHeap::new();
open.push(Open {
@@ -164,13 +164,10 @@ pub fn find_path(game: &Game, from: IVec2, to: IVec2) -> Option<Path> {
for dir in [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] {
let next = pos + dir;
if game.walkable.contains(&next) {
- let penalty = if let Some(chair) = chair
- && let Some(set) = game.tile_index.get(&chair)
- && set.contains(&next)
- {
- 8
- } else {
+ let penalty = if game.walkable_preferred.contains(&next) {
0
+ } else {
+ 8
};
open.push(Open {
heuristic: -(distance + next.distance_squared(to).isqrt()),
@@ -193,6 +190,8 @@ pub fn find_path(game: &Game, from: IVec2, to: IVec2) -> Option<Path> {
c = cn
}
+ optimize_path(&game.walkable_preferred, &mut segments);
+
debug!(
"done in {:?} (distance={})",
start.elapsed(),
@@ -206,6 +205,35 @@ pub fn find_path(game: &Game, from: IVec2, to: IVec2) -> Option<Path> {
})
}
+fn optimize_path(walkable: &HashSet<IVec2>, segments: &mut Vec<Vec2>) {
+ while segments.len() >= 3 {
+ let mut changed = false;
+ for i in 0..segments.len() - 2 {
+ let a = segments[i + 0];
+ let c = segments[i + 2];
+ let b = &mut segments[i + 1];
+ let min = a.min(c).as_ivec2();
+ let max = a.max(c).as_ivec2();
+ let mut clear = true;
+ for x in min.x..=max.x {
+ for y in min.y..=max.y {
+ clear &= walkable.contains(&ivec2(x, y));
+ }
+ }
+ if clear {
+ let new_b = (a + c) * 0.5;
+ if b.distance(new_b) > 0.1 {
+ *b = new_b;
+ changed = true;
+ }
+ }
+ }
+ if !changed {
+ break;
+ }
+ }
+}
+
#[derive(Debug, Clone)]
pub struct HoldLocation {
target: Vec2,
diff --git a/server/bot/src/step.rs b/server/bot/src/step.rs
index 75e5d427..4c256ad8 100644
--- a/server/bot/src/step.rs
+++ b/server/bot/src/step.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -27,6 +27,7 @@ use hurrycurry_protocol::{
};
pub struct StepState {
+ label: String,
path: Path,
interact_position: IVec2,
item_expected: Option<ItemIndex>,
@@ -39,10 +40,10 @@ pub struct StepState {
}
impl StepState {
- pub fn new_idle(me: PlayerID) -> Self {
- Self::new_wait(me, 0.)
+ pub fn new_idle(me: PlayerID, label: String) -> Self {
+ Self::new_wait(me, 0., label)
}
- pub fn new_wait(me: PlayerID, timer: f32) -> Self {
+ pub fn new_wait(me: PlayerID, timer: f32, label: String) -> Self {
Self {
me,
path: Path::EMPTY,
@@ -53,6 +54,7 @@ impl StepState {
interact_position: IVec2::ZERO,
item_expected: None,
item_current: None,
+ label,
}
}
pub fn new_segment(
@@ -61,6 +63,7 @@ impl StepState {
hand: Hand,
pos: IVec2,
interact: f32,
+ label: String,
) -> Option<Self> {
let own_pos = game.players.get(&me)?.movement.position.as_ivec2();
let path = find_path_to_neighbour(game, own_pos, pos)?;
@@ -69,6 +72,7 @@ impl StepState {
.get(&pos)
.and_then(|t| t.item.as_ref().map(|i| i.kind));
Some(Self {
+ label,
me,
path,
hand,
@@ -91,18 +95,22 @@ impl StepState {
#[cfg(feature = "debug_events")]
{
use crate::debug_player_color;
- use hurrycurry_protocol::{DebugEvent, DebugEventDisplay, glam::Vec2};
+ use hurrycurry_protocol::{DebugEvent, DebugEventDisplay};
out.push(PacketS::Debug(DebugEvent {
- key: format!("step-{}", self.me),
+ key: format!("step-label-{}", self.me),
color: debug_player_color(self.me),
display: DebugEventDisplay::Label {
- pos: self.interact_position.as_vec2() + Vec2::splat(0.5),
- text: format!(
- "D={} E={} T={:.01}",
- self.path.is_done() as u8,
- (self.item_expected == self.item_current) as u8,
- self.wait_timer
- ),
+ pos: ItemLocation::Player(self.me, Hand(0)),
+ text: self.label.to_string(),
+ },
+ timeout: 1.,
+ }));
+ out.push(PacketS::Debug(DebugEvent {
+ key: format!("step-target-{}", self.me),
+ color: debug_player_color(self.me),
+ display: DebugEventDisplay::Label {
+ pos: ItemLocation::Tile(self.interact_position),
+ text: format!("D={} T={:.01}", self.path.is_done() as u8, self.wait_timer),
},
timeout: 1.,
}));
diff --git a/server/data/src/book/diagram_layout.rs b/server/data/src/book/diagram_layout.rs
index e2a18cb1..6a93e70c 100644
--- a/server/data/src/book/diagram_layout.rs
+++ b/server/data/src/book/diagram_layout.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/data/src/book/mod.rs b/server/data/src/book/mod.rs
index 990d979c..f1fa1a53 100644
--- a/server/data/src/book/mod.rs
+++ b/server/data/src/book/mod.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -19,6 +19,8 @@
pub mod diagram_layout;
pub mod recipe_diagram;
+use std::cmp::Reverse;
+
use crate::{
PrivateGamedata,
book::{diagram_layout::diagram_layout, recipe_diagram::recipe_diagram},
@@ -39,16 +41,24 @@ pub fn book(data: &Gamedata, serverdata: &PrivateGamedata) -> Result<Book> {
});
let mut toc = Vec::new();
- for (name, repr) in serverdata.recipe_groups.clone() {
- let repr = repr
+ for (name, rg) in serverdata.recipe_groups.clone() {
+ let repr_items = rg
+ .repr
.into_iter()
- .filter(|&i| data.demands.iter().any(|d| d.input == i))
+ .map(|i| data.demands[i.0].input)
.collect::<Vec<_>>();
- if repr.is_empty() {
+ if repr_items.is_empty() {
continue;
}
- let mut diagram = recipe_diagram(data, serverdata, &repr)?;
+ let mut points = Vec::new();
+ for d in rg.demands {
+ let demand = &data.demands[d.0];
+ points.push((demand.input, demand.points));
+ }
+ points.sort_by_key(|(_, p)| Reverse(*p));
+
+ let mut diagram = recipe_diagram(data, serverdata, &repr_items)?;
diagram_layout(&mut diagram).context("during layouting")?;
let title = Message::Translation {
id: format!("b.{name}.title"),
@@ -62,6 +72,7 @@ pub fn book(data: &Gamedata, serverdata: &PrivateGamedata) -> Result<Book> {
params: vec![],
},
diagram,
+ points,
});
}
diff --git a/server/data/src/book/recipe_diagram.rs b/server/data/src/book/recipe_diagram.rs
index bef90946..a5a5db44 100644
--- a/server/data/src/book/recipe_diagram.rs
+++ b/server/data/src/book/recipe_diagram.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/data/src/entities.rs b/server/data/src/entities.rs
index 135e4b78..28ea7c86 100644
--- a/server/data/src/entities.rs
+++ b/server/data/src/entities.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -98,6 +98,9 @@ pub enum EntityDecl {
#[serde(default)]
item_indices: Vec<ItemIndex>,
},
+ Bot {
+ name: String,
+ },
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, ValueEnum)]
diff --git a/server/data/src/filter_demands.rs b/server/data/src/filter_demands.rs
index 4eb2d597..6e230530 100644
--- a/server/data/src/filter_demands.rs
+++ b/server/data/src/filter_demands.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -15,14 +15,16 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use hurrycurry_protocol::{Demand, ItemIndex, Recipe, TileIndex, glam::IVec2};
+use crate::recipes::RecipeGroup;
+use hurrycurry_protocol::{Demand, DemandIndex, ItemIndex, Recipe, TileIndex, glam::IVec2};
use log::debug;
-use std::collections::{HashMap, HashSet};
+use std::collections::{BTreeMap, HashMap, HashSet};
pub fn filter_demands_and_recipes(
initial_map: &HashMap<IVec2, (Vec<TileIndex>, Option<ItemIndex>)>,
demands: &mut Vec<Demand>,
recipes: &mut Vec<Recipe>,
+ recipe_groups: &mut BTreeMap<String, RecipeGroup>,
) {
debug!(
"running demand filter with {} recipes and {} demands",
@@ -70,9 +72,9 @@ pub fn filter_demands_and_recipes(
};
let base_cost = match r {
- Recipe::Passive { speed, .. } => 2. + (1. / speed) * 0.1,
- Recipe::Active { speed, .. } => 2. + (1. / speed),
- Recipe::Instant { .. } => 1.,
+ Recipe::Passive { speed, .. } => 1. + (1. / speed) * 0.15,
+ Recipe::Active { speed, .. } => 0.5 + (1. / speed),
+ Recipe::Instant { .. } => 0.5,
};
let output_cost = (ingred_cost + base_cost) / output_count as f32;
@@ -91,14 +93,32 @@ pub fn filter_demands_and_recipes(
r.inputs().all(|o| producable.contains_key(&o))
&& r.outputs().all(|o| producable.contains_key(&o))
});
- demands.retain_mut(|d| {
+
+ let mut demand_map = HashMap::new();
+ let mut demands_out = Vec::new();
+ for (oi, d) in demands.into_iter().enumerate() {
if let Some(&cost) = producable.get(&d.input) {
- d.points = cost as i64;
- true
- } else {
- false
+ d.points = cost as i64 + 3;
+ demand_map.insert(DemandIndex(oi), DemandIndex(demands_out.len()));
+ demands_out.push(d.clone());
}
- });
+ }
+ *demands = demands_out;
+ for rg in recipe_groups.values_mut() {
+ rg.demands = rg
+ .demands
+ .clone()
+ .into_iter()
+ .filter_map(|i| demand_map.get(&i).copied())
+ .collect();
+ rg.repr = rg
+ .repr
+ .clone()
+ .into_iter()
+ .filter_map(|i| demand_map.get(&i).copied())
+ .collect();
+ }
+
debug!(
"{} applicable recipes and {} demands selected",
recipes.len(),
diff --git a/server/data/src/lib.rs b/server/data/src/lib.rs
index 06233d1e..fed41b9f 100644
--- a/server/data/src/lib.rs
+++ b/server/data/src/lib.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -25,29 +25,28 @@ pub mod registry;
use crate::{
book::book,
entities::{ButtonAction, EntityDecl},
- recipes::{RecipeDecl, load_recipes},
+ recipes::{RecipeDecl, RecipeGroup, load_recipes},
registry::{ItemTileRegistry, filter_unused_tiles_and_items},
};
use anyhow::{Context, Result, anyhow, bail};
use clap::Parser;
use filter_demands::filter_demands_and_recipes;
use hurrycurry_protocol::{
- Gamedata, GamedataFlags, ItemIndex, MapMetadata, Recipe, TileIndex,
+ GameConfig, Gamedata, GamedataFlags, ItemIndex, MapMetadata, Recipe, TileIndex,
book::Book,
glam::{IVec2, Vec2},
};
-use log::debug;
+use log::{debug, info};
use serde::Deserialize;
use std::{
- collections::{BTreeMap, BTreeSet, HashMap, HashSet},
+ collections::{BTreeMap, HashMap, HashSet},
fs::read_to_string,
path::Path,
- time::Duration,
};
#[derive(Debug, Deserialize)]
pub struct DataIndex {
- pub maps: HashMap<String, MapMetadata>,
+ pub maps: HashSet<String>,
pub recipes: HashSet<String>,
}
@@ -61,8 +60,11 @@ pub struct MapDecl {
#[serde(default)] hand_count: Option<usize>,
#[serde(default)] entities: Vec<EntityDecl>,
#[serde(default)] score_baseline: i64,
- #[serde(default)] default_timer: Option<u64>,
+ #[serde(default)] timer: Option<f32>,
#[serde(default)] flags: GamedataFlags,
+ name: String,
+ #[serde(default)] difficulty: i32,
+ #[serde(default)] players: usize,
}
fn default_recipes() -> String {
"default".to_string()
@@ -92,24 +94,38 @@ pub struct PrivateGamedata {
pub chef_spawn: Vec2,
pub customer_spawn: Option<Vec2>,
pub score_baseline: i64,
- pub default_timer: Option<Duration>,
+ pub timer: f32,
pub book: Book,
pub entity_decls: Vec<EntityDecl>,
- pub recipe_groups: BTreeMap<String, BTreeSet<ItemIndex>>,
+ pub recipe_groups: BTreeMap<String, RecipeGroup>,
}
-pub fn map_list(data_path: &Path) -> Result<Vec<(String, MapMetadata)>> {
+pub fn map_list(data_path: &Path) -> Result<Vec<String>> {
let index =
read_to_string(data_path.join("index.yaml")).context("Failed reading data index")?;
let index = serde_yaml_ng::from_str::<DataIndex>(&index)?;
- Ok(map_listing(&index))
+ Ok(index.maps.into_iter().collect())
+}
+pub fn load_map_metadata(data_path: &Path) -> Result<Vec<MapMetadata>> {
+ let mut maps = Vec::new();
+ info!("Loading map metadata...");
+ for name in map_list(&data_path)? {
+ let (d, _) = build_gamedata(&data_path, &GameConfig::new_map(&name), false)
+ .context(anyhow!("load map {name:?} "))?;
+ maps.push(d.metadata);
+ }
+ maps.retain(|m| m.players > 0);
+ maps.sort_unstable_by_key(|m| m.difficulty);
+ maps.sort_by_key(|m| m.players);
+ Ok(maps)
}
pub fn build_gamedata(
data_path: &Path,
- map_name: &str,
+ config: &GameConfig,
generate_book: bool,
) -> Result<(Gamedata, PrivateGamedata)> {
+ let map_name = config.map.clone();
debug!("Preparing gamedata for {map_name}");
// Load index
@@ -140,7 +156,7 @@ pub fn build_gamedata(
let palette = load_palette(data_path, &map_in)?;
let reg = ItemTileRegistry::default();
- let (mut recipes, mut demands, recipe_groups) = load_recipes(recipes_in, &reg)?;
+ let (mut recipes, mut demands, mut recipe_groups) = load_recipes(recipes_in, &reg)?;
let mut entities = Vec::new();
let mut chef_spawn = None;
@@ -232,19 +248,29 @@ pub fn build_gamedata(
}
entities.push(e);
}
+
+ for name in config.bots.clone() {
+ entities.push(EntityDecl::Bot { name });
+ }
+
debug!("{} entities created", entities.len());
- filter_demands_and_recipes(&initial_map, &mut demands, &mut recipes);
+ filter_demands_and_recipes(&initial_map, &mut demands, &mut recipes, &mut recipe_groups);
let (items, tiles) = reg.finish();
- let default_timer = if map_name.ends_with("lobby") {
- None
- } else {
- Some(Duration::from_secs(map_in.default_timer.unwrap_or(420)))
- };
-
+ let hand_count = config.hand_count.or(map_in.hand_count).unwrap_or(1);
let mut data = Gamedata {
- current_map: map_name.to_string(),
+ metadata: MapMetadata {
+ name: map_name.to_string(),
+ display_name: map_in.name,
+ players: map_in.players,
+ difficulty: map_in.difficulty,
+ hand_count: hand_count,
+ demand_items: demands
+ .iter()
+ .map(|d| items[d.input.0].to_string())
+ .collect::<Vec<_>>(),
+ },
tile_collide: tiles_flagged(&tile_flags, &tiles, 'c'),
tile_placeable_items: tile_placeable_items(
&initial_map,
@@ -252,6 +278,7 @@ pub fn build_gamedata(
tiles_flagged(&tile_flags, &tiles, 'x'),
),
tile_placeable_any: tiles_flagged(&tile_flags, &tiles, 'a'),
+ tile_avoid_pathfinding: tiles_flagged(&tile_flags, &tiles, 'W'),
tile_interactable_empty: tiles_flagged(&tile_flags, &tiles, 'e')
.union(&interactable_empty_extra)
.copied()
@@ -264,13 +291,13 @@ pub fn build_gamedata(
item_names: items,
demands,
tile_names: tiles,
- hand_count: map_in.hand_count.unwrap_or(1),
+ hand_count,
};
let mut serverdata = PrivateGamedata {
initial_map,
chef_spawn,
customer_spawn,
- default_timer,
+ timer: config.timer.or(map_in.timer).unwrap_or(420.),
book: Book::default(),
score_baseline: map_in.score_baseline,
entity_decls: entities,
@@ -314,18 +341,6 @@ fn tile_interactable_empty_bc_recipe(recipes: &[Recipe]) -> HashSet<TileIndex> {
.collect()
}
-fn map_listing(index: &DataIndex) -> Vec<(String, MapMetadata)> {
- let mut maps = index
- .maps
- .iter()
- .filter(|(_, v)| v.players > 0)
- .map(|(k, v)| (k.to_owned(), v.to_owned()))
- .collect::<Vec<(String, MapMetadata)>>();
- maps.sort_unstable_by_key(|(_, m)| m.difficulty);
- maps.sort_by_key(|(_, m)| m.players);
- maps
-}
-
fn load_palette(data_path: &Path, map_in: &MapDecl) -> Result<HashMap<char, TileArgs>> {
// Load palette
let palettes =
diff --git a/server/data/src/recipes.rs b/server/data/src/recipes.rs
index e4d68a7d..1cfcda44 100644
--- a/server/data/src/recipes.rs
+++ b/server/data/src/recipes.rs
@@ -18,7 +18,7 @@
use crate::registry::ItemTileRegistry;
use anyhow::{Result, anyhow};
-use hurrycurry_protocol::{Demand, ItemIndex, Recipe};
+use hurrycurry_protocol::{Demand, DemandIndex, Recipe};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
@@ -48,18 +48,20 @@ pub enum RecipeDeclAction {
Demand,
}
+#[derive(Debug, Clone, Default)]
+pub struct RecipeGroup {
+ pub repr: BTreeSet<DemandIndex>,
+ pub demands: BTreeSet<DemandIndex>,
+}
+
#[allow(clippy::type_complexity)]
pub(crate) fn load_recipes(
recipes_in: Vec<RecipeDecl>,
reg: &ItemTileRegistry,
-) -> Result<(
- Vec<Recipe>,
- Vec<Demand>,
- BTreeMap<String, BTreeSet<ItemIndex>>,
-)> {
+) -> Result<(Vec<Recipe>, Vec<Demand>, BTreeMap<String, RecipeGroup>)> {
let mut recipes = Vec::new();
let mut demands = Vec::new();
- let mut recipe_groups = BTreeMap::<String, BTreeSet<ItemIndex>>::new();
+ let mut recipe_groups = BTreeMap::<String, RecipeGroup>::new();
for mut r in recipes_in {
#[cfg(feature = "fast_recipes")]
@@ -76,10 +78,13 @@ pub(crate) fn load_recipes(
let mut inputs = r.inputs.into_iter().map(|i| reg.register_item(i));
let mut outputs = r.outputs.into_iter().map(|o| reg.register_item(o));
let tile = r.tile.map(|t| reg.register_tile(t));
- if let Some(g) = r.group
- && !r.group_hidden
- {
- recipe_groups.entry(g).or_default().extend(inputs.clone());
+ if let Some(g) = r.group {
+ let group = recipe_groups.entry(g).or_default();
+ let d = DemandIndex(demands.len());
+ group.demands.insert(d);
+ if !r.group_hidden {
+ group.repr.insert(d);
+ }
}
match r.action {
RecipeDeclAction::Never => {}
diff --git a/server/data/src/registry.rs b/server/data/src/registry.rs
index 3288c037..508d86a5 100644
--- a/server/data/src/registry.rs
+++ b/server/data/src/registry.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -72,7 +72,7 @@ pub(crate) fn filter_unused_tiles_and_items(data: &mut Gamedata, serverdata: &mu
used_items.extend(demand.output);
}
for rg in serverdata.recipe_groups.values() {
- used_items.extend(rg);
+ used_items.extend(rg.demands.iter().map(|i| data.demands[i.0].input));
}
for (tile, item) in serverdata.initial_map.values() {
used_tiles.extend(tile);
@@ -189,9 +189,6 @@ pub(crate) fn filter_unused_tiles_and_items(data: &mut Gamedata, serverdata: &mu
*output = item_map[output];
}
}
- for rg in serverdata.recipe_groups.values_mut() {
- *rg = rg.clone().into_iter().map(|e| item_map[&e]).collect();
- }
for (tiles, item) in serverdata.initial_map.values_mut() {
for tile in tiles {
*tile = tile_map[tile];
@@ -245,6 +242,12 @@ pub(crate) fn filter_unused_tiles_and_items(data: &mut Gamedata, serverdata: &mu
.into_iter()
.map(|e| tile_map[&e])
.collect();
+ data.tile_avoid_pathfinding = data
+ .tile_avoid_pathfinding
+ .clone()
+ .into_iter()
+ .map(|e| tile_map[&e])
+ .collect();
data.tile_placeable_items = data
.tile_placeable_items
.clone()
diff --git a/server/discover/src/main.rs b/server/discover/src/main.rs
index 8b86af74..7cd49c5b 100644
--- a/server/discover/src/main.rs
+++ b/server/discover/src/main.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/editor/src/main.rs b/server/editor/src/main.rs
index 8ed66e11..d7165363 100644
--- a/server/editor/src/main.rs
+++ b/server/editor/src/main.rs
@@ -5,8 +5,8 @@ use clap::Parser;
use futures_util::{SinkExt, StreamExt};
use hurrycurry_game_core::network::sync::Network;
use hurrycurry_protocol::{
- Character, Gamedata, Hand, Message, PacketC, PacketS, PlayerClass, PlayerID, TileIndex,
- VERSION,
+ Character, Gamedata, Hand, MapMetadata, Message, PacketC, PacketS, PlayerClass, PlayerID,
+ TileIndex, VERSION,
glam::{IVec2, Vec2, ivec2},
movement::MovementBase,
};
@@ -144,7 +144,11 @@ async fn handle_conn(
tile_collide: (0..TILES.len()).map(TileIndex).collect(),
tile_placeable_items: BTreeMap::new(),
tile_names: TILES.iter().map(|(name, _, _)| name.to_string()).collect(),
- current_map: "editor".to_owned(),
+ metadata: MapMetadata {
+ name: "editor".to_string(),
+ display_name: "Editor".to_string(),
+ ..Default::default()
+ },
..Default::default()
})));
state.out.push(PacketC::SetIngame {
@@ -276,7 +280,8 @@ impl State {
self.movement.position = pos.unwrap_or(self.movement.position);
self.movement.input(dir, boost);
self.movement.update(&self.walkable, dt.as_secs_f32());
- self.out.push(self.movement.movement_packet_c(player));
+ self.out
+ .push(self.movement.movement_packet_c(player, false));
}
PacketS::Communicate {
message: Some(Message::Text(t)),
@@ -335,9 +340,7 @@ impl State {
rot: 0.,
dir: Vec2::X,
boost: false,
- });
- self.out.push(PacketC::MovementSync {
- player: PlayerID(0),
+ sync: true,
});
}
diff --git a/server/game-core/src/gamedata_index.rs b/server/game-core/src/gamedata_index.rs
index d4bbcce1..499ea643 100644
--- a/server/game-core/src/gamedata_index.rs
+++ b/server/game-core/src/gamedata_index.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/game-core/src/interaction.rs b/server/game-core/src/interaction.rs
index 0b02b341..fd47a7f5 100644
--- a/server/game-core/src/interaction.rs
+++ b/server/game-core/src/interaction.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -17,7 +17,7 @@
*/
use crate::{Game, Involvement, Item};
use hurrycurry_locale::{TrError, tre};
-use hurrycurry_protocol::{ItemLocation, PacketC, Recipe};
+use hurrycurry_protocol::{Effect, ItemLocation, PacketC, Recipe};
use log::info;
use std::collections::{BTreeSet, VecDeque};
@@ -224,7 +224,13 @@ impl Game {
}
self.score.points += pd;
self.score.instant_recipes += 1;
- self.events.push_back(PacketC::Score(self.score.clone()));
+ if *pd != 0 {
+ self.events.push_back(PacketC::Effect {
+ effect: Effect::Points { amount: *pd },
+ location: this_loc,
+ });
+ self.events.push_back(PacketC::Score(self.score.clone()));
+ }
produce_events(
&mut self.events,
this_had_item,
@@ -242,14 +248,9 @@ impl Game {
}
let can_place = automated
- || this_tile_parts.iter().any(|tile| {
- other_slot.as_ref().is_some_and(|other| {
- self.data
- .tile_placeable_items
- .get(tile)
- .is_none_or(|pl| pl.contains(&other.kind))
- })
- });
+ || other_slot
+ .as_ref()
+ .is_some_and(|other| self.data.can_place_item(&this_tile_parts, other.kind));
if can_place
&& this_slot.is_none()
diff --git a/server/game-core/src/lib.rs b/server/game-core/src/lib.rs
index df48c323..5e3a98ee 100644
--- a/server/game-core/src/lib.rs
+++ b/server/game-core/src/lib.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -22,8 +22,9 @@ pub mod spatial_index;
use crate::gamedata_index::GamedataIndex;
use hurrycurry_protocol::{
- Character, Gamedata, Hand, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC,
- PlayerClass, PlayerID, RecipeIndex, Score, TileIndex, glam::IVec2, movement::MovementBase,
+ Character, GameConfig, Gamedata, Hand, ItemIndex, ItemLocation, Message, MessageTimeout,
+ PacketC, PlayerClass, PlayerID, RecipeIndex, Score, TileIndex, glam::IVec2,
+ movement::MovementBase,
};
use spatial_index::SpatialIndex;
use std::{
@@ -66,6 +67,7 @@ pub struct Player {
#[derive(Default)]
pub struct Game {
+ pub config: GameConfig,
pub data: Arc<Gamedata>,
pub data_index: GamedataIndex,
@@ -78,6 +80,7 @@ pub struct Game {
pub players_spatial_index: SpatialIndex<PlayerID>,
pub walkable: HashSet<IVec2>,
+ pub walkable_preferred: HashSet<IVec2>, // bots try to avoid leaving these
pub tile_index: HashMap<TileIndex, HashSet<IVec2>>,
pub item_locations_index: HashSet<ItemLocation>,
@@ -122,6 +125,7 @@ impl Game {
rot,
boost,
dir,
+ ..
} => {
if let Some(p) = self.players.get_mut(&player) {
p.movement.input(dir, boost);
@@ -197,25 +201,20 @@ impl Game {
pub fn set_tile(&mut self, pos: IVec2, parts: Vec<TileIndex>) {
self.tiles.remove(&pos);
- self.walkable.remove(&pos);
if let Some(prev) = self.tiles.get(&pos) {
for &part in &prev.parts {
self.tile_index.entry(part).or_default().remove(&pos);
}
}
- if self.data.walkable(&parts) {
- self.walkable.insert(pos);
- }
+ self.update_tile_changed(pos, &parts);
for &part in &parts {
self.tile_index.entry(part).or_default().insert(pos);
}
if !parts.is_empty() {
self.tiles.insert(pos, Tile { parts, item: None });
}
- self.map_changes.insert(pos);
}
pub fn add_tile_part(&mut self, pos: IVec2, part: TileIndex) {
- self.map_changes.insert(pos);
let tile = self.tiles.entry(pos).or_insert(Tile {
item: None,
parts: vec![],
@@ -224,11 +223,9 @@ impl Game {
return;
}
tile.parts.push(part);
+ let parts = tile.parts.clone();
+ self.update_tile_changed(pos, &parts);
self.tile_index.entry(part).or_default().insert(pos);
- self.walkable.remove(&pos);
- if self.data.walkable(&tile.parts) {
- self.walkable.insert(pos);
- }
}
pub fn remove_tile_part(&mut self, pos: IVec2, part: TileIndex) {
let Some(tile) = self.tiles.get_mut(&pos) else {
@@ -239,18 +236,31 @@ impl Game {
if tile.parts.len() == lb {
return;
}
- self.map_changes.insert(pos);
+ let parts = tile.parts.clone();
+ self.update_tile_changed(pos, &parts);
self.tile_index.entry(part).or_default().remove(&pos);
+ }
+ fn update_tile_changed(&mut self, pos: IVec2, parts: &[TileIndex]) {
+ self.map_changes.insert(pos);
self.walkable.remove(&pos);
- if self.data.walkable(&tile.parts) {
+ self.walkable_preferred.remove(&pos);
+ if self.data.walkable(&parts) {
self.walkable.insert(pos);
}
+ if self.data.walkable_preferred(&parts) {
+ self.walkable_preferred.insert(pos);
+ }
}
pub fn set_item(&mut self, pos: IVec2, kind: Option<ItemIndex>) {
let Some(tile) = self.tiles.get_mut(&pos) else {
return;
};
+ if kind.is_some() {
+ self.item_locations_index.insert(ItemLocation::Tile(pos));
+ } else {
+ self.item_locations_index.remove(&ItemLocation::Tile(pos));
+ }
tile.item = kind.map(|kind| Item { kind, active: None });
self.events.push_back(PacketC::SetItem {
location: ItemLocation::Tile(pos),
@@ -259,7 +269,7 @@ impl Game {
}
pub fn tick(&mut self, dt: f32) {
- self.score.time_remaining -= dt as f64;
+ self.score.time_remaining -= dt;
self.score.time_remaining -= self.score.time_remaining.max(0.);
for (&pid, player) in &mut self.players {
@@ -423,6 +433,7 @@ impl Game {
self.score = Score::default();
self.environment_effects.clear();
self.walkable.clear();
+ self.walkable_preferred.clear();
self.tile_index.clear();
self.item_locations_index.clear();
}
@@ -439,11 +450,7 @@ impl Game {
if let Some(tile) = self.tiles.get_mut(&pos)
&& tile.item.is_none()
{
- self.events.push_back(PacketC::SetItem {
- location: ItemLocation::Tile(pos),
- item: Some(item.kind),
- });
- tile.item = Some(item);
+ self.set_item(pos, Some(item.kind));
}
}
}
diff --git a/server/game-core/src/network/mod.rs b/server/game-core/src/network/mod.rs
index 45963567..b7b82d88 100644
--- a/server/game-core/src/network/mod.rs
+++ b/server/game-core/src/network/mod.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/game-core/src/network/sync.rs b/server/game-core/src/network/sync.rs
index 3d36881e..44cc9050 100644
--- a/server/game-core/src/network/sync.rs
+++ b/server/game-core/src/network/sync.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/game-core/src/network/tokio.rs b/server/game-core/src/network/tokio.rs
index 5d7f6b07..4d8375cc 100644
--- a/server/game-core/src/network/tokio.rs
+++ b/server/game-core/src/network/tokio.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/game-core/src/spatial_index.rs b/server/game-core/src/spatial_index.rs
index 8dd0cc22..04b30614 100644
--- a/server/game-core/src/spatial_index.rs
+++ b/server/game-core/src/spatial_index.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/locale-export/src/godot_csv.rs b/server/locale-export/src/godot_csv.rs
new file mode 100644
index 00000000..a2da301d
--- /dev/null
+++ b/server/locale-export/src/godot_csv.rs
@@ -0,0 +1,70 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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 crate::load_ini;
+use anyhow::{Result, anyhow};
+use std::{collections::BTreeMap, fmt::Write as W2, fs::File, io::Write, path::Path};
+
+pub fn export_godot_csv(input_dir: &Path, output: &Path) -> Result<()> {
+ let translations = input_dir
+ .read_dir()?
+ .flat_map(|e| {
+ e.map_err(|e| anyhow!("{e}"))
+ .and_then(|e| {
+ if e.file_name().to_string_lossy().ends_with(".ini") {
+ Ok(Some((
+ e.path()
+ .file_stem()
+ .ok_or(anyhow!("empty filename"))?
+ .to_string_lossy()
+ .to_string(),
+ load_ini(&e.path())?,
+ )))
+ } else {
+ Ok(None)
+ }
+ })
+ .transpose()
+ })
+ .collect::<Result<BTreeMap<_, _>>>()?;
+
+ let langs = translations.keys().cloned().collect::<Vec<String>>();
+ let mut tr_tr = BTreeMap::<String, BTreeMap<String, String>>::new();
+ for (k, v) in translations {
+ for (kk, vv) in v {
+ tr_tr.entry(kk).or_default().insert(k.clone(), vv);
+ }
+ }
+
+ File::create(output)?.write_all(
+ tr_tr
+ .into_iter()
+ .try_fold(format!("id,{}\n", langs.join(",")), |mut a, (k, v)| {
+ writeln!(
+ a,
+ "{k},{}",
+ v.values()
+ .map(serde_json::to_string)
+ .collect::<Result<Vec<_>, serde_json::Error>>()?
+ .join(",")
+ )?;
+ Ok::<_, anyhow::Error>(a)
+ })?
+ .as_bytes(),
+ )?;
+ Ok(())
+}
diff --git a/server/locale-export/src/json_pack.rs b/server/locale-export/src/json_pack.rs
new file mode 100644
index 00000000..8e4eb645
--- /dev/null
+++ b/server/locale-export/src/json_pack.rs
@@ -0,0 +1,46 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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 crate::load_ini;
+use anyhow::Result;
+use serde_json::to_string;
+use std::{
+ collections::BTreeMap,
+ fs::File,
+ io::Write,
+ path::{Path, PathBuf},
+};
+
+pub fn export_json_pack(output: &Path, inputs: &[PathBuf]) -> Result<()> {
+ let mut o = BTreeMap::<String, BTreeMap<String, String>>::new();
+ for f in inputs {
+ let locale = f
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .split_once(".")
+ .unwrap()
+ .0;
+ for (k, v) in load_ini(f)? {
+ o.entry(k).or_default().insert(locale.to_owned(), v);
+ }
+ }
+ File::create(output)?.write_all(to_string(&o)?.as_bytes())?;
+ Ok(())
+}
diff --git a/server/locale-export/src/main.rs b/server/locale-export/src/main.rs
index 56e1a93f..8dd41ddc 100644
--- a/server/locale-export/src/main.rs
+++ b/server/locale-export/src/main.rs
@@ -1,8 +1,33 @@
-use anyhow::{Context, Result, anyhow};
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+pub mod godot_csv;
+pub mod json_pack;
+pub mod po;
+
+use crate::{
+ godot_csv::export_godot_csv,
+ json_pack::export_json_pack,
+ po::{export_po, import_old_po, import_old_pot},
+};
+use anyhow::{Result, anyhow};
use clap::Parser;
use std::{
collections::BTreeMap,
- fmt::Write as W2,
fs::{File, read_to_string},
io::Write,
path::{Path, PathBuf},
@@ -23,6 +48,10 @@ enum Args {
output: PathBuf,
inputs: Vec<PathBuf>,
},
+ ExportJsonPack {
+ output: PathBuf,
+ inputs: Vec<PathBuf>,
+ },
ExportGodotCsv {
input_dir: PathBuf,
output: PathBuf,
@@ -35,6 +64,41 @@ enum Args {
},
}
+fn main() -> Result<()> {
+ let args = Args::parse();
+ match args {
+ Args::ExportJson { inputs, output } => {
+ let ini = export_load(&inputs)?;
+ File::create(output)?.write_all(serde_json::to_string(&ini)?.as_bytes())?;
+ Ok(())
+ }
+ Args::ExportJsonPack { inputs, output } => export_json_pack(&output, &inputs),
+ Args::ExportPo {
+ remap_ids,
+ output,
+ inputs,
+ } => export_po(&inputs, &output, remap_ids),
+ Args::ExportGodotCsv { input_dir, output } => export_godot_csv(&input_dir, &output),
+ Args::ImportOldPo {
+ reference,
+ input,
+ output,
+ } => import_old_po(&reference, &input, &output),
+ Args::ImportOldPot { input, output } => import_old_pot(&input, &output),
+ }
+}
+
+fn load_ini(path: &Path) -> Result<BTreeMap<String, String>> {
+ read_to_string(path)?
+ .lines()
+ .skip(1)
+ .map(|l| {
+ let (k, v) = l.split_once("=").ok_or(anyhow!("'=' missing"))?;
+ Ok::<_, anyhow::Error>((k.trim_end().to_owned(), v.trim_start().replace("%n", "\n")))
+ })
+ .collect()
+}
+
static NATIVE_LANGUAGE_NAMES: &[(&str, &str)] = &[
("en", "English"),
("de", "Deutsch"),
@@ -72,244 +136,3 @@ fn export_load(inputs: &[PathBuf]) -> Result<BTreeMap<String, String>> {
}
Ok(ini)
}
-
-fn main() -> Result<()> {
- let args = Args::parse();
- match args {
- Args::ExportJson { inputs, output } => {
- let ini = export_load(&inputs)?;
- File::create(output)?.write_all(serde_json::to_string(&ini)?.as_bytes())?;
- Ok(())
- }
- Args::ExportPo {
- remap_ids: id_map,
- output,
- inputs,
- } => {
- let ini = export_load(&inputs)?;
- let id_map = id_map.map(|path| load_ini(&path)).transpose()?;
- File::create(output)?.write_all(
- format!(
- r#"
-msgid ""
-msgstr ""
-"Language: {}\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-{}"#,
- inputs
- .first()
- .ok_or(anyhow!("at least one input required"))?
- .file_stem()
- .ok_or(anyhow!("file name empty"))?
- .to_string_lossy(),
- ini.into_iter()
- .try_fold(String::new(), |mut a, (mut key, value)| {
- if let Some(id_map) = &id_map
- && let Some(new_id) = id_map.get(&key)
- {
- key = new_id.to_owned()
- }
- let key = serde_json::to_string(&key)?;
- let value = serde_json::to_string(&value)?;
- writeln!(a, "msgid {key}\nmsgstr {value}\n\n",)?;
- Ok::<_, anyhow::Error>(a)
- })?
- )
- .as_bytes(),
- )?;
- Ok(())
- }
- Args::ExportGodotCsv { input_dir, output } => {
- let translations = input_dir
- .read_dir()?
- .flat_map(|e| {
- e.map_err(|e| anyhow!("{e}"))
- .and_then(|e| {
- if e.file_name().to_string_lossy().ends_with(".ini") {
- Ok(Some((
- e.path()
- .file_stem()
- .ok_or(anyhow!("empty filename"))?
- .to_string_lossy()
- .to_string(),
- load_ini(&e.path())?,
- )))
- } else {
- Ok(None)
- }
- })
- .transpose()
- })
- .collect::<Result<BTreeMap<_, _>>>()?;
-
- let langs = translations.keys().cloned().collect::<Vec<String>>();
- let mut tr_tr = BTreeMap::<String, BTreeMap<String, String>>::new();
- for (k, v) in translations {
- for (kk, vv) in v {
- tr_tr.entry(kk).or_default().insert(k.clone(), vv);
- }
- }
-
- File::create(output)?.write_all(
- tr_tr
- .into_iter()
- .try_fold(format!("id,{}\n", langs.join(",")), |mut a, (k, v)| {
- writeln!(
- a,
- "{k},{}",
- v.values()
- .map(serde_json::to_string)
- .collect::<Result<Vec<_>, serde_json::Error>>()?
- .join(",")
- )?;
- Ok::<_, anyhow::Error>(a)
- })?
- .as_bytes(),
- )?;
- Ok(())
- }
- Args::ImportOldPo {
- reference,
- input,
- output,
- } => {
- let reference = read_to_string(reference)?;
- let input = read_to_string(input)?;
-
- let id_reverse = reference
- .lines()
- .skip(1)
- .map(|l| {
- l.split_once("=")
- .map(|(k, v)| (v, k))
- .ok_or(anyhow!("invalid ini"))
- })
- .collect::<Result<BTreeMap<&str, &str>>>()?;
-
- let mut outmap = BTreeMap::new();
- let mut mode = 0;
- let mut msgid = String::new();
- let mut msgstr = String::new();
- for (i, mut line) in input.lines().enumerate() {
- if line.starts_with("#") {
- continue;
- }
- if line.is_empty() {
- continue;
- }
- if let Some(rest) = line.strip_prefix("msgid ") {
- if !msgid.is_empty() {
- if let Some(id) = id_reverse.get(&msgid.as_str()) {
- outmap.insert(id.to_owned(), msgstr.clone());
- } else {
- eprintln!("warning: message id {msgid:?} is unknown")
- }
- }
- line = rest;
- msgid = String::new();
- mode = 1;
- } else if let Some(rest) = line.strip_prefix("msgstr ") {
- line = rest;
- msgstr = String::new();
- mode = 2;
- } else if line.starts_with("msgctxt ") {
- mode = 0;
- eprintln!("warning: msgctxt not implemented (line {})", i + 1);
- continue;
- }
- let frag =
- serde_json::from_str::<String>(line).context(anyhow!("line {}", i + 1))?;
- match mode {
- 0 => (),
- 1 => msgid.push_str(&frag),
- 2 => msgstr.push_str(&frag),
- _ => unreachable!(),
- };
- }
-
- File::create(output)?.write_all(
- outmap
- .into_iter()
- .try_fold("[hurrycurry]\n".to_string(), |mut a, (k, v)| {
- writeln!(a, "{k}={v}")?;
- Ok::<_, anyhow::Error>(a)
- })?
- .as_bytes(),
- )?;
-
- Ok(())
- }
- Args::ImportOldPot { input, output } => {
- let output_raw = read_to_string(&output).unwrap_or("".to_owned());
- let input = read_to_string(input)?;
-
- let mut output_flip = output_raw
- .lines()
- .skip(1)
- .map(|l| {
- l.split_once("=")
- .map(|(k, v)| (v.to_owned(), k.to_owned()))
- .ok_or(anyhow!("invalid ini"))
- })
- .collect::<Result<BTreeMap<String, String>>>()?;
-
- let mut id = false;
- let mut msgid = String::new();
- for (i, mut line) in input.lines().enumerate() {
- if line.starts_with("#") {
- continue;
- }
- if line.is_empty() {
- continue;
- }
- if let Some(rest) = line.strip_prefix("msgid ") {
- if !msgid.is_empty() && !output_flip.contains_key(&msgid) {
- output_flip.insert(msgid.replace("\n", "\\n"), format!("unknown{i}"));
- }
- line = rest;
- id = true;
- msgid = String::new();
- } else if line.starts_with("msgctxt ") || line.starts_with("msgstr ") {
- id = false;
- continue;
- }
- if id {
- let frag =
- serde_json::from_str::<String>(line).context(anyhow!("line {}", i + 1))?;
- msgid.push_str(frag.as_str());
- }
- }
-
- let output_unflip = output_flip
- .into_iter()
- .map(|(v, k)| (k, v))
- .collect::<BTreeMap<_, _>>();
-
- File::create(output)?.write_all(
- output_unflip
- .into_iter()
- .try_fold("[hurrycurry]\n".to_string(), |mut a, (k, v)| {
- writeln!(a, "{k}={v}")?;
- Ok::<_, anyhow::Error>(a)
- })?
- .as_bytes(),
- )?;
-
- Ok(())
- }
- }
-}
-
-fn load_ini(path: &Path) -> Result<BTreeMap<String, String>> {
- read_to_string(path)?
- .lines()
- .skip(1)
- .map(|l| {
- let (k, v) = l.split_once("=").ok_or(anyhow!("'=' missing"))?;
- Ok::<_, anyhow::Error>((k.trim_end().to_owned(), v.trim_start().replace("%n", "\n")))
- })
- .collect()
-}
diff --git a/server/locale-export/src/po.rs b/server/locale-export/src/po.rs
new file mode 100644
index 00000000..05319ba0
--- /dev/null
+++ b/server/locale-export/src/po.rs
@@ -0,0 +1,190 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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 crate::{export_load, load_ini};
+use anyhow::{Context, Result, anyhow};
+use std::{
+ collections::BTreeMap,
+ fmt::Write as W2,
+ fs::{File, read_to_string},
+ io::Write,
+ path::{Path, PathBuf},
+};
+
+pub fn export_po(inputs: &[PathBuf], output: &Path, id_map: Option<PathBuf>) -> Result<()> {
+ let ini = export_load(&inputs)?;
+ let id_map = id_map.map(|path| load_ini(&path)).transpose()?;
+ File::create(output)?.write_all(
+ format!(
+ r#"
+msgid ""
+msgstr ""
+"Language: {}\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+{}"#,
+ inputs
+ .first()
+ .ok_or(anyhow!("at least one input required"))?
+ .file_stem()
+ .ok_or(anyhow!("file name empty"))?
+ .to_string_lossy(),
+ ini.into_iter()
+ .try_fold(String::new(), |mut a, (mut key, value)| {
+ if let Some(id_map) = &id_map
+ && let Some(new_id) = id_map.get(&key)
+ {
+ key = new_id.to_owned()
+ }
+ let key = serde_json::to_string(&key)?;
+ let value = serde_json::to_string(&value)?;
+ writeln!(a, "msgid {key}\nmsgstr {value}\n\n",)?;
+ Ok::<_, anyhow::Error>(a)
+ })?
+ )
+ .as_bytes(),
+ )?;
+ Ok(())
+}
+
+pub fn import_old_po(reference: &Path, input: &Path, output: &Path) -> Result<()> {
+ let reference = read_to_string(reference)?;
+ let input = read_to_string(input)?;
+
+ let id_reverse = reference
+ .lines()
+ .skip(1)
+ .map(|l| {
+ l.split_once("=")
+ .map(|(k, v)| (v, k))
+ .ok_or(anyhow!("invalid ini"))
+ })
+ .collect::<Result<BTreeMap<&str, &str>>>()?;
+
+ let mut outmap = BTreeMap::new();
+ let mut mode = 0;
+ let mut msgid = String::new();
+ let mut msgstr = String::new();
+ for (i, mut line) in input.lines().enumerate() {
+ if line.starts_with("#") {
+ continue;
+ }
+ if line.is_empty() {
+ continue;
+ }
+ if let Some(rest) = line.strip_prefix("msgid ") {
+ if !msgid.is_empty() {
+ if let Some(id) = id_reverse.get(&msgid.as_str()) {
+ outmap.insert(id.to_owned(), msgstr.clone());
+ } else {
+ eprintln!("warning: message id {msgid:?} is unknown")
+ }
+ }
+ line = rest;
+ msgid = String::new();
+ mode = 1;
+ } else if let Some(rest) = line.strip_prefix("msgstr ") {
+ line = rest;
+ msgstr = String::new();
+ mode = 2;
+ } else if line.starts_with("msgctxt ") {
+ mode = 0;
+ eprintln!("warning: msgctxt not implemented (line {})", i + 1);
+ continue;
+ }
+ let frag = serde_json::from_str::<String>(line).context(anyhow!("line {}", i + 1))?;
+ match mode {
+ 0 => (),
+ 1 => msgid.push_str(&frag),
+ 2 => msgstr.push_str(&frag),
+ _ => unreachable!(),
+ };
+ }
+
+ File::create(output)?.write_all(
+ outmap
+ .into_iter()
+ .try_fold("[hurrycurry]\n".to_string(), |mut a, (k, v)| {
+ writeln!(a, "{k}={v}")?;
+ Ok::<_, anyhow::Error>(a)
+ })?
+ .as_bytes(),
+ )?;
+
+ Ok(())
+}
+
+pub fn import_old_pot(input: &Path, output: &Path) -> Result<()> {
+ let output_raw = read_to_string(&output).unwrap_or("".to_owned());
+ let input = read_to_string(input)?;
+
+ let mut output_flip = output_raw
+ .lines()
+ .skip(1)
+ .map(|l| {
+ l.split_once("=")
+ .map(|(k, v)| (v.to_owned(), k.to_owned()))
+ .ok_or(anyhow!("invalid ini"))
+ })
+ .collect::<Result<BTreeMap<String, String>>>()?;
+
+ let mut id = false;
+ let mut msgid = String::new();
+ for (i, mut line) in input.lines().enumerate() {
+ if line.starts_with("#") {
+ continue;
+ }
+ if line.is_empty() {
+ continue;
+ }
+ if let Some(rest) = line.strip_prefix("msgid ") {
+ if !msgid.is_empty() && !output_flip.contains_key(&msgid) {
+ output_flip.insert(msgid.replace("\n", "\\n"), format!("unknown{i}"));
+ }
+ line = rest;
+ id = true;
+ msgid = String::new();
+ } else if line.starts_with("msgctxt ") || line.starts_with("msgstr ") {
+ id = false;
+ continue;
+ }
+ if id {
+ let frag = serde_json::from_str::<String>(line).context(anyhow!("line {}", i + 1))?;
+ msgid.push_str(frag.as_str());
+ }
+ }
+
+ let output_unflip = output_flip
+ .into_iter()
+ .map(|(v, k)| (k, v))
+ .collect::<BTreeMap<_, _>>();
+
+ File::create(output)?.write_all(
+ output_unflip
+ .into_iter()
+ .try_fold("[hurrycurry]\n".to_string(), |mut a, (k, v)| {
+ writeln!(a, "{k}={v}")?;
+ Ok::<_, anyhow::Error>(a)
+ })?
+ .as_bytes(),
+ )?;
+
+ Ok(())
+}
diff --git a/server/locale/src/error.rs b/server/locale/src/error.rs
index 7e41519d..e5091f15 100644
--- a/server/locale/src/error.rs
+++ b/server/locale/src/error.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/locale/src/lib.rs b/server/locale/src/lib.rs
index af1c19fc..d12aa481 100644
--- a/server/locale/src/lib.rs
+++ b/server/locale/src/lib.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/locale/src/message.rs b/server/locale/src/message.rs
index 8d01fe19..90f9ded0 100644
--- a/server/locale/src/message.rs
+++ b/server/locale/src/message.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/protocol/src/book.rs b/server/protocol/src/book.rs
index ba432890..6a77491f 100644
--- a/server/protocol/src/book.rs
+++ b/server/protocol/src/book.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -16,7 +16,7 @@
*/
-use crate::Message;
+use crate::{ItemIndex, Message};
use glam::Vec2;
use serde::{Deserialize, Serialize};
@@ -41,6 +41,7 @@ pub enum BookPage {
title: Message,
description: Message,
diagram: Diagram,
+ points: Vec<(ItemIndex, i64)>,
},
}
diff --git a/server/protocol/src/helpers.rs b/server/protocol/src/helpers.rs
index 7000dc9f..a6b118a7 100644
--- a/server/protocol/src/helpers.rs
+++ b/server/protocol/src/helpers.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -15,7 +15,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::{Gamedata, ItemIndex, Recipe, RecipeIndex, TileIndex};
+use crate::{GameConfig, Gamedata, ItemIndex, Recipe, RecipeIndex, TileIndex};
impl Gamedata {
pub fn tile_name(&self, index: TileIndex) -> &str {
@@ -59,6 +59,12 @@ impl Gamedata {
pub fn walkable(&self, tiles: &[TileIndex]) -> bool {
!tiles.iter().any(|t| self.tile_collide.contains(t))
}
+ pub fn walkable_preferred(&self, tiles: &[TileIndex]) -> bool {
+ self.walkable(tiles)
+ && !tiles
+ .iter()
+ .any(|p| self.tile_avoid_pathfinding.contains(p))
+ }
}
impl Recipe {
@@ -201,3 +207,12 @@ pub mod deser {
.collect())
}
}
+
+impl GameConfig {
+ pub fn new_map(map: &str) -> Self {
+ Self {
+ map: map.to_owned(),
+ ..Default::default()
+ }
+ }
+}
diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs
index 6d299497..a9683360 100644
--- a/server/protocol/src/lib.rs
+++ b/server/protocol/src/lib.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -66,11 +66,14 @@ pub struct DemandIndex(#[serde(deserialize_with = "deser_usize")] pub usize);
#[serde(transparent)]
pub struct Hand(#[serde(deserialize_with = "deser_usize")] pub usize);
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MapMetadata {
pub name: String,
+ pub display_name: String,
pub players: usize,
pub difficulty: i32,
+ pub hand_count: usize,
+ pub demand_items: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -85,13 +88,13 @@ pub struct Demand {
pub struct Serverdata {
pub name: String,
pub motd: Option<String>,
- pub maps: Vec<(String, MapMetadata)>,
+ pub maps: Vec<MapMetadata>,
pub bot_algos: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Gamedata {
- pub current_map: String,
+ pub metadata: MapMetadata,
pub item_names: Vec<String>,
pub tile_names: Vec<String>,
pub tile_collide: HashSet<TileIndex>,
@@ -99,6 +102,7 @@ pub struct Gamedata {
pub tile_placeable_items: BTreeMap<TileIndex, HashSet<ItemIndex>>,
pub tile_placeable_any: HashSet<TileIndex>,
pub tile_interactable_empty: HashSet<TileIndex>,
+ pub tile_avoid_pathfinding: HashSet<TileIndex>,
pub recipes: Vec<Recipe>,
pub demands: Vec<Demand>,
pub hand_count: usize,
@@ -183,8 +187,8 @@ pub enum PacketS {
/// For internal use only (customers)
#[serde(skip)]
Effect {
- player: PlayerID,
- name: String,
+ effect: Effect,
+ location: ItemLocation,
},
/// Used internally and only used when built with debug_events
@@ -255,6 +259,8 @@ pub enum PacketC {
rot: f32,
dir: Vec2,
boost: bool,
+ #[serde(skip_serializing_if = "is_false")]
+ sync: bool,
},
Pause {
state: bool,
@@ -285,8 +291,9 @@ pub enum PacketC {
message: Option<Message>,
timeout: Option<MessageTimeout>,
},
- Effect2 {
- name: String,
+ Effect {
+ #[serde(flatten)]
+ effect: Effect,
location: ItemLocation,
},
ServerMessage {
@@ -304,9 +311,6 @@ pub enum PacketC {
lobby: bool,
},
Menu(Menu),
- MovementSync {
- player: PlayerID,
- },
Environment {
effects: HashSet<String>,
},
@@ -337,6 +341,9 @@ pub enum PacketC {
VoteEnded {
result: bool,
},
+ SpectatorCount {
+ count: usize,
+ },
/// For use in replay sessions only
ReplayStart,
@@ -346,7 +353,11 @@ pub enum PacketC {
Debug(DebugEvent),
}
-#[derive(Debug, Clone, Serialize, Deserialize)]
+fn is_false(x: &bool) -> bool {
+ !*x
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case", tag = "action")]
pub enum VoteSubject {
StartGame { config: GameConfig },
@@ -354,11 +365,21 @@ pub enum VoteSubject {
RestartGame,
}
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct GameConfig {
pub map: String,
pub hand_count: Option<usize>,
pub timer: Option<f32>,
+ #[serde(default)]
+ pub bots: Vec<String>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case", tag = "effect")]
+pub enum Effect {
+ Satisfied,
+ Angry,
+ Points { amount: i64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -380,7 +401,7 @@ pub struct MessageTimeout {
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Score {
- pub time_remaining: f64,
+ pub time_remaining: f32,
pub stars: u8,
pub points: i64,
pub demands_failed: usize,
@@ -445,6 +466,6 @@ pub struct DebugEvent {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "ty")]
pub enum DebugEventDisplay {
- Path { points: Vec<Vec2> },
- Label { pos: Vec2, text: String },
+ Path { player: PlayerID, points: Vec<Vec2> },
+ Label { pos: ItemLocation, text: String },
}
diff --git a/server/protocol/src/movement.rs b/server/protocol/src/movement.rs
index 521de924..860c4678 100644
--- a/server/protocol/src/movement.rs
+++ b/server/protocol/src/movement.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -89,12 +89,13 @@ impl MovementBase {
player,
}
}
- pub fn movement_packet_c(&self, player: PlayerID) -> PacketC {
+ pub fn movement_packet_c(&self, player: PlayerID, sync: bool) -> PacketC {
PacketC::Movement {
rot: self.rotation,
pos: self.position,
boost: self.input_boost,
dir: self.input_direction,
+ sync,
player,
}
}
diff --git a/server/protocol/src/registry.rs b/server/protocol/src/registry.rs
index 10f12320..6c7e06bc 100644
--- a/server/protocol/src/registry.rs
+++ b/server/protocol/src/registry.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/registry/src/conn_test.rs b/server/registry/src/conn_test.rs
index bc371a10..2fb88272 100644
--- a/server/registry/src/conn_test.rs
+++ b/server/registry/src/conn_test.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/registry/src/list.rs b/server/registry/src/list.rs
index 56277db5..69e251a7 100644
--- a/server/registry/src/list.rs
+++ b/server/registry/src/list.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/registry/src/lobby.rs b/server/registry/src/lobby.rs
index 80a79236..662c12d8 100644
--- a/server/registry/src/lobby.rs
+++ b/server/registry/src/lobby.rs
@@ -1,7 +1,7 @@
use crate::Registry;
use anyhow::Result;
use hurrycurry_protocol::{
- Gamedata, PacketC, PacketS, PlayerClass, PlayerID, Serverdata, TileIndex, VERSION,
+ Gamedata, MapMetadata, PacketC, PacketS, PlayerClass, PlayerID, Serverdata, TileIndex, VERSION,
glam::{IVec2, Vec2, ivec2, vec2},
movement::MovementBase,
registry::Entry,
@@ -46,7 +46,6 @@ async fn lobby(registry: Arc<RwLock<Registry>>, saddr: SocketAddr) -> Result<()>
}
}
-#[allow(unused_assignments)]
async fn handle_conn(sock: TcpStream, addr: SocketAddr, entries: &[Entry]) -> Result<()> {
let sock = tokio_tungstenite::accept_async(sock).await?;
info!("{addr} connected via websocket");
@@ -84,7 +83,11 @@ async fn handle_conn(sock: TcpStream, addr: SocketAddr, entries: &[Entry]) -> Re
tile_collide: (0..TILES.len()).map(TileIndex).collect(),
tile_placeable_items: BTreeMap::new(),
tile_names: TILES.iter().map(|(s, _)| s.to_string()).collect(),
- current_map: "registry".to_owned(),
+ metadata: MapMetadata {
+ name: "registry".to_string(),
+ display_name: "Registry Lobby".to_string(),
+ ..Default::default()
+ },
..Default::default()
})));
let walkable = HashSet::from_iter(
@@ -168,7 +171,7 @@ async fn handle_conn(sock: TcpStream, addr: SocketAddr, entries: &[Entry]) -> Re
movement.position = pos.unwrap_or(movement.position);
movement.input(dir, boost);
movement.update(&walkable, dt.as_secs_f32());
- out.push(movement.movement_packet_c(player));
+ out.push(movement.movement_packet_c(player, false));
if !redirected {
for (i, e) in entries.iter().enumerate() {
if movement
diff --git a/server/registry/src/main.rs b/server/registry/src/main.rs
index 224845d3..21bc4136 100644
--- a/server/registry/src/main.rs
+++ b/server/registry/src/main.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/registry/src/register.rs b/server/registry/src/register.rs
index 473e958a..e3d035b9 100644
--- a/server/registry/src/register.rs
+++ b/server/registry/src/register.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/replaytool/src/main.rs b/server/replaytool/src/main.rs
index 8dde133d..4785a99a 100644
--- a/server/replaytool/src/main.rs
+++ b/server/replaytool/src/main.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/replaytool/src/record.rs b/server/replaytool/src/record.rs
index 383d214d..e51db3f8 100644
--- a/server/replaytool/src/record.rs
+++ b/server/replaytool/src/record.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/replaytool/src/render.rs b/server/replaytool/src/render.rs
index 9654256d..ccd97da5 100644
--- a/server/replaytool/src/render.rs
+++ b/server/replaytool/src/render.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/replaytool/src/replay.rs b/server/replaytool/src/replay.rs
index 0f465c54..92da6140 100644
--- a/server/replaytool/src/replay.rs
+++ b/server/replaytool/src/replay.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/benchmark.rs b/server/src/benchmark.rs
index 216cf0a8..3e694e0e 100644
--- a/server/src/benchmark.rs
+++ b/server/src/benchmark.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -16,15 +16,9 @@
*/
-use crate::{
- entity::bot::BotDriver,
- server::{Server, ServerConfig},
-};
-use anyhow::Result;
-use hurrycurry_bot::algos::Simple;
-use hurrycurry_data::build_gamedata;
-use hurrycurry_protocol::{Character, PacketC, PlayerClass};
-use log::debug;
+use crate::server::{Server, ServerConfig};
+use anyhow::{Result, anyhow};
+use hurrycurry_protocol::{GameConfig, PacketC};
use std::{path::PathBuf, time::Instant};
use tokio::sync::broadcast;
@@ -37,14 +31,17 @@ pub fn benchmark(data_path: PathBuf) -> Result<()> {
broadcast::channel(1024).0,
)?;
- server.load(build_gamedata(&server.config.data_path, "junior", true)?, None);
-
- server.entities.push(Box::new(BotDriver::new(
- "player".to_string(),
- Character::default(),
- PlayerClass::Chef,
- |p| Box::new(Simple::new(p)),
- )));
+ server
+ .load(GameConfig {
+ map: "junior".to_string(),
+ bots: vec![
+ "simple".to_owned(),
+ "dishwasher".to_owned(),
+ "waiter".to_string(),
+ ],
+ ..Default::default()
+ })
+ .map_err(|e| anyhow!("{e}"))?;
let start = Instant::now();
let mut packets = 0;
@@ -61,7 +58,7 @@ pub fn benchmark(data_path: PathBuf) -> Result<()> {
}
}
- debug!("points: {}", server.game.score.points);
+ println!("points: {}", server.game.score.points);
println!("simulation took {:?}", start.elapsed());
println!(
"packets broadcast: {packets} ({packets_movement} movement, {} other)",
diff --git a/server/src/commands.rs b/server/src/commands.rs
index f8ead074..3d306aca 100644
--- a/server/src/commands.rs
+++ b/server/src/commands.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -16,17 +16,15 @@
*/
use crate::{
- entity::{bot::BotDriver, tutorial::Tutorial},
+ entity::tutorial::Tutorial,
server::{AnnounceState, Server},
};
use anyhow::Result;
use clap::{Parser, ValueEnum};
use hurrycurry_bot::algos::ALGO_CONSTRUCTORS;
-use hurrycurry_data::build_gamedata;
use hurrycurry_locale::{TrError, tre, trm};
use hurrycurry_protocol::{
- Character, GameConfig, Hand, ItemLocation, Menu, Message, PacketC, PacketS, PlayerClass,
- PlayerID, VoteSubject,
+ Effect, GameConfig, Hand, ItemLocation, Menu, Message, PacketC, PacketS, PlayerID, VoteSubject,
};
use std::{fmt::Write, str::FromStr};
@@ -36,7 +34,7 @@ enum Command {
/// Start a new game
#[clap(alias = "s")]
Start {
- /// Gamedata specification
+ /// Map name
#[arg(default_value = "junior")]
map: String,
@@ -45,8 +43,12 @@ enum Command {
hand_count: Option<usize>,
/// Duration in seconds
- #[arg(short = 't', long)]
+ #[arg(short, long)]
timer: Option<f32>,
+
+ /// Add bot by name
+ #[arg(short, long)]
+ bot: Vec<String>,
},
/// Shows the best entries of the scoreboard for this map.
#[clap(alias = "top", alias = "top5")]
@@ -66,14 +68,14 @@ enum Command {
},
/// Abort the current game
End,
- /// Send an effect
- Effect { name: String },
/// Send an item message
Item { name: String },
/// Send a tile message
Tile { name: String },
/// Show all possible demands for this map
Demands,
+ /// Play effect
+ Effect { name: String },
/// Ready yourself
Ready {
#[arg(short, long)]
@@ -81,8 +83,6 @@ enum Command {
#[arg(short, long)]
unready: bool,
},
- #[clap(alias = "summon", alias = "bot")]
- CreateBot { algo: String, name: Option<String> },
/// Reload the current map
#[clap(alias = "r")]
Reload,
@@ -99,6 +99,10 @@ enum Command {
message_id: String,
arguments: Vec<String>,
},
+ /// Add bots to the current game (CHEAT)
+ #[cfg(feature = "cheats")]
+ #[clap(alias = "summon", alias = "bot")]
+ CreateBot { algo: String, name: Option<String> },
/// Set your players hand item (CHEAT)
#[cfg(feature = "cheats")]
#[clap(alias = "give")]
@@ -147,7 +151,14 @@ impl Server {
map,
hand_count,
timer,
+ bot,
} => {
+ for b in &bot {
+ ALGO_CONSTRUCTORS
+ .iter()
+ .find(|(name, _)| *name == b)
+ .ok_or(tre!("s.error.algo_not_found", s = b.to_string()))?;
+ }
self.packet_in(
replies,
PacketS::InitiateVote {
@@ -157,6 +168,7 @@ impl Server {
hand_count,
map,
timer,
+ bots: bot,
},
},
},
@@ -184,11 +196,10 @@ impl Server {
if self.count_chefs() > 1 {
return Err(tre!("s.error.must_be_alone"));
}
- self.load(
- build_gamedata(&self.config.data_path, &self.game.data.current_map, true)
- .map_err(|e| TrError::Plain(e.to_string()))?,
- None,
- );
+ self.load(GameConfig {
+ map: self.game.data.metadata.name.clone(),
+ ..Default::default()
+ })?;
self.announce_state = AnnounceState::Done
}
Command::Ready { force, unready } => {
@@ -205,27 +216,21 @@ impl Server {
Command::Book => {
replies.push(PacketC::Menu(Menu::Book(self.priv_gamedata.book.clone())))
}
- Command::Effect { name } => {
- self.broadcast
- .send(PacketC::Effect2 {
- name,
- location: ItemLocation::Player(player, Hand(0)),
- })
- .ok();
- }
Command::Item { name } => {
let item = self
.game
.data
.get_item_by_name(&name)
.ok_or(tre!("s.error.item_not_found", s = name))?;
- self.broadcast
- .send(PacketC::Communicate {
+ self.packet_in(
+ replies,
+ PacketS::Communicate {
player,
- message: Some(Message::Item(item)),
+ pin: None,
timeout: None,
- })
- .ok();
+ message: Some(Message::Item(item)),
+ },
+ )?;
}
Command::Tile { name } => {
let tile = self
@@ -233,38 +238,24 @@ impl Server {
.data
.get_tile_by_name(&name)
.ok_or(tre!("s.error.no_tile", s = name))?;
- self.broadcast
- .send(PacketC::Communicate {
+ self.packet_in(
+ replies,
+ PacketS::Communicate {
player,
message: Some(Message::Tile(tile)),
timeout: None,
- })
- .ok();
- }
- Command::CreateBot { algo, name } => {
- let (aname, cons) = ALGO_CONSTRUCTORS
- .iter()
- .find(|(name, _)| *name == algo.as_str())
- .ok_or(tre!("s.error.algo_not_found", s = algo))?;
- self.entities.push(Box::new(BotDriver::new(
- format!("{}-bot", name.unwrap_or((*aname).to_owned())),
- Character {
- color: 0,
- hairstyle: 0,
- headwear: 0,
+ pin: None,
},
- PlayerClass::Bot,
- cons,
- )));
+ )?;
}
Command::Scoreboard { map, text } => {
- let mapname = map.as_ref().unwrap_or(&self.game.data.current_map);
+ let mapname = map.as_ref().unwrap_or(&self.game.data.metadata.name);
let mapname_pretty = &self
.data
.maps
.iter()
- .find(|(n, _)| n == mapname)
- .map(|(_, m)| &m.name)
+ .find(|m| m.name == *mapname)
+ .map(|m| &m.display_name)
.ok_or(tre!("s.error.scoreboard_disabled"))?;
if let Some(board) = self.scoreboard.get(mapname) {
if text {
@@ -295,13 +286,12 @@ impl Server {
}
}
Command::Info { map } => {
- let mapname = map.as_ref().unwrap_or(&self.game.data.current_map);
+ let mapname = map.as_ref().unwrap_or(&self.game.data.metadata.name);
let info = &self
.data
.maps
.iter()
- .find(|(n, _)| n == mapname)
- .map(|(_, m)| m)
+ .find(|m| m.name == *mapname)
.ok_or(tre!("s.error.no_info"))?;
replies.push(PacketC::ServerMessage {
message: Message::Text(format!(
@@ -312,18 +302,32 @@ impl Server {
});
}
Command::StartTutorial { item } => {
- let item = self
- .game
- .data
- .get_item_by_name(&item)
- .ok_or(tre!("s.error.item_not_found", s = item))?;
if self.entities.iter().any(|e| {
<dyn std::any::Any>::downcast_ref::<Tutorial>(e.as_ref())
.is_some_and(|t| t.player == player)
}) {
return Err(tre!("s.error.tutorial_already_running"));
}
- self.entities.push(Box::new(Tutorial::new(player, item)));
+ if matches!(
+ item.as_str(),
+ "book" | "map-selector" | "button:reject" | "button:accept"
+ ) {
+ let tile = self
+ .game
+ .data
+ .get_tile_by_name(&item)
+ .ok_or(tre!("s.error.tile_not_found", s = item))?;
+ self.entities
+ .push(Box::new(Tutorial::new_button(player, tile)));
+ } else {
+ let item = self
+ .game
+ .data
+ .get_item_by_name(&item)
+ .ok_or(tre!("s.error.item_not_found", s = item))?;
+ self.entities
+ .push(Box::new(Tutorial::new_item(player, item)));
+ }
}
Command::EndTutorial => {
if let Some(tutorial) = self
@@ -363,6 +367,35 @@ impl Server {
error: false,
});
}
+ Command::Effect { name } => {
+ let effect = match name.as_str() {
+ "angry" => Effect::Angry,
+ "satisfied" => Effect::Satisfied,
+ _ => return Err(tre!("s.error.no_effect")),
+ };
+ self.packet_out.push_back(PacketC::Effect {
+ effect,
+ location: ItemLocation::Player(player, Hand(0)),
+ });
+ }
+ #[cfg(feature = "cheats")]
+ Command::CreateBot { algo, name } => {
+ let (aname, cons) = ALGO_CONSTRUCTORS
+ .iter()
+ .find(|(name, _)| *name == algo.as_str())
+ .ok_or(tre!("s.error.algo_not_found", s = algo))?;
+ self.entities
+ .push(Box::new(crate::entity::bot::BotDriver::new(
+ format!("{}-bot", name.unwrap_or((*aname).to_owned())),
+ hurrycurry_protocol::Character {
+ color: 0,
+ hairstyle: 0,
+ headwear: 0,
+ },
+ hurrycurry_protocol::PlayerClass::Bot,
+ cons,
+ )));
+ }
#[cfg(feature = "cheats")]
Command::SetItem { name } => {
use hurrycurry_game_core::Item;
diff --git a/server/src/entity/bot.rs b/server/src/entity/bot.rs
index 36f701ec..6bd48af8 100644
--- a/server/src/entity/bot.rs
+++ b/server/src/entity/bot.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/button.rs b/server/src/entity/button.rs
index f0e2087d..afd18f1a 100644
--- a/server/src/entity/button.rs
+++ b/server/src/entity/button.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/campaign.rs b/server/src/entity/campaign.rs
index af6de391..e907b2b3 100644
--- a/server/src/entity/campaign.rs
+++ b/server/src/entity/campaign.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -21,7 +21,7 @@ use anyhow::Result;
use hurrycurry_data::entities::GateCondition;
use hurrycurry_locale::{TrError, trm};
use hurrycurry_protocol::{
- ItemLocation, Message, PacketC, PlayerID, TileIndex,
+ GameConfig, ItemLocation, Message, PacketC, PlayerID, TileIndex,
glam::{IVec2, Vec2},
};
@@ -39,7 +39,10 @@ impl Entity for Map {
.query(self.pos, 0.5, |_, _| activate = true);
if activate {
- *c.load_map = Some(self.name.clone());
+ *c.load_map = Some(GameConfig {
+ map: self.name.clone(),
+ ..GameConfig::default()
+ });
}
Ok(())
diff --git a/server/src/entity/conveyor.rs b/server/src/entity/conveyor.rs
index 6757ed43..4950be99 100644
--- a/server/src/entity/conveyor.rs
+++ b/server/src/entity/conveyor.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/ctf_minigame.rs b/server/src/entity/ctf_minigame.rs
index c975821d..d2b23aee 100644
--- a/server/src/entity/ctf_minigame.rs
+++ b/server/src/entity/ctf_minigame.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -18,6 +18,7 @@
use super::{Entity, EntityContext};
use anyhow::Result;
use hurrycurry_locale::TrError;
+use hurrycurry_protocol::{Effect, Hand};
use hurrycurry_protocol::{ItemIndex, ItemLocation, Message, PacketC, PlayerID, glam::IVec2};
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
@@ -36,11 +37,11 @@ struct TeamData {
}
impl TeamData {
- fn effect(&self, c: &mut EntityContext, name: &str) -> Result<(), TrError> {
+ fn effect(&self, c: &mut EntityContext, effect: Effect) -> Result<(), TrError> {
for pid in self.players.iter() {
- c.packet_out.push_back(PacketC::Effect2 {
- name: name.to_string(),
- location: ItemLocation::Player(*pid, hurrycurry_protocol::Hand(0)),
+ c.packet_out.push_back(PacketC::Effect {
+ effect: effect.clone(),
+ location: ItemLocation::Player(*pid, Hand(0)),
});
}
Ok(())
@@ -257,14 +258,14 @@ impl Entity for CtfMinigame {
.is_some_and(|a| a == from_team_idx)
{
for i in self.player_flags(&mut c, from)? {
- self.teams.get(&i).unwrap().effect(&mut c, "angry")?;
+ self.teams.get(&i).unwrap().effect(&mut c, Effect::Angry)?;
self.return_flag(&mut c, i)?;
self.teams.get_mut(&from_team_idx).unwrap().score += 10;
}
self.teams
.get(&from_team_idx)
.unwrap()
- .effect(&mut c, "satisfied")?;
+ .effect(&mut c, Effect::Satisfied)?;
self.send_table(&mut c)?;
}
Ok(true)
diff --git a/server/src/entity/customers.rs b/server/src/entity/customers.rs
index d5e1751d..835afb92 100644
--- a/server/src/entity/customers.rs
+++ b/server/src/entity/customers.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/demand_sink.rs b/server/src/entity/demand_sink.rs
index 173d166b..45244e91 100644
--- a/server/src/entity/demand_sink.rs
+++ b/server/src/entity/demand_sink.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -19,7 +19,7 @@
use crate::entity::{Entity, EntityContext};
use anyhow::Result;
use hurrycurry_locale::TrError;
-use hurrycurry_protocol::{ItemLocation, PacketC, glam::IVec2};
+use hurrycurry_protocol::{Effect, ItemLocation, PacketC, glam::IVec2};
pub struct DemandSink {
pub pos: IVec2,
@@ -36,8 +36,14 @@ impl Entity for DemandSink {
c.game.score.demands_completed += 1;
c.game.score.points += demand.points;
*c.score_changed = true;
- c.packet_out.push_back(PacketC::Effect2 {
- name: "satisfied".to_string(),
+ c.packet_out.push_back(PacketC::Effect {
+ effect: Effect::Satisfied,
+ location: ItemLocation::Tile(self.pos),
+ });
+ c.packet_out.push_back(PacketC::Effect {
+ effect: Effect::Points {
+ amount: demand.points,
+ },
location: ItemLocation::Tile(self.pos),
});
} else {
diff --git a/server/src/entity/environment_effect.rs b/server/src/entity/environment_effect.rs
index fab6f47b..bc823099 100644
--- a/server/src/entity/environment_effect.rs
+++ b/server/src/entity/environment_effect.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/item_portal.rs b/server/src/entity/item_portal.rs
index 4335a659..aec2a7db 100644
--- a/server/src/entity/item_portal.rs
+++ b/server/src/entity/item_portal.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs
index 604daf98..b17fbfb9 100644
--- a/server/src/entity/mod.rs
+++ b/server/src/entity/mod.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -33,8 +33,8 @@ pub mod tutorial;
use crate::{
entity::{
- ctf_minigame::CtfMinigame, demand_sink::DemandSink, pedestrians::Pedestrians,
- player_portal_pair::PlayerPortalPair, tag_minigame::TagMinigame,
+ bot::BotDriver, ctf_minigame::CtfMinigame, demand_sink::DemandSink,
+ pedestrians::Pedestrians, player_portal_pair::PlayerPortalPair, tag_minigame::TagMinigame,
},
scoreboard::ScoreboardStore,
};
@@ -44,10 +44,13 @@ use campaign::{Gate, Map};
use conveyor::Conveyor;
use customers::Customers;
use environment_effect::{EnvironmentController, EnvironmentEffectController};
+use hurrycurry_bot::algos::ALGO_CONSTRUCTORS;
use hurrycurry_data::{PrivateGamedata, entities::EntityDecl};
use hurrycurry_game_core::Game;
use hurrycurry_locale::TrError;
-use hurrycurry_protocol::{Character, ItemLocation, PacketC, PacketS, PlayerID};
+use hurrycurry_protocol::{
+ Character, GameConfig, ItemLocation, PacketC, PacketS, PlayerClass, PlayerID,
+};
use item_portal::ItemPortal;
use player_portal::PlayerPortal;
use std::{
@@ -65,7 +68,7 @@ pub struct EntityContext<'a> {
pub packet_out: &'a mut VecDeque<PacketC>,
pub packet_in: &'a mut VecDeque<PacketS>,
pub score_changed: &'a mut bool,
- pub load_map: &'a mut Option<String>,
+ pub load_map: &'a mut Option<GameConfig>,
pub scoreboard: &'a ScoreboardStore,
pub replies: Option<&'a mut Vec<PacketC>>,
pub dt: f32,
@@ -187,5 +190,23 @@ pub fn construct_entity(decl: &EntityDecl) -> DynEntity {
neutral_tile,
out_tile,
} => Box::new(PlayerPortalPair::new(a, b, in_tile, neutral_tile, out_tile)),
+
+ EntityDecl::Bot { name: search_name } => {
+ let (name, cons) = ALGO_CONSTRUCTORS
+ .iter()
+ .find(|(name, _)| *name == search_name.as_str())
+ .unwrap_or(&ALGO_CONSTRUCTORS[0]);
+
+ Box::new(BotDriver::new(
+ format!("{name}-bot"),
+ Character {
+ color: 0,
+ hairstyle: 0,
+ headwear: 0,
+ },
+ PlayerClass::Bot,
+ cons,
+ ))
+ }
}
}
diff --git a/server/src/entity/pedestrians.rs b/server/src/entity/pedestrians.rs
index 4fe91464..17a2e81c 100644
--- a/server/src/entity/pedestrians.rs
+++ b/server/src/entity/pedestrians.rs
@@ -1,8 +1,6 @@
-use crate::random_gauss;
-
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -18,6 +16,7 @@ use crate::random_gauss;
*/
use super::{Entity, EntityContext};
+use crate::random_gauss;
use anyhow::Result;
use hurrycurry_locale::TrError;
use hurrycurry_protocol::{Character, PacketS, PlayerClass, PlayerID, glam::Vec2};
diff --git a/server/src/entity/player_portal.rs b/server/src/entity/player_portal.rs
index 35733ca3..ade36b06 100644
--- a/server/src/entity/player_portal.rs
+++ b/server/src/entity/player_portal.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/player_portal_pair.rs b/server/src/entity/player_portal_pair.rs
index b525f4ca..edcfbefb 100644
--- a/server/src/entity/player_portal_pair.rs
+++ b/server/src/entity/player_portal_pair.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/tag_minigame.rs b/server/src/entity/tag_minigame.rs
index b0f3a711..2fc168d8 100644
--- a/server/src/entity/tag_minigame.rs
+++ b/server/src/entity/tag_minigame.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/tram.rs b/server/src/entity/tram.rs
index 20fe8120..5a802cff 100644
--- a/server/src/entity/tram.rs
+++ b/server/src/entity/tram.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/entity/tutorial.rs b/server/src/entity/tutorial.rs
index 87896a09..0f657297 100644
--- a/server/src/entity/tutorial.rs
+++ b/server/src/entity/tutorial.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -19,34 +19,60 @@ use super::{Entity, EntityContext};
use anyhow::Result;
use hurrycurry_locale::{TrError, trm};
use hurrycurry_protocol::{
- ItemIndex, Message, PacketC, PlayerID, Recipe, RecipeIndex, TileIndex, glam::IVec2,
+ ItemIndex, ItemLocation, Message, PacketC, PlayerID, Recipe, RecipeIndex, TileIndex,
+ glam::IVec2,
};
use log::{debug, warn};
pub struct Tutorial {
pub player: PlayerID,
- target: ItemIndex,
-
+ state: TutorialState,
next_update_due: f32,
- had_aquired_target: bool,
-
current_hint: Option<(Option<IVec2>, Message)>,
delete_timer: f32,
pub ended: bool,
}
+pub enum TutorialState {
+ Item {
+ item: ItemIndex,
+ had_aquired_target: bool,
+ },
+ Button {
+ tile: TileIndex,
+ pressed: bool,
+ },
+}
+
impl Tutorial {
- pub fn new(player: PlayerID, item: ItemIndex) -> Self {
+ fn new(player: PlayerID, target: TutorialState) -> Self {
Self {
ended: false,
player,
next_update_due: 0.,
- target: item,
+ state: target,
current_hint: None,
delete_timer: 1.5,
- had_aquired_target: false,
}
}
+ pub fn new_item(player: PlayerID, item: ItemIndex) -> Self {
+ Self::new(
+ player,
+ TutorialState::Item {
+ item,
+ had_aquired_target: false,
+ },
+ )
+ }
+ pub fn new_button(player: PlayerID, tile: TileIndex) -> Self {
+ Self::new(
+ player,
+ TutorialState::Button {
+ tile,
+ pressed: false,
+ },
+ )
+ }
}
impl Entity for Tutorial {
@@ -63,10 +89,34 @@ impl Entity for Tutorial {
}
c.packet_out.push_back(PacketC::TutorialEnded {
player: self.player,
- item: self.target,
+ item: match &self.state {
+ TutorialState::Item { item, .. } => *item,
+ _ => ItemIndex(0), // TODO
+ },
success: false,
});
}
+ fn interact(
+ &mut self,
+ c: EntityContext<'_>,
+ target: Option<ItemLocation>,
+ player: PlayerID,
+ ) -> Result<bool, TrError> {
+ if player == self.player {
+ match (&mut self.state, target) {
+ (TutorialState::Button { tile, pressed }, Some(ItemLocation::Tile(pos))) => {
+ if let Some(t) = c.game.tiles.get(&pos) {
+ if t.parts.contains(tile) {
+ *pressed = true;
+ }
+ }
+ }
+ _ => (),
+ };
+ }
+ Ok(false)
+ }
+
fn tick(&mut self, c: EntityContext<'_>) -> Result<(), TrError> {
if self.ended {
return Ok(());
@@ -78,21 +128,30 @@ impl Entity for Tutorial {
}
self.next_update_due += TARGET_DT;
- let mut hint = StepContext {
- ent: &c,
- had_aquired_target: &mut self.had_aquired_target,
- player: self.player,
- recursion_abort: 0,
- }
- .fulfil_demand(self.target)
- .err();
+ let mut hint = match &mut self.state {
+ TutorialState::Item {
+ item,
+ had_aquired_target,
+ } => StepContext {
+ ent: &c,
+ had_aquired_target,
+ player: self.player,
+ recursion_abort: 0,
+ }
+ .fulfil_demand(*item)
+ .err(),
+ TutorialState::Button { tile, pressed } => find_button(&c, *tile, *pressed),
+ };
if hint.is_none() {
self.delete_timer -= TARGET_DT;
if self.delete_timer <= 0. {
self.ended = true;
hint = None;
c.packet_out.push_back(PacketC::TutorialEnded {
- item: self.target,
+ item: match &self.state {
+ TutorialState::Item { item, .. } => *item,
+ _ => ItemIndex(0),
+ },
player: self.player,
success: true,
});
@@ -123,6 +182,31 @@ impl Entity for Tutorial {
}
}
+fn find_button(
+ ent: &EntityContext,
+ tile: TileIndex,
+ pressed: bool,
+) -> Option<(Option<IVec2>, Message)> {
+ if pressed {
+ return None;
+ }
+ ent.game
+ .tile_index
+ .get(&tile)
+ .map(|pos| pos.iter().next())
+ .flatten()
+ .map(|p| {
+ (
+ Some(*p),
+ match ent.game.data.tile_name(tile) {
+ "book" => trm!("s.tutorial.button.book"),
+ "map-selector" => trm!("s.tutorial.button.map_selector"),
+ _ => trm!("s.tutorial.interact"),
+ },
+ )
+ })
+}
+
struct StepContext<'a> {
ent: &'a EntityContext<'a>,
had_aquired_target: &'a mut bool,
diff --git a/server/src/lib.rs b/server/src/lib.rs
index cec288ae..b0e93d05 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -55,3 +55,100 @@ impl InterpolateExt for f32 {
fn random_gauss() -> f32 {
[(); 12].map(|()| random::<f32>()).iter().sum::<f32>() - 6.
}
+
+#[cfg(test)]
+mod test {
+ use super::{
+ ConnectionID,
+ server::{Server, ServerConfig},
+ };
+ use hurrycurry_protocol::{Character, GameConfig, Hand, PacketS, PlayerClass, PlayerID};
+ use tokio::sync::broadcast;
+
+ fn server() -> Server {
+ Server::new(
+ ServerConfig {
+ data_path: "../data".into(),
+ ..Default::default()
+ },
+ broadcast::channel(1024).0,
+ )
+ .unwrap()
+ }
+
+ #[test]
+ fn init_server() {
+ server();
+ }
+
+ #[test]
+ fn full_game() {
+ let mut s = server();
+ s.load(GameConfig {
+ map: "junior".to_string(),
+ ..Default::default()
+ })
+ .unwrap();
+ while s.tick(0.1).is_none() {}
+ }
+
+ #[test]
+ fn map_load() {
+ let mut s = server();
+ s.load(GameConfig {
+ map: "junior".to_string(),
+ ..Default::default()
+ })
+ .unwrap();
+ }
+ #[test]
+ fn map_load_book() {
+ let mut s = server();
+ s.load(GameConfig {
+ map: "junior".to_string(),
+ ..Default::default()
+ })
+ .unwrap();
+ }
+ #[test]
+ fn tick() {
+ let mut s = server();
+ for _ in 0..100 {
+ s.tick(0.1);
+ }
+ }
+ #[test]
+ fn packet_sender_verif() {
+ let mut s = server();
+
+ for (conn, p) in [
+ PacketS::Leave {
+ player: PlayerID(0),
+ },
+ PacketS::Interact {
+ hand: Hand(0),
+ player: PlayerID(0),
+ target: None,
+ },
+ PacketS::ReplayTick { dt: 1. },
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ s.packet_in_outer(
+ ConnectionID(conn.try_into().unwrap()),
+ PacketS::Join {
+ name: format!("test {conn}"),
+ character: Character::default(),
+ class: PlayerClass::Chef,
+ id: None,
+ position: None,
+ },
+ )
+ .unwrap();
+
+ let x = s.packet_in_outer(ConnectionID(conn.try_into().unwrap()), p);
+ assert!(x.is_ok(), "test {} {:?}", conn, x)
+ }
+ }
+}
diff --git a/server/src/main.rs b/server/src/main.rs
index f1af7c54..e5c79b25 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -15,11 +15,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use anyhow::{Context, Result, bail};
+use anyhow::{Context, Result, anyhow, bail};
use clap::Parser;
use futures_util::{SinkExt, StreamExt};
use hurrycurry_locale::trm;
-use hurrycurry_protocol::{PacketC, PacketS};
+use hurrycurry_protocol::{GameConfig, PacketC, PacketS};
use hurrycurry_server::{
ConnectionID,
benchmark::benchmark,
@@ -55,6 +55,18 @@ pub(crate) struct Args {
/// Inactivity timeout in seconds
#[arg(long, default_value_t = 60.)]
inactivity_timeout: f32,
+ /// Minimum time between initiating votes per player
+ #[arg(long, default_value_t = 30.)]
+ vote_initiate_cooldown: f32,
+ /// Minimum time between joining the game and casting a vote
+ #[arg(long, default_value_t = 20.)]
+ vote_cast_cooldown: f32,
+ /// Duration after which votes are counted up even if no majority was found
+ #[arg(long, default_value_t = 20.)]
+ vote_timeout: f32,
+ /// Always accept votes immediatly
+ #[arg(long)]
+ skip_voting: bool,
/// Registers this server to the public server registry
#[arg(long)]
#[cfg(feature = "register")]
@@ -144,11 +156,19 @@ async fn run(data_path: PathBuf, args: Args) -> anyhow::Result<()> {
data_path,
motd: args.motd,
name: args.server_name.clone(),
+ vote_cast_cooldown: args.vote_cast_cooldown,
+ vote_initiate_cooldown: args.vote_initiate_cooldown,
+ vote_timeout: args.vote_timeout,
+ skip_voting: args.skip_voting,
};
let mut state = Server::new(config, tx)?;
state
- .load_map(&state.config.lobby.clone())
+ .load(GameConfig {
+ map: state.config.lobby.clone(),
+ ..Default::default()
+ })
+ .map_err(|e| anyhow!("{e}"))
.context("initial lobby load")?;
let state = Arc::new(RwLock::new(state));
@@ -257,7 +277,7 @@ async fn run(data_path: PathBuf, args: Args) -> anyhow::Result<()> {
if matches!(
packet,
- PacketS::Movement { .. } | PacketS::ReplayTick { .. } | PacketS::Keepalive
+ PacketS::Movement { .. } | PacketS::ReplayTick { .. } | PacketS::Keepalive | PacketS::Debug(_)
) {
trace!("{id} <- {packet:?}");
} else {
@@ -322,7 +342,7 @@ fn find_data_path() -> Result<PathBuf> {
#[cfg(windows)]
match read_windows_reg_datadir() {
Ok(path) => test_order.push(path),
- Err(e) => warn!("Cannot find read datadir from windows registry: {e}"),
+ Err(e) => warn!("Cannot read datadir from windows registry: {e}"),
};
#[cfg(not(windows))]
@@ -344,96 +364,6 @@ fn find_data_path() -> Result<PathBuf> {
Ok(PathBuf::from_str(d)?)
}
-#[cfg(test)]
-mod test {
- use hurrycurry_data::build_gamedata;
- use hurrycurry_protocol::{Character, PacketS, PlayerClass, PlayerID};
- use hurrycurry_server::{
- ConnectionID,
- server::{Server, ServerConfig},
- };
- use tokio::sync::broadcast;
-
- fn server() -> Server {
- Server::new(
- ServerConfig {
- data_path: "../data".into(),
- ..Default::default()
- },
- broadcast::channel(1024).0,
- )
- .unwrap()
- }
-
- #[test]
- fn init_server() {
- server();
- }
- #[test]
- fn full_game() {
- let mut s = server();
- s.load_map("junior").unwrap();
- while s.tick(0.1).is_none() {}
- }
-
- #[test]
- fn map_load() {
- let mut s = server();
- s.load(
- build_gamedata(&s.config.data_path, "junior", false).unwrap(),
- None,
- );
- }
- #[test]
- fn map_load_book() {
- let mut s = server();
- s.load(
- build_gamedata(&s.config.data_path, "junior", true).unwrap(),
- None,
- );
- }
- #[test]
- fn tick() {
- let mut s = server();
- for _ in 0..100 {
- s.tick(0.1);
- }
- }
- #[test]
- fn packet_sender_verif() {
- let mut s = server();
-
- for (conn, p) in [
- PacketS::Effect {
- player: PlayerID(0),
- name: "test".to_owned(),
- },
- PacketS::Leave {
- player: PlayerID(0),
- },
- PacketS::ReplayTick { dt: 1. },
- ]
- .into_iter()
- .enumerate()
- {
- s.packet_in_outer(
- ConnectionID(conn.try_into().unwrap()),
- PacketS::Join {
- name: format!("test {conn}"),
- character: Character::default(),
- class: PlayerClass::Chef,
- id: None,
- position: None,
- },
- )
- .unwrap();
-
- let x = s.packet_in_outer(ConnectionID(conn.try_into().unwrap()), p);
- assert!(x.is_ok(), "test {} {:?}", conn, x)
- }
- }
-}
-
#[cfg(windows)]
fn read_windows_reg_datadir() -> Result<String> {
use anyhow::Context;
diff --git a/server/src/network/mdns.rs b/server/src/network/mdns.rs
index b15a197a..0da8dbd8 100644
--- a/server/src/network/mdns.rs
+++ b/server/src/network/mdns.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/network/mod.rs b/server/src/network/mod.rs
index 5eadc6b1..9b3b2dbc 100644
--- a/server/src/network/mod.rs
+++ b/server/src/network/mod.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/network/register.rs b/server/src/network/register.rs
index 101d96d1..bae857cc 100644
--- a/server/src/network/register.rs
+++ b/server/src/network/register.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/network/upnp.rs b/server/src/network/upnp.rs
index a443a223..e31f1f51 100644
--- a/server/src/network/upnp.rs
+++ b/server/src/network/upnp.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/src/scoreboard.rs b/server/src/scoreboard.rs
index 86616c1f..635e347c 100644
--- a/server/src/scoreboard.rs
+++ b/server/src/scoreboard.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -17,7 +17,7 @@
*/
use anyhow::Result;
use directories::ProjectDirs;
-use hurrycurry_protocol::{Score, Scoreboard, ScoreboardEntry};
+use hurrycurry_protocol::{GameConfig, Score, Scoreboard, ScoreboardEntry};
use log::warn;
use serde::{Deserialize, Serialize};
use std::{
@@ -65,13 +65,24 @@ impl ScoreboardStore {
pub fn get<'a>(&'a self, map: &str) -> Option<&'a Scoreboard> {
self.maps.get(map)
}
- pub fn insert(&mut self, map: &str, players: Vec<String>, score: Score) {
- let b = self.maps.entry(map.to_owned()).or_default();
- b.plays += 1;
- b.best.push(ScoreboardEntry { players, score });
- b.best.sort_by_key(|e| Reverse(e.score.points));
- while b.best.len() > 16 {
- b.best.pop();
+ pub fn insert(&mut self, config: &GameConfig, players: Vec<String>, score: Score) {
+ if config.is_valid_for_scoreboard() {
+ let b = self.maps.entry(config.map.to_owned()).or_default();
+ b.plays += 1;
+ b.best.push(ScoreboardEntry { players, score });
+ b.best.sort_by_key(|e| Reverse(e.score.points));
+ while b.best.len() > 16 {
+ b.best.pop();
+ }
}
}
}
+
+pub trait GameConfigScoreboardCheckExt {
+ fn is_valid_for_scoreboard(&self) -> bool;
+}
+impl GameConfigScoreboardCheckExt for GameConfig {
+ fn is_valid_for_scoreboard(&self) -> bool {
+ self.hand_count.is_none() && self.timer.is_none() && self.bots.is_empty()
+ }
+}
diff --git a/server/src/server.rs b/server/src/server.rs
index e272c07d..c980b6e6 100644
--- a/server/src/server.rs
+++ b/server/src/server.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -18,20 +18,20 @@
use crate::{
ConnectionID,
entity::{Entities, EntityContext, construct_entity},
- scoreboard::ScoreboardStore,
+ scoreboard::{GameConfigScoreboardCheckExt, ScoreboardStore},
vote::VoteState,
};
-use anyhow::{Context, Result, anyhow};
-use hurrycurry_data::{PrivateGamedata, build_gamedata, map_list};
+use anyhow::{Context, Result};
+use hurrycurry_data::{PrivateGamedata, build_gamedata, load_map_metadata};
use hurrycurry_game_core::{Game, Item, Player};
use hurrycurry_locale::{
GLOBAL_LOCALE, TrError,
message::{COLORED, MessageDisplayExt},
- tre,
+ tre, trm,
};
use hurrycurry_protocol::{
- Character, Gamedata, Hand, ItemLocation, Menu, MessageTimeout, PacketC, PacketS, PlayerClass,
- PlayerID, Score, Serverdata, VoteSubject, glam::Vec2, movement::MovementBase,
+ Character, GameConfig, Gamedata, ItemLocation, Menu, MessageTimeout, PacketC, PacketS,
+ PlayerClass, PlayerID, Score, Serverdata, VoteSubject, glam::Vec2, movement::MovementBase,
};
use log::{info, warn};
use std::{
@@ -59,6 +59,10 @@ pub enum AnnounceState {
pub struct ServerConfig {
pub inactivity_timeout: f32,
+ pub vote_cast_cooldown: f32,
+ pub vote_initiate_cooldown: f32,
+ pub vote_timeout: f32,
+ pub skip_voting: bool,
pub lobby: String,
pub data_path: PathBuf,
pub motd: Option<String>,
@@ -66,7 +70,7 @@ pub struct ServerConfig {
}
pub struct Server {
- pub config: ServerConfig,
+ pub config: Arc<ServerConfig>,
pub tick_perf: (Duration, usize),
pub broadcast: broadcast::Sender<PacketC>,
pub connections: HashMap<ConnectionID, ConnectionData>,
@@ -88,6 +92,7 @@ pub struct Server {
impl Server {
pub fn new(config: ServerConfig, tx: broadcast::Sender<PacketC>) -> Result<Self> {
+ let config = Arc::new(config);
Ok(Self {
data: Box::new(Serverdata {
bot_algos: vec![
@@ -96,12 +101,12 @@ impl Server {
"dishwasher".to_string(),
"frank".to_string(),
],
- maps: map_list(&config.data_path)?,
+ maps: load_map_metadata(&config.data_path).context("map metadata")?,
motd: config.motd.clone(),
name: config.name.clone(),
}),
+ vote_state: VoteState::new(config.clone()),
config,
- vote_state: VoteState::default(),
game: Game::default(),
tick_perf: (Duration::ZERO, 0),
broadcast: tx,
@@ -127,6 +132,10 @@ impl Default for ServerConfig {
lobby: "lobby".to_string(),
name: "A Hurry Curry! Server".to_string(),
motd: None,
+ skip_voting: false,
+ vote_cast_cooldown: 20.,
+ vote_initiate_cooldown: 30.,
+ vote_timeout: 20.,
}
}
}
@@ -136,7 +145,6 @@ pub trait GameServerExt {
&mut self,
gamedata: Gamedata,
serverdata: &PrivateGamedata,
- timer: Option<Duration>,
packet_out: &mut VecDeque<PacketC>,
is_lobby: bool,
);
@@ -156,7 +164,6 @@ impl GameServerExt for Game {
&mut self,
gamedata: Gamedata,
serverdata: &PrivateGamedata,
- timer: Option<Duration>,
packet_out: &mut VecDeque<PacketC>,
is_lobby: bool,
) {
@@ -173,7 +180,7 @@ impl GameServerExt for Game {
self.data = gamedata.into();
self.data_index.update(&self.data);
self.score = Score {
- time_remaining: timer.map(|dur| dur.as_secs_f64()).unwrap_or(0.),
+ time_remaining: serverdata.timer,
..Default::default()
};
@@ -231,20 +238,15 @@ impl GameServerExt for Game {
}
impl Server {
- pub fn load_map(&mut self, name: &str) -> Result<()> {
- self.load(
- build_gamedata(&self.config.data_path, name, true)
- .context(anyhow!("preparing gamedata for {:?}", name))?,
- None,
- );
- Ok(())
- }
- pub fn load(
- &mut self,
- (gamedata, serverdata): (Gamedata, PrivateGamedata),
- timer: Option<Duration>,
- ) {
- info!("Changing map to {:?}", gamedata.current_map);
+ pub fn load(&mut self, config: GameConfig) -> Result<(), TrError> {
+ if self.game.data.metadata.name == config.map {
+ info!("Reloading map {:?}", config.map);
+ } else {
+ info!("Changing map to {:?}", config.map);
+ }
+ let (gamedata, serverdata) = build_gamedata(&self.config.data_path, &config, true)
+ .map_err(|m| tre!("s.error.map_load", s = format!("{m:#}")))?;
+
for mut e in self.entities.drain(..) {
e.destructor(EntityContext {
game: &mut self.game,
@@ -264,18 +266,20 @@ impl Server {
warn!("Internal entity destructor packet errored: {e}");
}
}
- let is_lobby = gamedata.current_map == self.config.lobby;
- self.game.load(
- gamedata,
- &serverdata,
- timer.or(serverdata.default_timer),
- &mut self.packet_out,
- is_lobby,
- );
+ let is_lobby = gamedata.metadata.name == self.config.lobby;
+ self.game
+ .load(gamedata, &serverdata, &mut self.packet_out, is_lobby);
+ self.game.config = config;
self.entities.clear();
for ed in &serverdata.entity_decls {
self.entities.push(construct_entity(ed));
}
+ if !self.game.config.is_valid_for_scoreboard() {
+ self.packet_out.push_back(PacketC::ServerMessage {
+ message: trm!("s.custom_game_warn"),
+ error: false,
+ });
+ }
self.priv_gamedata = serverdata.into();
for e in &mut self.entities {
e.constructor(EntityContext {
@@ -297,6 +301,7 @@ impl Server {
AnnounceState::Queued
};
self.update_paused();
+ Ok(())
}
pub fn packet_in(
@@ -333,7 +338,9 @@ impl Server {
position,
);
info!("{player} joined");
- self.vote_state.join(player);
+ if class == PlayerClass::Chef {
+ self.vote_state.join(player);
+ }
replies.push(PacketC::Joined { id: player })
}
PacketS::Leave { player } => {
@@ -346,11 +353,9 @@ impl Server {
self.last_movement_update.remove(&player);
self.vote_state.leave(player);
}
- PacketS::Effect { player, name } => {
- self.packet_out.push_back(PacketC::Effect2 {
- name,
- location: ItemLocation::Player(player, Hand(0)),
- });
+ PacketS::Effect { effect, location } => {
+ self.packet_out
+ .push_back(PacketC::Effect { effect, location });
}
PacketS::Movement {
pos,
@@ -388,6 +393,7 @@ impl Server {
player,
hand,
} => {
+ let mut entity_handled = false;
for e in &mut self.entities {
if e.interact(
EntityContext {
@@ -404,9 +410,13 @@ impl Server {
target,
player,
)? {
- return Ok(());
+ // multiple entities might handle this in case of a button tutorial
+ entity_handled = true;
}
}
+ if entity_handled {
+ return Ok(());
+ }
let pid = player;
let player = self
@@ -516,8 +526,8 @@ impl Server {
Ok(())
}
- /// Returns Some(map) if the game should end
- pub fn tick(&mut self, dt: f32) -> Option<(String, Option<Duration>)> {
+ /// Returns Some(config) if the game should end
+ pub fn tick(&mut self, dt: f32) -> Option<GameConfig> {
if self.score_changed {
self.score_changed = false;
self.packet_out
@@ -554,13 +564,12 @@ impl Server {
for (&pid, player) in &mut self.game.players {
if player.movement_input_changed || player.movement_must_sync {
player.movement_input_changed = false;
- self.packet_out
- .push_back(player.movement.movement_packet_c(pid));
- if player.movement_must_sync {
- player.movement_must_sync = false;
- self.packet_out
- .push_back(PacketC::MovementSync { player: pid });
- }
+ self.packet_out.push_back(
+ player
+ .movement
+ .movement_packet_c(pid, player.movement_must_sync),
+ );
+ player.movement_must_sync = false;
}
}
@@ -634,20 +643,29 @@ impl Server {
true
}
});
+ if let Some(config) = load_map {
+ return Some(config);
+ }
self.vote_state.tick(dt);
if let Some(subject) = self.vote_state.ratified.take() {
match subject {
- VoteSubject::StartGame { config } => load_map = Some(config.map),
- VoteSubject::EndGame => load_map = Some("lobby".to_string()),
- VoteSubject::RestartGame => load_map = Some(self.game.data.current_map.clone()),
+ VoteSubject::StartGame { config } => return Some(config),
+ VoteSubject::EndGame => {
+ return Some(GameConfig {
+ map: self.config.lobby.to_owned(),
+ ..Default::default()
+ });
+ }
+ VoteSubject::RestartGame => {
+ return Some(GameConfig {
+ map: self.game.data.metadata.name.to_owned(),
+ ..Default::default()
+ });
+ }
}
}
- if let Some(map) = load_map {
- return Some((map, None));
- }
-
while let Some(p) = self.packet_loopback.pop_front() {
if let Err(e) = self.packet_in(&mut vec![], p) {
warn!("Loopback packet errored: {e}");
@@ -655,7 +673,7 @@ impl Server {
}
if !self.game.lobby {
- self.game.score.time_remaining -= dt as f64;
+ self.game.score.time_remaining -= dt;
if self.game.score.time_remaining < 0. {
let relative_score =
(self.game.score.points * 100) / self.priv_gamedata.score_baseline.max(1);
@@ -668,7 +686,7 @@ impl Server {
self.packet_out
.push_back(PacketC::Menu(Menu::Score(self.game.score.clone())));
self.scoreboard.insert(
- &self.game.data.current_map,
+ &self.game.config,
self.game
.players
.values()
@@ -677,7 +695,10 @@ impl Server {
.collect(),
self.game.score.clone(),
);
- Some((self.config.lobby.to_string(), None))
+ Some(GameConfig {
+ map: self.config.lobby.to_string(),
+ ..Default::default()
+ })
} else {
None
}
@@ -686,6 +707,13 @@ impl Server {
}
}
+ pub fn count_spectators(&self) -> usize {
+ self.connections
+ .values()
+ .filter(|c| c.players.is_empty())
+ .count()
+ }
+
pub fn count_chefs(&self) -> usize {
self.game
.players
diff --git a/server/src/state.rs b/server/src/state.rs
index f26275e3..e090c1f2 100644
--- a/server/src/state.rs
+++ b/server/src/state.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -20,10 +20,11 @@ use crate::{
server::{AnnounceState, ConnectionData, Server},
};
use anyhow::Result;
-use hurrycurry_data::build_gamedata;
use hurrycurry_locale::{TrError, tre, trm};
-use hurrycurry_protocol::{KEEPALIVE_INTERVAL, Menu, Message, PacketC, PacketS, PlayerID, version};
-use log::{debug, info, trace, warn};
+use hurrycurry_protocol::{
+ GameConfig, KEEPALIVE_INTERVAL, Menu, Message, PacketC, PacketS, PlayerID, version,
+};
+use log::{debug, error, info, trace, warn};
use std::{
collections::HashSet,
time::{Duration, Instant},
@@ -74,9 +75,15 @@ impl Server {
debug!("tick perf {:?}", self.tick_perf.0 / 500);
self.tick_perf = (Duration::ZERO, 0);
}
- if let Some((name, timer)) = r {
+ if let Some(config) = r {
self.scoreboard.save()?;
- self.load(build_gamedata(&self.config.data_path, &name, true)?, timer);
+ if let Err(e) = self.load(config) {
+ error!("Failed to load map: {e}");
+ self.packet_out.push_back(PacketC::ServerMessage {
+ message: e.into(),
+ error: true,
+ });
+ }
}
}
@@ -100,7 +107,10 @@ impl Server {
self.packet_out.extend(self.game.events.drain(..));
self.packet_out.extend(self.vote_state.packet_out.drain(..));
while let Some(p) = self.packet_out.pop_front() {
- if matches!(p, PacketC::UpdateMap { .. } | PacketC::Movement { .. }) {
+ if matches!(
+ p,
+ PacketC::UpdateMap { .. } | PacketC::Movement { .. } | PacketC::Debug(_)
+ ) {
trace!("-> {p:?}");
} else {
debug!("-> {p:?}");
@@ -135,6 +145,9 @@ impl Server {
},
);
self.update_paused();
+ self.packet_out.push_back(PacketC::SpectatorCount {
+ count: self.count_spectators(),
+ });
(init, broadcast_rx, replies_rx)
}
@@ -148,6 +161,9 @@ impl Server {
}
self.connections.remove(&conn);
self.update_paused();
+ self.packet_out.push_back(PacketC::SpectatorCount {
+ count: self.count_spectators(),
+ });
}
}
@@ -199,6 +215,9 @@ impl Server {
.unwrap()
.players
.remove(player);
+ self.packet_out.push_back(PacketC::SpectatorCount {
+ count: self.count_spectators(),
+ });
}
PacketS::Join { .. } => {
if conn_data.players.len() > 8 {
@@ -221,6 +240,8 @@ impl Server {
_ => (),
}
+ let is_leave = matches!(packet, PacketS::Leave { .. });
+
self.packet_in(&mut replies, packet)?;
for p in &replies {
@@ -228,21 +249,25 @@ impl Server {
self.connections.get_mut(&conn).unwrap().players.insert(*id);
self.player_inactivity_timers
.insert(*id, self.config.inactivity_timeout);
+ self.packet_out.push_back(PacketC::SpectatorCount {
+ count: self.count_spectators(),
+ });
}
}
- if self.count_chefs() == 0 && !self.game.lobby {
+ if is_leave && self.count_chefs() == 0 {
self.broadcast
.send(PacketC::ServerMessage {
message: trm!("s.state.abort_no_players"),
error: false,
})
.ok();
- self.load(
- build_gamedata(&self.config.data_path, &self.config.lobby, true)
- .map_err(|m| tre!("s.error.map_load", s = format!("{m}")))?,
- None,
- );
+ if let Err(e) = self.load(GameConfig {
+ map: self.config.lobby.to_string(),
+ ..Default::default()
+ }) {
+ error!("Failed to load lobby: {e}");
+ }
}
Ok(replies)
}
@@ -309,12 +334,12 @@ fn get_packet_player(packet: &PacketS) -> Option<PlayerID> {
| PacketS::Communicate { player, .. }
| PacketS::ReplaceHand { player, .. }
| PacketS::InitiateVote { player, .. }
- | PacketS::CastVote { player, .. }
- | PacketS::Effect { player, .. } => Some(*player),
+ | PacketS::CastVote { player, .. } => Some(*player),
PacketS::Join { .. }
| PacketS::Idle { .. }
| PacketS::Ready
| PacketS::ApplyScore(_)
+ | PacketS::Effect { .. }
| PacketS::ReplayTick { .. }
| PacketS::Debug(_)
| PacketS::Keepalive => None,
diff --git a/server/src/vote.rs b/server/src/vote.rs
index e9d7c118..e0805c52 100644
--- a/server/src/vote.rs
+++ b/server/src/vote.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -16,6 +16,7 @@
*/
+use crate::server::ServerConfig;
use hurrycurry_locale::{
GLOBAL_LOCALE, TrError,
message::{COLORED, MessageDisplayExt},
@@ -23,10 +24,13 @@ use hurrycurry_locale::{
};
use hurrycurry_protocol::{Message, PacketC, PacketS, PlayerID, VoteSubject};
use log::info;
-use std::collections::{HashMap, HashSet, VecDeque};
+use std::{
+ collections::{HashMap, HashSet, VecDeque},
+ sync::Arc,
+};
-#[derive(Default)]
pub struct VoteState {
+ config: Arc<ServerConfig>,
initiate_cooldown: HashMap<PlayerID, f32>,
cast_cooldown: HashMap<PlayerID, f32>,
voting_players: HashSet<PlayerID>,
@@ -42,9 +46,22 @@ pub struct ActiveVote {
}
impl VoteState {
+ pub fn new(config: Arc<ServerConfig>) -> Self {
+ Self {
+ config,
+ initiate_cooldown: Default::default(),
+ cast_cooldown: Default::default(),
+ voting_players: Default::default(),
+ active: Default::default(),
+ ratified: Default::default(),
+ packet_out: Default::default(),
+ }
+ }
pub fn join(&mut self, player: PlayerID) {
- self.cast_cooldown.insert(player, 30.);
- self.initiate_cooldown.insert(player, 40.);
+ self.cast_cooldown
+ .insert(player, self.config.vote_cast_cooldown);
+ self.initiate_cooldown
+ .insert(player, self.config.vote_initiate_cooldown);
self.update();
}
pub fn leave(&mut self, player: PlayerID) {
@@ -82,9 +99,15 @@ impl VoteState {
"{player} initiates vote for {}",
subject_to_message(&subject).display_nodata(&GLOBAL_LOCALE, &COLORED)
);
- self.initiate_cooldown.insert(player, 40.);
+ if self.voting_players.len() == 1 {
+ info!("Vote passed (skipped)");
+ self.ratified = Some(subject);
+ return Ok(());
+ }
+ self.initiate_cooldown
+ .insert(player, self.config.vote_initiate_cooldown);
self.active = Some(ActiveVote {
- timeout: 30.,
+ timeout: self.config.vote_timeout,
initiated_by: player,
ballots: [(player, true)].into_iter().collect(), // assuming initiating player agrees with their own vote
subject: subject.clone(),
@@ -93,7 +116,7 @@ impl VoteState {
initiated_by: player,
message: subject_to_message(&subject),
subject,
- timeout: 30.,
+ timeout: self.config.vote_timeout,
});
self.update();
}
@@ -152,14 +175,15 @@ impl VoteState {
let total = self.voting_players.len();
let mut agree = 0;
let mut reject = 0;
- for (_, v) in &a.ballots {
- if *v {
+ for &v in a.ballots.values() {
+ if v {
agree += 1;
} else {
reject += 1;
}
}
- if agree * 2 > total || reject * 2 > total || a.timeout <= 0. {
+ if agree * 2 > total || reject * 2 > total || a.timeout <= 0. || self.config.skip_voting
+ {
let result = agree > reject;
if result {
info!("Vote passed");
@@ -189,3 +213,76 @@ fn subject_to_message(s: &VoteSubject) -> Message {
VoteSubject::RestartGame => trm!("s.vote.subject.restart_game"),
}
}
+
+#[cfg(test)]
+mod test {
+ use crate::{server::ServerConfig, vote::VoteState};
+ use hurrycurry_protocol::{PacketS, PlayerID, VoteSubject};
+
+ #[test]
+ fn initial_cast_cooldown() {
+ let mut vs = VoteState::new(ServerConfig::default().into());
+ assert_eq!(vs.voting_players.len(), 0);
+ vs.join(PlayerID(0));
+ assert_eq!(vs.voting_players.len(), 1);
+ vs.join(PlayerID(1));
+ assert_eq!(vs.voting_players.len(), 1);
+ vs.tick(60.);
+ assert_eq!(vs.voting_players.len(), 2);
+ }
+
+ #[test]
+ fn initial_initiate_cooldown() {
+ let mut vs = VoteState::new(ServerConfig::default().into());
+ assert_eq!(vs.initiate_cooldown.len(), 0);
+ vs.join(PlayerID(0));
+ assert_eq!(vs.initiate_cooldown.len(), 0);
+ vs.join(PlayerID(1));
+ assert_eq!(vs.initiate_cooldown.len(), 1);
+ vs.tick(60.);
+ assert_eq!(vs.initiate_cooldown.len(), 0);
+ }
+
+ #[test]
+ fn singleplayer_vote_skip() {
+ let mut vs = VoteState::new(ServerConfig::default().into());
+ vs.join(PlayerID(0));
+ vs.packet_in(PacketS::InitiateVote {
+ player: PlayerID(0),
+ subject: VoteSubject::EndGame,
+ })
+ .unwrap();
+ assert_eq!(vs.ratified, Some(VoteSubject::EndGame));
+ }
+
+ #[test]
+ fn vote_timeout() {
+ let mut vs = VoteState::new(ServerConfig::default().into());
+ vs.join(PlayerID(0));
+ vs.join(PlayerID(1));
+ vs.tick(60.);
+ vs.packet_in(PacketS::InitiateVote {
+ player: PlayerID(0),
+ subject: VoteSubject::EndGame,
+ })
+ .unwrap();
+ assert_eq!(vs.ratified, None);
+ vs.tick(60.);
+ assert_eq!(vs.ratified, Some(VoteSubject::EndGame));
+ }
+
+ #[test]
+ fn accept_on_leave() {
+ let mut vs = VoteState::new(ServerConfig::default().into());
+ vs.join(PlayerID(0));
+ vs.join(PlayerID(1));
+ vs.tick(60.);
+ vs.packet_in(PacketS::InitiateVote {
+ player: PlayerID(0),
+ subject: VoteSubject::EndGame,
+ })
+ .unwrap();
+ vs.leave(PlayerID(1));
+ assert_eq!(vs.ratified, Some(VoteSubject::EndGame));
+ }
+}
diff --git a/server/tools/src/diagram_dot.rs b/server/tools/src/diagram_dot.rs
index 33b1a1fb..1bab6a3d 100644
--- a/server/tools/src/diagram_dot.rs
+++ b/server/tools/src/diagram_dot.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/server/tools/src/graph.rs b/server/tools/src/graph.rs
index 72eab3fe..ca523427 100644
--- a/server/tools/src/graph.rs
+++ b/server/tools/src/graph.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -17,12 +17,19 @@
*/
use anyhow::Result;
use hurrycurry_data::build_gamedata;
-use hurrycurry_protocol::{Demand, ItemIndex, Recipe, RecipeIndex};
+use hurrycurry_protocol::{Demand, GameConfig, ItemIndex, Recipe, RecipeIndex};
pub(crate) fn graph() -> Result<()> {
println!("digraph {{");
- let (data, _) = build_gamedata("data".as_ref(), "5star", true)?;
+ let (data, _) = build_gamedata(
+ "data".as_ref(),
+ &GameConfig {
+ map: "5star".to_string(),
+ ..Default::default()
+ },
+ true,
+ )?;
for i in 0..data.item_names.len() {
println!("i{i} [label=\"{}\"]", data.item_name(ItemIndex(i)))
}
diff --git a/server/tools/src/graph_summary.rs b/server/tools/src/graph_summary.rs
index 2227a1c4..00ac5ca6 100644
--- a/server/tools/src/graph_summary.rs
+++ b/server/tools/src/graph_summary.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -18,13 +18,20 @@
use anyhow::Result;
use hurrycurry_data::build_gamedata;
-use hurrycurry_protocol::{ItemIndex, Recipe, TileIndex};
+use hurrycurry_protocol::{GameConfig, ItemIndex, Recipe, TileIndex};
use std::collections::HashSet;
pub(crate) fn graph_summary() -> Result<()> {
println!("digraph {{");
- let (data, sdata) = build_gamedata("data".as_ref(), "5star", true)?;
+ let (data, sdata) = build_gamedata(
+ "data".as_ref(),
+ &GameConfig {
+ map: "5star".to_string(),
+ ..Default::default()
+ },
+ true,
+ )?;
struct Node {
inputs: Vec<ItemIndex>,
diff --git a/server/tools/src/main.rs b/server/tools/src/main.rs
index 2c45f57b..5b365f35 100644
--- a/server/tools/src/main.rs
+++ b/server/tools/src/main.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -30,8 +30,10 @@ use anyhow::Result;
use clap::Parser;
use hurrycurry_data::{
book::{diagram_layout::diagram_layout, recipe_diagram::recipe_diagram},
- build_gamedata,
+ build_gamedata, map_list,
};
+use hurrycurry_protocol::GameConfig;
+use std::collections::HashSet;
#[derive(Parser)]
enum Action {
@@ -45,9 +47,11 @@ enum Action {
MapDemands {
map: String,
},
+ AllItems,
MapItems {
map: String,
},
+ AllTiles,
MapTiles {
map: String,
},
@@ -64,7 +68,8 @@ fn main() -> Result<()> {
Action::Graph => graph()?,
Action::GraphSummary => graph_summary()?,
Action::GraphSingle { out, dot_out } => {
- let (data, serverdata) = build_gamedata("data".as_ref(), "5star", true)?;
+ let (data, serverdata) =
+ build_gamedata("data".as_ref(), &GameConfig::new_map("5star"), true)?;
let out = data.get_item_by_name(&out).unwrap();
let mut diagram = recipe_diagram(&data, &serverdata, &[out])?;
let out = if dot_out {
@@ -76,33 +81,43 @@ fn main() -> Result<()> {
println!("{out}");
}
Action::MapDemands { map } => {
- let (data, _) = build_gamedata("data".as_ref(), &map, true)?;
+ let (data, _) = build_gamedata("data".as_ref(), &GameConfig::new_map(&map), true)?;
for demand in &data.demands {
println!("{}", data.item_name(demand.input))
}
}
Action::MapItems { map } => {
- let (data, _) = build_gamedata("data".as_ref(), &map, true)?;
-
+ let (data, _) = build_gamedata("data".as_ref(), &GameConfig::new_map(&map), false)?;
for name in &data.item_names {
println!("{name}")
}
}
Action::MapTiles { map } => {
- let (data, _) = build_gamedata("data".as_ref(), &map, true)?;
+ let (data, _) = build_gamedata("data".as_ref(), &GameConfig::new_map(&map), false)?;
for name in &data.tile_names {
println!("{name}")
}
}
- Action::CheckMap { map: _ } => {
- // if map == "all" {
- // let index = DataIndex::new("data".into())?;
- // for map in index.maps.keys() {
- // check_map(map)?;
- // }
- // } else {
- // check_map(&map)?;
- // }
+ Action::CheckMap { map: _ } => {}
+ Action::AllItems => {
+ let mut items = HashSet::new();
+ for map in map_list("data".as_ref())? {
+ let (data, _) = build_gamedata("data".as_ref(), &GameConfig::new_map(&map), false)?;
+ items.extend(data.item_names);
+ }
+ for n in items {
+ println!("{n}")
+ }
+ }
+ Action::AllTiles => {
+ let mut tiles = HashSet::new();
+ for map in map_list("data".as_ref())? {
+ let (data, _) = build_gamedata("data".as_ref(), &GameConfig::new_map(&map), false)?;
+ tiles.extend(data.tile_names);
+ }
+ for n in tiles {
+ println!("{n}")
+ }
}
}
Ok(())
diff --git a/server/tools/src/map_linter.rs b/server/tools/src/map_linter.rs
index 41f1e925..f1e315e3 100644
--- a/server/tools/src/map_linter.rs
+++ b/server/tools/src/map_linter.rs
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/test-client/.gitignore b/test-client/.gitignore
index 27b9b44d..d12afe29 100644
--- a/test-client/.gitignore
+++ b/test-client/.gitignore
@@ -1,2 +1,3 @@
-/locale
-/*.js
+/assets/locale*
+/main.js
+/main.js.map
diff --git a/test-client/announcement.ts b/test-client/announcement.ts
new file mode 100644
index 00000000..1c813fda
--- /dev/null
+++ b/test-client/announcement.ts
@@ -0,0 +1,77 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { PacketC } from "./protocol.ts";
+import { System } from "./system.ts";
+
+export class SAnnouncement extends System {
+ timer = 0
+ active = false
+
+ override packet(p: PacketC): void {
+ if (p.type == "menu" && p.menu == "announce_start") {
+ this.active = true
+ this.timer = 3.5
+ }
+ }
+
+ override tick(dt: number): void {
+ if (this.active) {
+ this.timer -= dt
+ if (this.timer < -3)
+ this.active = false
+ }
+ }
+
+ override draw(ctx: CanvasRenderingContext2D): void {
+ if (!this.active) return
+ ctx.save()
+
+ ctx.fillStyle = "#ffffff"
+ ctx.strokeStyle = "#903544"
+ ctx.font = "80px sans-serif"
+ ctx.fillStyle = "white"
+ ctx.lineWidth = 10
+ ctx.textAlign = "center"
+ ctx.textBaseline = "middle"
+ ctx.lineJoin = "round"
+ ctx.lineCap = "round"
+ ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2)
+
+ for (const [text, t] of [["Ready?", 3.5], ["Go!", 1]] as const) {
+ const rt = t - this.timer;
+ if (rt < 0 || rt > 2) continue
+
+ const scale = rt < 0.5
+ ? 1 - Math.pow(1 - rt / 0.5, 3)
+ : 1 + Math.pow((rt - 0.5) / 1.5, 4)
+ const alpha = rt < 0.5
+ ? 1 - Math.pow(1 - rt / 0.5, 4)
+ : 1 - (rt - 0.5) / 1.5
+
+ ctx.save()
+ ctx.globalAlpha = alpha
+ ctx.scale(scale, scale)
+ ctx.strokeText(text, 0, 0)
+ ctx.fillText(text, 0, 0)
+ ctx.restore()
+ }
+
+ ctx.restore()
+ }
+}
diff --git a/test-client/assets/item_sprites.ini b/test-client/assets/item_sprites.ini
new file mode 100644
index 00000000..cf900188
--- /dev/null
+++ b/test-client/assets/item_sprites.ini
@@ -0,0 +1,169 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2026 Hurry Curry! Contributors
+#
+# 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/>.
+
+[pan]
+ f rgb(41, 41, 41)|s|p m -0.7 0.5 l -1.2 1.2 l 0.2 0.2 l 1.2 -1.2 z
+ f rgb(50, 50, 50)|as|p M -1.4 0 A 0.4 0.4 90 0 1 1.4 0 A 0.4 0.4 90 0 1 -1.4 0 Z
+ v sprite
+[pot]
+ f rgb(41, 41, 41)|s|p m -1.6 -0.4 h 3.2 v 0.8 h -3.2 z
+ f rgb(50, 50, 50)|as|p M -1.4 0 A 0.4 0.4 90 0 1 1.4 0 A 0.4 0.4 90 0 1 -1.4 0 Z
+ v arrange_sprites
+[glass]
+ f rgba(172, 227, 255, 0.58)|as|p M -0.9 0 A 0.1 0.1 0 0 0 0.9 0 A 0.1 0.1 0 0 0 -0.9 0
+ v arrange_sprites
+[plate]
+ s rgb(224, 224, 224)|f rgb(204, 204, 204)|lw 0.15|p M -1.2 0 A 0.4 0.4 90 0 1 1.2 0 A 0.4 0.4 90 0 1 -1.2 0 Z
+ v arrange_sprites
+[dirty-plate]
+ s rgb(205, 178, 178)|f rgb(198, 167, 167)|lw 0.15|p M -1.2 0 A 0.4 0.4 90 0 1 1.2 0 A 0.4 0.4 90 0 1 -1.2 0 Z
+ f rgb(122, 81, 62)|p M -0.6 0.3 A 0.01 0.01 0 0 0 -0.4 0.3 A 0.01 0.01 0 0 0 -0.6 0.3 M 0.1 0.4 A 0.01 0.01 0 0 0 0.3 0.4 A 0.01 0.01 0 0 0 0.1 0.4 M -0.3 -0.1 A 0.01 0.01 0 0 0 0 -0.1 A 0.01 0.01 0 0 0 -0.3 -0.1 M 0.4 0.1 A 0.01 0.01 0 0 0 0.6 0.1 A 0.01 0.01 0 0 0 0.4 0.1 M 0.2 -0.3 A 0.01 0.01 0 0 0 0.3 -0.4 A 0.01 0.01 0 0 0 0.2 -0.3
+[foodprocessor]
+ f rgb(65, 136, 223)|as|p M -1.4 -1.4 H 1.4 V 1.4 H -1.4 Z
+ s rgb(172, 227, 255)|p M -1.1 0 A 0.1 0.1 0 0 0 1.1 0 A 0.1 0.1 0 0 0 -1.1 0
+ v arrange_sprites
+[basket]
+ s rgb(75, 77, 78)|p M -1.1 -1.1 H 1.1 V 1.1 H -1.1 Z
+ s rgb(75, 77, 78)|lw 0.01|p M -0.5 -1 L 1 0.5 L 0.5 1 L -1 -0.5 Z M -1 0 L 0 -1 L 1 0 L 0 1 Z M 0.5 -1 L 1 -0.5 L -0.5 1 L -1 0.5 Z M -1 -1 L 1 1 M 1 -1 L -1 1
+ f rgb(75, 77, 78)|p M -0.2 1.1 V 2.1 H 0.2 V 1.1
+ v sprite
+
+[tomato]
+ f rgb(209, 15, 15)|as|p M -0.9 0 A 0.4 0.4 90 0 1 0.9 0 A 0.4 0.4 90 0 1 -0.9 0 Z
+ s rgb(37, 140, 8)|p M -0.2 -0.6 L 0 -0.7 M 0 -0.7 L -0.1 -0.9 M 0 -0.7 L 0.2 -0.6 M 0 -0.7 L 0.2 -0.9
+[sliced-tomato]
+ s rgb(209, 15, 15)|f rgb(202, 92, 92)|p M -1 0 A 0.4 0.4 90 0 1 1 0 A 0.4 0.4 90 0 1 -1 0 Z
+[lettuce]
+ f rgb(87, 222, 46)|as|p M -1.35 0.15 C -1.35 -0.15 -1.5 -0.15 -0.9 -0.75 S 0.15 -1.2 0.75 -1.05 S 1.35 0 1.35 0.45 S 0.75 1.65 0 1.5 S -1.35 0.9 -1.35 0.15
+ s rgb(132, 255, 70)|lw 0.02|p M -0.3 0.9 Q -0.75 0.6 -0.75 0 M 0.45 0.45 Q 0 0.75 -0.45 0.45 M 0 0.3 Q -0.3 0.3 -0.3 0 M -0.45 -0.3 Q 0 -0.6 0.6 -0.3 M 0.45 -0.15 Q 0.75 0.15 0.6 0.75 M 0.15 -0.15 Q 0.45 0 0.3 0.3 M -1.05 0.3 Q -0.9 -0.6 0 -0.75 M 0.9 -0.45 Q 1.2 0.3 0.75 1.05 M 0.45 1.2 Q -0.45 1.35 -0.9 0.6
+[sliced-lettuce]
+ f rgb(87, 222, 46)|as|p m -0.9 0.6 c 0 -0.3 0.3 -0.9 0.6 -1.2 c 0.3 -0.3 0.75 -0.45 0.9 -0.3 c 0.3 0.15 0.6 0.6 0.45 0.9 c -0.15 0.3 -0.3 0.45 -0.6 0.6 c -0.3 0.15 -0.3 0 -0.75 0.15 c -0.3 0.15 -0.6 0.15 -0.6 -0.15
+ s rgb(158, 228, 123)|lw 0.03|p M -0.7 0.5 C -0.2 -0.2 0.1 -0.2 0.4 -0.7 M -0.6 0.6 C -0.2 0.3 0.3 0.1 0.9 -0.1 M -0.2 0.2 C 0.1 0 0.5 -0.1 0.7 -0.5
+[steak]
+ f rgb(255, 164, 162)|s rgb(247, 216, 204)|p M 1.2 -0.5 C 0.9 -0.8 0 -0.8 -0.7 -0.6 S -1.4 0 -1.2 0.4 S -0.4 0.8 0.4 0.7 S 1.7 0 1.4 -0.3 Z
+ s rgb(247, 216, 204)|p M -0.2 0 L 0.6 -0.1 L 0.8 -0.3 L 0.9 0.1
+[seared-steak]
+ f rgb(122, 56, 55)|s rgb(157, 110, 92)|p M 1.2 -0.5 C 0.9 -0.8 0 -0.8 -0.7 -0.6 S -1.4 0 -1.2 0.4 S -0.4 0.8 0.4 0.7 S 1.7 0 1.4 -0.3 Z
+ s rgb(157, 110, 92)|p M -0.2 0 L 0.6 -0.1 L 0.8 -0.3 L 0.9 0.1
+[flour]
+ f rgb(138, 98, 55)|as|p M 0.8 -0.4 C 1 0 1 0.6 0.9 1 C 0.3 1.2 -0.3 1.2 -0.9 1 C -1 0.6 -1 0 -0.8 -0.4 Z
+ f rgb(138, 98, 55)|as|p M -0.7 -0.2 Q -0.8 -0.3 -0.7 -0.4 C -0.2 -0.5 0.2 -0.5 0.7 -0.4 Q 0.8 -0.3 0.7 -0.2 C 0.2 -0.1 -0.2 -0.1 -0.7 -0.2 Z
+ f rgb(225, 225, 225)|p M -0.7 -0.2 Q -0.4 -0.5 0 -0.5 Q 0.4 -0.5 0.7 -0.2 Q 0 0 -0.7 -0.2 Z
+[dough]
+ f rgb(219, 185, 148)|as|p M -0.9 0 A 0.1 0.1 0 0 0 0.9 0 A 0.1 0.1 0 0 0 -0.9 0
+[bun]
+ f rgb(188, 103, 49)|as|p M -1.1 0 C -1.1 1.2 1.1 1.2 1.1 0 S -1.1 -1.2 -1.1 0
+[sliced-bun]
+ f rgb(188, 103, 49)|as|p M -1.1 0 C -1.1 1.2 1.1 1.2 1.1 0 Z
+ f rgb(214, 148, 106)|as|p M -1.1 0 C -1.1 0.3 1.1 0.3 1.1 0 C 1.1 -0.3 -1.1 -0.3 -1.1 0 Z
+[rolled-dough]
+ f rgb(236, 201, 141)|as|p M -1.4 0 A 0.1 0.1 0 0 0 1.4 0 A 0.1 0.1 0 0 0 -1.4 0
+ v arrange_sprites
+[pizza]
+ f rgb(220, 143, 61)|s rgb(123, 63, 22)|p M -1.4 0 A 0.1 0.1 0 0 0 1.4 0 A 0.1 0.1 0 0 0 -1.4 0
+ v arrange_sprites
+[rice]
+ f rgb(138, 98, 55)|as|p M 0.8 -0.4 C 1 0 1 0.6 0.9 1 C 0.3 1.2 -0.3 1.2 -0.9 1 C -1 0.6 -1 0 -0.8 -0.4 Z
+ f rgb(138, 98, 55)|as|p M -0.7 -0.2 Q -0.8 -0.3 -0.7 -0.4 C -0.2 -0.5 0.2 -0.5 0.7 -0.4 Q 0.8 -0.3 0.7 -0.2 C 0.2 -0.1 -0.2 -0.1 -0.7 -0.2 Z
+ f rgb(238, 200, 143)|p M -0.7 -0.2 Q -0.4 -0.5 0 -0.5 Q 0.4 -0.5 0.7 -0.2 Q 0 0 -0.7 -0.2 Z
+[cooked-rice]
+ f rgb(255, 243, 231)|as|p M -0.9 0 A 0.1 0.1 0 0 0 0.9 0 A 0.1 0.1 0 0 0 -0.9 0
+[rice-flour]
+ f rgb(243, 221, 148)|lw 0.02|as|p M -0.9 0 A 0.1 0.1 0 0 0 0.9 0 A 0.1 0.1 0 0 0 -0.9 0
+[mochi-dough]
+ f rgb(250, 245, 115)|lw 0.02|as|p M -0.9 0 A 0.1 0.1 0 0 0 0.9 0 A 0.1 0.1 0 0 0 -0.9 0
+[strawberry-mochi]
+ f rgb(255, 134, 215)|lw 0.02|as|p M -0.6 0 A 0.1 0.1 0 0 0 0.6 0 A 0.1 0.1 0 0 0 -0.6 0
+[mushroom]
+ f rgb(202, 178, 165)|as|p m -0.4 0 h 0.8 v 1.2 h -0.8 z
+ f rgb(98, 50, 25)|as|p m -1.2 0 a 0.4 0.4 90 0 1 2.4 0 z
+[sliced-mushroom]
+ f rgb(202, 178, 165)|s rgb(98, 50, 25)|p m -1.2 0 a 0.4 0.4 90 0 1 2.4 0 h -0.8 v 1.2 h -0.8 v -1.2 z
+[processed-mushroom]
+ f rgb(125, 79, 55)|lw 0.02|as|p M -0.9 0 A 0.1 0.1 0 0 0 0.9 0 A 0.1 0.1 0 0 0 -0.9 0
+[patty]
+ f rgb(255, 164, 162)|as|p M -0.9 0 A 0.1 0.1 0 0 0 0.9 0 A 0.1 0.1 0 0 0 -0.9 0
+[seared-patty]
+ f rgb(122, 56, 55)|as|p M -0.9 0 A 0.1 0.1 0 0 0 0.9 0 A 0.1 0.1 0 0 0 -0.9 0
+[cheese]
+ f rgb(243, 191, 48)|as|p M -0.8 0.8 L 0.7 0.5 L 0.7 -0.2 L -0.8 0.1 Z M -0.8 0.1 L 0.1 -0.6 L 0.7 -0.2 Z
+ s rgb(201, 157, 34)|lw 0.05|p M -0.6 0.5 L -0.59 0.5 M -0.2 0.2 L -0.19 0.2 M 0.3 0.3 L 0.31 0.3 M 0 -0.3 L 0.01 -0.3
+[sliced-cheese]
+ f rgb(243, 191, 48)|as|p M -0.7 -0.7 H 0.7 V 0.7 H -0.7 Z
+ s rgb(201, 157, 34)|lw 0.05|p M -0.4 0.4 L -0.41 0.4 M -0.3 -0.2 L -0.31 -0.2 M 0.3 -0.4 L 0.31 -0.4 M 0.2 0.2 L 0.21 0.2 M 0.5 0.6 L 0.51 0.6
+[fish]
+ f rgb(59, 95, 129)|as|p M -1.4 0.3 Q -1.5 0 -1.4 -0.3 C -1.2 -0.3 -1.1 -0.1 -0.9 -0.1 S 0.3 -0.4 0.9 -0.3 S 1.5 0.2 0.9 0.3 S -0.7 0.1 -0.9 0.1 S -1.2 0.3 -1.4 0.3
+[sliced-fish]
+ f rgb(255, 161, 127)|as|p M -0.9 -0.6 H 0.9 V 0.6 H -0.9 Z
+ s rgb(255, 141, 99)|lw 0.05|p M -0.7 -0.4 V 0.4 M -0.4 -0.4 V 0.4 M -0.1 -0.4 V 0.4 M 0.2 -0.4 V 0.4 M 0.5 -0.4 V 0.4
+[nigiri]
+ f rgb(250, 243, 229)|as|p M -0.7 0 C -0.7 0.6 0.7 0.6 0.7 0 S -0.7 -0.6 -0.7 0
+ f rgb(255, 161, 127)|as|p M -0.8 0 Q 0 -0.6 0.8 0 L 1 -0.1 Q 0 -0.9 -1 -0.1 L -0.8 0
+[potato]
+ f rgb(163, 121, 47)|as|p M -1 0.3 C -0.9 0.7 -0.1 0.7 0.4 0.6 S 1.1 0.2 1.2 -0.3 S 0.4 -1.2 -0.2 -1.1 S -1.1 -0.1 -1 0.3
+[sliced-potato]
+ f rgb(255, 231, 145)|as|lw 0.02|p M -1.1 -0.2 L -0.9 0 L 0 -0.9 L -0.2 -1.1 Z M -0.7 1 L -0.4 1 L -0.4 -0.1 L -0.7 -0.1 Z M -0.3 0.2 L 0.5 1.1 L 0.7 0.9 L -0.1 0 Z M -0.2 -0.3 L 0.4 -1.1 L 0.6 -1 L 0 -0.2 Z M 0.3 -0.4 L 0.7 0.6 L 0.9 0.5 L 0.5 -0.5 Z
+[french-fries]
+ f rgb(228, 140, 52)|as|lw 0.02|p M -1.1 -0.2 L -0.9 0 L 0 -0.9 L -0.2 -1.1 Z M -0.7 1 L -0.4 1 L -0.4 -0.1 L -0.7 -0.1 Z M -0.3 0.2 L 0.5 1.1 L 0.7 0.9 L -0.1 0 Z M -0.2 -0.3 L 0.4 -1.1 L 0.6 -1 L 0 -0.2 Z M 0.3 -0.4 L 0.7 0.6 L 0.9 0.5 L 0.5 -0.5 Z
+[leek]
+ f rgb(193, 226, 208)|as|lw 0.02|p M -1.1 0.9 L 0.1 -0.3 L 0.3 -0.1 L -0.9 1.1 Z
+ s rgb(32, 144, 81)|lw 0.05|p M -0.2 0.1 Q 0.2 -0.4 0.2 -0.9 M -0.1 0.1 Q 0.3 -0.4 0.5 -0.9 M -0.1 0.2 Q 0.5 -0.3 0.8 -0.8 M -0.2 0.2 Q 0.5 0 0.9 -0.5
+[sliced-leek]
+ f rgb(193, 226, 208)|as|lw 0.02|p M -1.1 0.4 A 0.1 0.1 0 0 0 -0.5 0.4 A 0.1 0.1 0 0 0 -1.1 0.4
+ f rgb(193, 226, 208)|as|lw 0.02|p M -0.7 0.2 A 0.1 0.1 0 0 0 -0.1 0.2 A 0.1 0.1 0 0 0 -0.7 0.2
+ f rgb(84, 175, 124)|as|lw 0.02|p M -0.3 0 A 0.1 0.1 0 0 0 0.3 0 A 0.1 0.1 0 0 0 -0.3 0
+ f rgb(32, 144, 81)|as|lw 0.02|p M 0.1 -0.2 A 0.1 0.1 0 0 0 0.7 -0.2 A 0.1 0.1 0 0 0 0.1 -0.2
+ f rgb(32, 144, 81)|as|lw 0.02|p M 0.5 -0.4 A 0.1 0.1 0 0 0 1.1 -0.4 A 0.1 0.1 0 0 0 0.5 -0.4
+[coconut]
+ f rgb(87, 52, 49)|as|p M -1.1 0 A 1 1 0 0 0 1.1 0 A 1 1 0 0 0 -1.1 0
+ s rgb(72, 40, 37)|lw 0.05|p M -0.2 -0.8 L -0.2 -0.8 M 0 -0.6 L 0 -0.6 M 0.2 -0.8 L 0.2 -0.8
+[strawberry]
+ f rgb(255, 68, 68)|as|p M -0.8 0.8 C -0.9 0.7 -0.8 -0.2 -0.5 -0.5 S 0.3 -0.7 0.5 -0.5 S 0.8 0.2 0.5 0.5 S -0.7 0.9 -0.8 0.8
+ f rgb(65, 190, 34)|as|lw 0.02|p M 0.4 -0.4 Q 0 -0.3 -0.3 -0.6 Q 0.2 -0.6 0.4 -0.4 Q 0.6 -0.2 0.6 0.2 Q 0.2 -0.2 0.4 -0.4 Q 0.7 -0.5 0.9 0 Q 0.5 -0.1 0.4 -0.4 Q 0.1 -0.5 -0.1 -0.9 Q 0.4 -0.7 0.4 -0.4
+[noodles]
+ s rgb(216, 194, 84)|lw 0.05|p M -0.7 0.4 C -0.4 0.8 0 0.9 0.5 0.9 S 1.2 0.4 0.9 0.1 S 0.1 0.2 -0.4 0.2 S -0.8 -0.4 -0.5 -0.6 S 0.5 -0.7 0.8 -0.3
+ s rgb(211, 195, 116)|lw 0.05|p M -0.7 0.2 C -0.5 0.7 -0.4 0.5 0 0.5 S 0.8 0.2 0.3 0.1 S -1 -0.2 -0.6 -0.3 S -0.2 -0.5 0.3 -0.4 S 0.9 0.2 0.7 0.6
+ s rgb(243, 223, 128)|lw 0.05|p M -0.3 -0.6 C -0.8 0.1 -0.7 0.5 -0.3 0.7 S 0 0.4 0.1 0 S 0 -0.7 0.4 -0.8 S 0.6 -0.5 0.6 -0.3 S 0.5 0.4 0.2 0.5
+[cooked-noodles]
+ s rgb(202, 172, 19)|lw 0.05|p M -0.7 0.4 C -0.4 0.8 0 0.9 0.5 0.9 S 1.2 0.4 0.9 0.1 S 0.1 0.2 -0.4 0.2 S -0.8 -0.4 -0.5 -0.6 S 0.5 -0.7 0.8 -0.3
+ s rgb(226, 198, 57)|lw 0.05|p M -0.7 0.2 C -0.5 0.7 -0.4 0.5 0 0.5 S 0.8 0.2 0.3 0.1 S -1 -0.2 -0.6 -0.3 S -0.2 -0.5 0.3 -0.4 S 0.9 0.2 0.7 0.6
+ s rgb(255, 221, 54)|lw 0.05|p M -0.3 -0.6 C -0.8 0.1 -0.7 0.5 -0.3 0.7 S 0 0.4 0.1 0 S 0 -0.7 0.4 -0.8 S 0.6 -0.5 0.6 -0.3 S 0.5 0.4 0.2 0.5
+[burned]
+ f rgb(50, 43, 43)|as|p M -1.2 0 C -1.2 0.75 1.2 0.75 1.2 0 C 1.2 -1.2 -1.2 -1.2 -1.2 0
+
+[mushroom-soup]
+ f rgb(183, 104, 62)|lw 0.02|as|p M -1.2 0 A 0.1 0.1 0 0 0 1.2 0 A 0.1 0.1 0 0 0 -1.2 0
+[tomato-juice]
+ f rgb(209, 15, 15)|lw 0.02|as|p M -1.2 0 A 0.1 0.1 0 0 0 1.2 0 A 0.1 0.1 0 0 0 -1.2 0
+[tomato-soup]
+ f rgb(242, 67, 14)|lw 0.02|as|p M -1.2 0 A 0.1 0.1 0 0 0 1.2 0 A 0.1 0.1 0 0 0 -1.2 0
+[cheese-leek-soup]
+ f rgb(161, 197, 72)|lw 0.02|as|p M -1.2 0 A 0.1 0.1 0 0 0 1.2 0 A 0.1 0.1 0 0 0 -1.2 0
+[milk]
+ f rgb(246, 243, 234)|lw 0.02|as|p M -1.2 0 A 0.1 0.1 0 0 0 1.2 0 A 0.1 0.1 0 0 0 -1.2 0
+[curry]
+ f rgb(245, 131, 9)|lw 0.02|as|p M -1.2 0 A 0.1 0.1 0 0 0 1.2 0 A 0.1 0.1 0 0 0 -1.2 0
+[water]
+ f rgba(32, 101, 249, 0.67)|lw 0.02|as|p M -1.2 0 A 0.1 0.1 0 0 0 1.2 0 A 0.1 0.1 0 0 0 -1.2 0
+[strawberry-puree]
+ f rgb(177, 17, 94)|lw 0.02|as|p M -1.2 0 A 0.1 0.1 0 0 0 1.2 0 A 0.1 0.1 0 0 0 -1.2 0
+[strawberry-shake]
+ f rgb(247, 132, 216)|lw 0.02|as|p M -1.2 0 A 0.1 0.1 0 0 0 1.2 0 A 0.1 0.1 0 0 0 -1.2 0
+[strawberry-icecream]
+ f rgb(247, 132, 216)|as|lw 0.1|p M -0.9 0 A 0.1 0.1 0 0 0 0.9 0 A 0.1 0.1 0 0 0 -0.9 0
+
+[unknown-order]
+ f rgb(144, 53, 68)|s|lw 0.15|p M 0 1 L 0 1 M 0 0.4 L -0.2 -0.8 L 0.2 -0.8 Z
+ f rgb(255, 255, 255)|s|lw 0.05|p M 0 1 L 0 1.01 M 0 0.4 L -0.2 -0.8 L 0.2 -0.8 Z
diff --git a/test-client/assets/tile_sprites.ini b/test-client/assets/tile_sprites.ini
new file mode 100644
index 00000000..3d021b42
--- /dev/null
+++ b/test-client/assets/tile_sprites.ini
@@ -0,0 +1,80 @@
+# Hurry Curry! - a game about cooking
+# Copyright (C) 2026 Hurry Curry! Contributors
+#
+# 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/>.
+
+[floor]
+ f rgb(51, 51, 51)|s rgb(34, 34, 34)|lw 0.02|lj 0|p M -1.96 -1.96 h 3.92 v 3.92 h -3.92 z
+[table]
+ f rgb(133, 76, 38)|p M -1.6 0 A 0.4 0.4 90 0 1 1.6 0 A 0.4 0.4 90 0 1 -1.6 0 Z
+[chair]
+ f rgb(136, 83, 41)|p m -1.2 -1.2 h 2.4 v 2.4 h -2.4 z
+[crate]
+ f rgb(79, 127, 24)|as|lj 1|p m -1.6 -1.6 h 3.2 v 3.2 h -3.2 z
+ v sprite
+[wall]
+ f rgb(23, 23, 23)|p M -2 -2 h 4 v 4 h -4 z
+[wall-window]
+ f rgb(23, 23, 23)|p M -2 -2 h 4 v 4 h -4 z
+[door]
+ s rgb(23, 23, 23)|lw 0.1|p M -1.9 -1.9 h 3.8 v 3.8 h -3.8 z
+[street]
+ f rgb(73, 73, 73)|p M -2 -2 h 4 v 4 h -4 z
+[path]
+ f rgb(82, 57, 44)|p M -2 -2 h 4 v 4 h -4 z
+[grass]
+ f rgb(75, 113, 70)|p M -2 -2 h 4 v 4 h -4 z
+[tree]
+ f rgb(99, 180, 88)|p M -1.6 0 A 0.4 0.4 90 0 1 1.6 0 A 0.4 0.4 90 0 1 -1.6 0 Z
+[counter]
+ f rgb(222, 150, 91)|p M -2 -2 h 4 v 4 h -4 z
+[counter-window]
+ f rgb(154, 106, 67)|p M -2 -2 h 4 v 4 h -4 z
+[stove]
+ f rgb(122, 115, 109)|p M -2 -2 h 4 v 4 h -4 z
+ f rgb(97, 96, 94)|as|p M -1.2 0 A 0.4 0.4 90 0 1 1.2 0 A 0.4 0.4 90 0 1 -1.2 0 Z
+[trash]
+ f rgb(60, 58, 56)|as|p M -1.5 0 A 1 1 0 0 0 1.5 0 A 1 1 0 0 0 -1.5 0
+ s rgb(172, 51, 51)|p M -0.8 -0.8 L 0.8 0.8 M 0.8 -0.8 L -0.8 0.8
+[oven]
+ f rgb(198, 59, 59)|as|lw 0.05|lj 0|p M -1.9 -1.9 h 3.8 v 3.8 h -3.8 z
+[freezer]
+ f rgb(54, 182, 171)|as|lw 0.05|lj 0|p M -1.9 -1.9 h 3.8 v 3.8 h -3.8 z
+[deep-fryer]
+ f rgb(123, 124, 112)|as|p M -1.7 -1.7 H 1.7 V 1.7 H -1.7 Z
+ f rgb(50, 50, 50)|as|p M -1.2 -1.2 H 1.2 V 1.2 H -1.2 Z
+
+[cutting-board]
+ f rgb(201, 107, 49)|as|p m -1.6 -1.2 h 2.8 v 2.4 h -2.8 z
+ f rgb(93, 93, 105)|as|lw 0.01|p m 1.8 0.8 l 0 -1.6 l -0.2 0.4 l -0.2 0.8 l 0.2 0 l 0 0.4 z
+[rolling-board]
+ f rgb(201, 107, 49)|as|p m -1.6 -1.2 h 2.8 v 2.4 h -2.8 z
+ f rgb(191, 92, 31)|as|lw 0.01|p m 1.4 1.3 v -0.3 h -0.2 v -2 h 0.2 v -0.3 h 0.2 v 0.3 h 0.2 v 2 h -0.2 v 0.3 z
+[sink]
+ s rgb(215, 224, 235)|af|p M -1.6 -1.4 H 1.2 V 1.5 H -1.6 Z
+ f rgb(131, 137, 145)|p M 1.4 -1.7 L -0.1 -0.6 L 0.1 -0.3 L 1.6 -1.4 Z
+
+[button-base]
+ f rgb(29, 29, 29)|p M -2 -2 h 4 v 4 h -4 z
+[button:accept]
+ f rgb(76, 239, 0)|as|p M -1.6 0 A 0.4 0.4 90 0 1 1.6 0 A 0.4 0.4 90 0 1 -1.6 0 Z
+ s rgb(255, 255, 255)|p M -0.7 0.3 L -0.2 0.8 L 0.6 -0.7
+[button:reject]
+ f rgb(239, 0, 0)|as|p M -1.6 0 A 0.4 0.4 90 0 1 1.6 0 A 0.4 0.4 90 0 1 -1.6 0 Z
+ s rgb(255, 255, 255)|p M -0.8 -0.8 L 0.8 0.8 M 0.8 -0.8 L -0.8 0.8
+[map-selector]
+ f rgb(56, 99, 239)|s|p m -1.2 -1.2 h 2.4 v 2.4 h -2.4 z
+[book]
+ s rgb(230, 45, 45)|f rgb(223, 223, 223)|p M 0 -0.6 Q 0.4 -0.8 1 -0.6 V 1 Q 0.4 0.8 0 1 Q -0.4 0.8 -1 1 V -0.6 Q -0.4 -0.8 0 -0.6
+ s rgb(144, 144, 144)|lw 0.01|p M -0.8 -0.4 Q -0.3 -0.5 -0.1 -0.4 M -0.8 -0.2 Q -0.3 -0.3 -0.1 -0.2 M -0.8 0 Q -0.3 -0.1 -0.1 0 M -0.8 0.2 Q -0.3 0.1 -0.1 0.2 M 0.2 -0.4 Q 0.4 -0.5 0.8 -0.4 M 0.2 -0.2 Q 0.4 -0.3 0.8 -0.2
+ f rgb(144, 144, 144)|p M 0.2 0 Q 0.4 -0.1 0.8 0 V 0.5 Q 0.4 0.4 0.2 0.5 Z
diff --git a/test-client/chat.ts b/test-client/chat.ts
new file mode 100644
index 00000000..2fab966c
--- /dev/null
+++ b/test-client/chat.ts
@@ -0,0 +1,69 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { canvas, player_controller, send } from "./main.ts";
+import { System } from "./system.ts";
+
+const KEY_CHAT = "Enter"
+const KEY_CLOSE = "Escape"
+
+export class SChat extends System {
+ chat: null | HTMLInputElement = null;
+
+ override input(ev: KeyboardEvent, down: boolean): boolean {
+ if (this.chat) {
+ this.chat.focus()
+ return true
+ } else if (down && ev.code == KEY_CHAT) {
+ this.open()
+ }
+ return false
+ }
+
+ open() {
+ this.chat = document.createElement("input")
+ this.chat.type = "text"
+ this.chat.placeholder = "Message"
+ document.body.append(this.chat)
+ this.chat.addEventListener("keydown", ev => {
+ if (ev.code == KEY_CLOSE) {
+ this.close()
+ ev.preventDefault()
+ }
+ if (ev.code == KEY_CHAT) {
+ this.submit()
+ ev.preventDefault()
+ }
+ })
+ this.chat.focus()
+ }
+ close() {
+ if (!this.chat) return
+ this.chat.remove()
+ this.chat = null;
+ canvas.focus()
+ }
+ submit() {
+ if (this.chat) {
+ if (this.chat.value.length) send({ player: player_controller.my_id!, type: "communicate", message: { text: this.chat.value } })
+ setTimeout(() => this.close(), 0)
+ } else {
+ this.open()
+ }
+ }
+}
diff --git a/test-client/connect_screen.ts b/test-client/connect_screen.ts
new file mode 100644
index 00000000..b1c67476
--- /dev/null
+++ b/test-client/connect_screen.ts
@@ -0,0 +1,53 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { tr } from "./locale.ts";
+import { ws } from "./main.ts";
+import { PacketC } from "./protocol.ts";
+import { System } from "./system.ts";
+
+export class SConnectScreen extends System {
+ disconnect_reason?: string
+
+ override packet(p: PacketC): void {
+ if (p.type == "disconnect" && "translation" in p.reason)
+ this.disconnect_reason = tr(p.reason.translation.id)
+ }
+
+ override draw(ctx: CanvasRenderingContext2D): void {
+ if (ws.readyState == ws.CONNECTING) this.draw_wait(ctx, "Connecting...")
+ else if (ws.readyState == ws.CLOSING) this.draw_wait(ctx, "Closing...")
+ else if (ws.readyState == ws.CLOSED) this.draw_wait(ctx, this.disconnect_reason ?? "Disconnected")
+ }
+
+ draw_wait(ctx: CanvasRenderingContext2D, text: string) {
+ ctx.fillStyle = "#444"
+ ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height)
+ ctx.fillStyle = "#555"
+ ctx.font = "50px sans-serif"
+ ctx.strokeStyle = "black"
+ ctx.fillStyle = "white"
+ ctx.lineWidth = 10
+ ctx.textAlign = "center"
+ ctx.textBaseline = "middle"
+ ctx.lineJoin = "round"
+ ctx.lineCap = "round"
+ ctx.strokeText(text, ctx.canvas.width / 2, ctx.canvas.height / 2)
+ ctx.fillText(text, ctx.canvas.width / 2, ctx.canvas.height / 2)
+ }
+}
diff --git a/test-client/index.html b/test-client/index.html
index fecde427..5e2b3ae7 100644
--- a/test-client/index.html
+++ b/test-client/index.html
@@ -1,6 +1,6 @@
<!--
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/test-client/locale.ts b/test-client/locale.ts
index 01f1b790..3b554553 100644
--- a/test-client/locale.ts
+++ b/test-client/locale.ts
@@ -1,22 +1,28 @@
import { data } from "./main.ts";
import { Message } from "./protocol.ts";
+import language_data from "./assets/locale.json.gz" with {type: "bytes"}
-const LANGUAGES = [
- "ar", "de", "en", "es", "eu",
- "fi", "fr", "he", "ja", "nl",
- "pl", "pt", "to", "tr", "sv",
- "zh_Hans", "zh_Hant", "vi",
-]
+import schema from "./assets/locale.json" with {type: "json"}
+export type Tr = { [key in keyof typeof schema]: string }
+
+const TR: Tr = {} as Tr
-let TR: { [key: string]: string } = {}
export async function init_locale(lang?: string) {
if (!lang) return await init_locale(select_language())
- const res = await fetch(`/locale/${encodeURIComponent(lang)}.json`, { headers: { "Accept": "application/json" } })
- if (!res.ok) throw new Error("language pack download failed");
- TR = await res.json()
+ const tk = JSON.parse(await decompress(language_data))
+ for (const key in tk) {
+ TR[key as keyof Tr] = tk[key][lang] ?? tk[key]["en"]
+ }
+}
+async function decompress(data: Uint8Array): Promise<string> {
+ const r = new ReadableStream({ start(c) { c.enqueue(data.buffer); c.close() } })
+ .pipeThrough(new DecompressionStream("gzip"))
+ .pipeThrough(new TransformStream({ start() { }, transform(ch, ct) { ct.enqueue(ch.buffer) } }))
+ .pipeThrough(new TextDecoderStream())
+ return (await Array.fromAsync(r)).join("")
}
-export function tr(key: string, ...args: string[]): string {
+export function tr(key: keyof Tr, ...args: string[]): string {
let s = TR[key];
if (!s) return key
if (args.length) s = s.replace(/{(\d+)}/ig, (_m, index) => args[parseInt(index)])
@@ -26,15 +32,12 @@ export function tr(key: string, ...args: string[]): string {
function select_language(): string {
const plang = (new URLSearchParams(globalThis.location.hash.substring(1))).get("lang")
if (plang) return plang
- const navlang = navigator.language.split("-")[0] ?? "en"
- if (LANGUAGES.includes(navlang)) return navlang
- console.warn("fallback language selected");
- return "en"
+ return navigator.language.split("-")[0] ?? "en"
}
export function message_str(m: Message): string {
if ("text" in m) return m.text
- if ("translation" in m) return tr(m.translation.id, ...m.translation.params.map(message_str))
+ if ("translation" in m) return tr(m.translation.id as keyof Tr, ...m.translation.params.map(message_str))
if ("tile" in m) return data.tile_names[m.tile]
if ("item" in m) return data.item_names[m.item]
return "[unknown message type]"
diff --git a/test-client/main.ts b/test-client/main.ts
index 4208a9ae..5a947433 100644
--- a/test-client/main.ts
+++ b/test-client/main.ts
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -17,568 +17,166 @@
*/
/// <reference lib="dom" />
-import { init_locale, tr } from "./locale.ts";
-import { MovementBase, collide_player_player, update_movement } from "./movement.ts";
-import { particle_splash, tick_particles } from "./particles.ts";
-import { DebugEvent, Gamedata, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PacketS, PlayerClass, PlayerID, Score, TileIndex } from "./protocol.ts";
-import { V2, lerp_exp_v2_mut, normalize, lerp_exp } from "./util.ts";
-import { draw_ingame, draw_wait } from "./visual.ts";
+import { SChat } from "./chat.ts";
+import { SCamera } from "./map/camera.ts";
+import { SDebugEvents } from "./map/debug_events.ts";
+import { SMapItems } from "./map/item.ts";
+import { SPlayers, SPlayersOverlay } from "./map/players.ts";
+import { SServerHints } from "./map/server_hints.ts";
+import { SMapTiles } from "./map/tiles.ts";
+import { SParticles } from "./map/particles.ts";
+import { SPlayerController } from "./player_controller.ts";
+import { Gamedata, PacketC, PacketS } from "./protocol.ts";
+import { SQuickCommands } from "./quick_commands.ts";
+import { System } from "./system.ts";
import { SVote } from "./vote.ts";
+import { SServerMessage } from "./server_message.ts";
+import { SScoreDisplay } from "./score_display.ts";
+import { SPinnedMessages } from "./pinned_messages.ts";
+import { GridLayer } from "./map/mod.ts";
+import { SConnectScreen } from "./connect_screen.ts";
+import { SAnnouncement } from "./announcement.ts";
+import { SSprites } from "./map/sprites.ts";
+import { SEffect } from "./map/effect.ts";
+import { init_locale } from "./locale.ts";
-const KEY_INTERACT_LEFT = "KeyJ"
-const KEY_INTERACT_RIGHT = "KeyL"
-const KEY_BOOST = "KeyK"
-const KEY_UP = "KeyW"
-const KEY_DOWN = "KeyS"
-const KEY_LEFT = "KeyA"
-const KEY_RIGHT = "KeyD"
-const KEY_CHAT = "Enter"
-const KEY_CLOSE = "Escape"
-const KEY_ZOOM = "KeyI"
-const KEY_VOTE_AGREE = "Digit1"
-const KEY_VOTE_REJECT = "Digit2"
-const HANDLED_KEYS = [KEY_INTERACT_LEFT, KEY_BOOST, KEY_CHAT, KEY_CLOSE, KEY_DOWN, KEY_UP, KEY_LEFT, KEY_RIGHT, KEY_VOTE_AGREE, KEY_VOTE_REJECT]
-
-export let ctx: CanvasRenderingContext2D;
+let ctx: CanvasRenderingContext2D;
export let canvas: HTMLCanvasElement;
-let ws: WebSocket;
+export let ws: WebSocket;
document.addEventListener("DOMContentLoaded", async () => {
await init_locale()
const ws_uri = globalThis.location.protocol.endsWith("s:")
? `wss://${globalThis.location.host}/`
: `ws://${globalThis.location.hostname}:27032/`
- ws = new WebSocket(ws_uri)
- ws.onerror = console.error
- ws.onmessage = m => {
- packet(JSON.parse(m.data) as PacketC);
- }
- let keepalive_interval: number
- ws.onclose = () => {
- console.log("close")
- clearInterval(keepalive_interval)
- }
- ws.onopen = () => {
+
+ const open = () => {
console.log("open")
send({ type: "join", name: "test", character: { color: Math.floor(Math.random() * 100), hairstyle: 0, headwear: 0 }, class: "chef" })
keepalive_interval = setInterval(() => {
send({ type: "keepalive" })
}, 1000)
}
+ const resize = () => {
+ canvas.width = globalThis.innerWidth
+ canvas.height = globalThis.innerHeight
+ }
+ ws = new WebSocket(ws_uri)
+ let keepalive_interval: number
canvas = document.createElement("canvas");
- document.body.append(canvas)
ctx = canvas.getContext("2d")!
resize()
- globalThis.addEventListener("resize", resize)
draw()
+ ws.addEventListener("open", open)
+ ws.addEventListener("error", console.error)
+ ws.addEventListener("message", m => packet(JSON.parse(m.data) as PacketC))
+ ws.addEventListener("close", () => clearInterval(keepalive_interval))
+ globalThis.addEventListener("resize", resize)
document.addEventListener("keydown", ev => keyboard(ev, true))
document.addEventListener("keyup", ev => keyboard(ev, false))
- document.addEventListener("contextmenu", ev => ev.preventDefault())
+ canvas.addEventListener("contextmenu", ev => ev.preventDefault())
+ document.body.append(canvas)
})
-export interface ItemData {
- kind: ItemIndex,
- x: number,
- y: number,
- tracking?: V2,
- active?: Involvement
- remove_anim?: number
-}
-export interface Involvement {
- position: number,
- speed: number,
- warn: boolean
-}
-export interface ItemSlot {
- item?: ItemData,
-}
-export interface PlayerData extends MovementBase {
- id: number,
- name: string,
- hands: ItemSlot[],
- class: PlayerClass,
- character: number,
- anim_position: V2,
- message_persist?: MessageData,
- message_pinned?: MessageData,
- message?: MessageData,
-}
-
-export interface TileData extends ItemSlot {
- x: number, y: number // tile position
- position: V2, // center position
- parts: TileIndex[]
-}
-export type MessageStyle = "hint" | "normal" | "error" | "pinned"
-export interface MessageData {
- style: MessageStyle
- inner: Message
- anim_position: V2,
- anim_size: number,
- timeout?: MessageTimeout,
-}
-
-export const players = new Map<PlayerID, PlayerData>()
-export const tiles = new Map<string, TileData>()
-export const items_removed = new Set<ItemData>()
-export const server_hints = new Map<string, MessageData>()
-export const debug_events: Map<string, DebugEvent & { timeout: number }> = new Map()
-
-export let data: Gamedata = {
- item_names: [],
- tile_names: [],
- tile_interactable_empty: [],
- tile_placeable_any: [],
- tile_placeable_items: {},
- hand_count: 0,
- current_map: "",
- tile_collide: [],
-}
-
-export let global_message: MessageData | undefined = undefined
-export let my_id: PlayerID = -1
-export const score: Score = { active_recipes: 0, demands_completed: 0, demands_failed: 0, instant_recipes: 0, passive_recipes: 0, players: 0, points: 0, stars: 0, time_remaining: 0 }
-export const camera: V2 = { x: 0, y: 0 }
-export let camera_scale = 0.05;
-export const interact_target_anim: V2 = { x: 0, y: 0 }
-export let interact_possible_anim: number = 0
-export let interact_active_anim: number = 0
-export let overlay_vis_anim: number = 0
-export let is_lobby = false
-let disconnect_reason: string | undefined
-let interacting: V2 | undefined;
-let last_server_sent_position: V2 = { x: 0, y: 0 }
+export let data: Gamedata = { item_names: [], tile_names: [], tile_interactable_empty: [], tile_placeable_any: [], tile_placeable_items: {}, hand_count: 0, metadata: { demand_items: [], difficulty: 0, display_name: "", name: "", players: 0, hand_count: 0 }, tile_collide: [] }
-export const vote = new SVote()
+const sprites = new SSprites()
+const vote = new SVote()
+const chat = new SChat()
+const map_tiles = new SMapTiles(sprites)
+const players = new SPlayers(sprites, map_tiles)
+const players_overlay = new SPlayersOverlay(sprites, players)
+const map_items = new SMapItems(sprites, map_tiles, players)
+const quick_commands = new SQuickCommands()
+const server_hints = new SServerHints(sprites, players)
+export const player_controller = new SPlayerController(map_tiles, players)
+const particles = new SParticles()
+const server_message = new SServerMessage()
+const score_display = new SScoreDisplay()
+const pinned_messages = new SPinnedMessages(sprites, players)
+const connect_screen = new SConnectScreen()
+export const camera = new SCamera(player_controller)
+const announcement = new SAnnouncement()
+const effect = new SEffect(map_items)
+const debug_events = new SDebugEvents(map_items)
-function get_item_location(loc: ItemLocation): ItemSlot {
- if ("tile" in loc) return tiles.get(loc.tile.toString())!
- if ("player" in loc) return players.get(loc.player[0])!.hands[loc.player[1]]
- throw new Error("invalid item location");
-}
-function get_item_location_tracking(loc: ItemLocation): V2 {
- if ("tile" in loc) return tiles.get(loc.tile.toString())!.position
- if ("player" in loc) return players.get(loc.player[0])!.position
- throw new Error("invalid item location");
-}
+const systems: System[] = [
+ vote, chat, debug_events, server_hints, map_tiles,
+ map_items, quick_commands, players, player_controller,
+ camera, particles, server_message, score_display,
+ pinned_messages, connect_screen, players_overlay,
+ announcement, sprites, effect,
+]
+const map_layers = [
+ new GridLayer(),
+ map_tiles,
+ players,
+ map_items,
+ particles,
+ player_controller,
+ server_hints,
+ players_overlay,
+ effect,
+ debug_events,
+]
+const ui_layers = [
+ pinned_messages,
+ server_message,
+ score_display,
+ announcement,
+ vote,
+ connect_screen,
+]
-function send(p: PacketS) {
+export function send(p: PacketS) {
if (p.type != "movement" && p.type != "keepalive") console.log("send", p);
ws.send(JSON.stringify(p))
}
+
function packet(p: PacketC) {
- if (!["movement", "update_map", "debug"].includes(p.type))
+ if (!["movement", "debug"].includes(p.type))
console.log(p);
- vote.packet(p)
- switch (p.type) {
- case "version":
- console.log(`Protocol version: ${p.major}.${p.minor}`);
- break;
- case "joined":
- my_id = p.id
- break;
- case "game_data":
- data = p
- break;
- case "add_player": {
- players.set(p.id, {
- id: p.id,
- hands: new Array(data.hand_count).fill(null).map(() => ({})),
- position: { x: p.position[0], y: p.position[1], },
- anim_position: { x: p.position[0], y: p.position[1] },
- character: p.character.color,
- class: p.class,
- name: p.name,
- rotation: 0,
- facing: { x: 0, y: 1 },
- velocity: { x: 0, y: 0 },
- stamina: 0,
- boosting: false,
- input_boost: false,
- input_direction: { x: 0, y: 0 },
- })
- break;
- }
- case "remove_player":
- players.delete(p.id)
- break;
- case "movement": {
- const pl = players.get(p.player)!
- if (p.player == my_id) return last_server_sent_position = { x: p.pos[0], y: p.pos[1] }
- pl.position.x = p.pos[0]
- pl.position.y = p.pos[1]
- pl.rotation = p.rot
- pl.input_direction.x = p.dir[0]
- pl.input_direction.y = p.dir[1]
- pl.input_boost = p.boost
- break;
- }
- case "move_item": {
- const from = get_item_location(p.from)
- const to = get_item_location(p.to)
-
- to.item = from.item
- to.item!.tracking = get_item_location_tracking(p.to)
- from.item = undefined
-
- break;
- }
- case "set_item": {
- const slot = get_item_location(p.location)
- const slotpos = get_item_location_tracking(p.location)
- if (slot.item !== undefined && slot.item !== null) items_removed.add(slot.item)
- delete slot.item
- if (p.item !== undefined && p.item !== null) slot.item = { kind: p.item, x: slotpos.x, y: slotpos.y, tracking: slotpos }
- break;
- }
- case "clear_progress": {
- delete get_item_location(p.item).item!.active
- break;
- }
- case "set_progress": {
- const slot = get_item_location(p.item)
- if (!slot.item) return
- slot.item.active = {
- position: p.position,
- speed: p.speed,
- warn: p.warn
- }
- break;
- }
- case "update_map":
- for (const [pos, parts] of p.changes) {
- tiles.set(pos.toString(), {
- x: pos[0],
- y: pos[1],
- position: { x: pos[0] + 0.5, y: pos[1] + 0.5 },
- parts
- })
- if (!parts.length)
- tiles.delete(pos.toString())
- }
- break;
- case "communicate": {
- const player = players.get(p.player)!
- if (p.message) {
- const message = {
- inner: p.message,
- anim_size: player.message_persist?.anim_size ?? 0,
- anim_position: player.anim_position,
- timeout: p.timeout ?? { initial: 5, remaining: 5, pinned: false },
- style: "normal" as const
- };
- if (p.timeout) player.message_persist = message
- else player.message = message
- if (p.timeout?.pinned) player.message_pinned = {
- inner: p.message,
- anim_size: player.message_pinned?.anim_size ?? 0,
- anim_position: { x: 20, y: 0 },
- style: "pinned",
- timeout: p.timeout
- }
- } else {
- delete player.message_persist
- delete player.message_pinned
- }
- break;
- }
- case "score":
- score.demands_completed = p.demands_completed
- score.demands_failed = p.demands_failed
- score.points = p.points
- score.time_remaining = p.time_remaining ?? null
- break;
- case "server_message":
- global_message = {
- inner: p.message,
- style: p.error ? "error" : "normal",
- anim_size: 0.,
- anim_position: { x: 0, y: 0 },
- timeout: { initial: 5, remaining: 5, pinned: false }
- }
- break;
- case "set_ingame":
- console.log(`ingame ${p.state}`);
- is_lobby = p.lobby
- send({ type: "ready" })
- break;
- case "movement_sync": {
- const me = players.get(my_id)
- if (me) {
- me.position.x = last_server_sent_position.x // ensuring object has same ref
- me.position.y = last_server_sent_position.y
- }
- break;
- }
- case "server_hint":
- if (p.player != my_id) return;
- if (p.message) server_hints.set(p.position + "", {
- inner: p.message,
- anim_size: 0.,
- anim_position: p.position ?
- { x: p.position[0] + 0.5, y: p.position[1] + 0.5 } :
- players.get(my_id)!.anim_position,
- style: "hint"
- })
- else server_hints.delete(p.position + "")
- break;
- case "tutorial_ended":
- break;
- case "environment":
- break
- case "effect2":
- break;
- case "pause":
- break; // TODO
- case "menu":
- switch (p.menu) {
- case "score":
- global_message = {
- timeout: { initial: 5, remaining: 5, pinned: false },
- inner: { text: `Score: ${JSON.stringify(p.data, null, 4)}` },
- anim_position: { x: 0, y: 0 },
- anim_size: 0,
- style: "normal"
- };
- break
- default: console.warn("unknown menu");
- }
- break;
- case "disconnect":
- if ("translation" in p.reason)
- disconnect_reason = tr(p.reason.translation.id)
- break;
- case "debug":
- debug_events.set(p.key, p)
- break;
- }
-}
-export let chat: null | HTMLInputElement = null;
+ if (p.type == "version") console.log(`Protocol version: ${p.major}.${p.minor}`)
+ else if (p.type == "game_data") data = p
+ else if (p.type == "set_ingame" && p.state) send({ type: "ready" })
-const QUICK_COMMANDS: { [key: string]: string } = {
- "Numpad1": "/start junior",
- "Numpad2": "/start debug2",
- "Numpad3": "/start sophomore",
- "Numpad4": "/start debug",
- "Numpad5": "/start 5star",
- "Numpad6": "/start campaign/lobby",
- "Numpad8": "/start-tutorial plate:seared-patty,sliced-bun",
- "Numpad9": "/start-tutorial plate:bun",
- "Numpad7": "/end-tutorial",
- "Numpad0": "/end",
- "NumpadEnter": "/r",
+ for (const s of systems)
+ s.packet(p)
}
export const keys_down = new Set();
function keyboard(ev: KeyboardEvent, down: boolean) {
- if (down && ev.code == KEY_CHAT) return toggle_chat()
- else if (down && ev.code == KEY_CLOSE && chat) return close_chat()
- else if (chat) return
- if (HANDLED_KEYS.includes(ev.code)) ev.preventDefault()
- if (!keys_down.has(KEY_INTERACT_LEFT) && ev.code == KEY_INTERACT_LEFT && down) set_interact(true, 0)
- if (keys_down.has(KEY_INTERACT_LEFT) && ev.code == KEY_INTERACT_LEFT && !down) set_interact(false, 0)
- if (!keys_down.has(KEY_INTERACT_RIGHT) && ev.code == KEY_INTERACT_RIGHT && down) set_interact(true, 1)
- if (keys_down.has(KEY_INTERACT_RIGHT) && ev.code == KEY_INTERACT_RIGHT && !down) set_interact(false, 1)
- if (down && ev.code in QUICK_COMMANDS) send({ player: my_id, type: "communicate", message: { text: QUICK_COMMANDS[ev.code] } })
- if (down && ev.code == "KeyE") particle_splash(get_interact_target() ?? { x: 0, y: 0 }, 0.8)
- if (down && ev.code == KEY_VOTE_AGREE) send({ type: "cast_vote", player: my_id, agree: true })
- if (down && ev.code == KEY_VOTE_REJECT) send({ type: "cast_vote", player: my_id, agree: false })
- if (down) keys_down.add(ev.code)
- else keys_down.delete(ev.code)
-}
-
-function close_chat() {
- if (!chat) return
- chat.remove()
- canvas.focus()
- chat = null;
-}
-function toggle_chat() {
- if (chat) {
- if (chat.value.length) send({ player: my_id, type: "communicate", message: { text: chat.value } })
- chat.remove()
- canvas.focus()
- chat = null;
- } else {
- chat = document.createElement("input")
- chat.type = "text"
- chat.placeholder = "Message"
- document.body.append(chat)
- chat.focus()
- }
-}
-
-export function can_interact_with_tile(t: TileData, hand: ItemSlot) {
- if (t.item) return true
- let can_interact = false
- for (const part of t.parts) {
- if (hand.item && data.tile_placeable_items[part])
- can_interact = data.tile_placeable_items[part].includes(hand.item.kind)
- if (hand.item && data.tile_placeable_any.includes(part))
- can_interact = true
- if (!hand.item && data.tile_interactable_empty.includes(part))
- can_interact = true
- }
- return can_interact
-}
-
-export function get_interact_target(): V2 | undefined {
- if (interacting) return interacting
- const me = players.get(my_id)
- if (!me) return
-
- if (location.hash.search("oldinteract") != -1)
- return {
- x: Math.floor(me.position.x + Math.sin(me.rotation)),
- y: Math.floor(me.position.y + Math.cos(me.rotation))
- }
-
- const rx = me.position.x + Math.sin(me.rotation) * 0.7
- const ry = me.position.y + Math.cos(me.rotation) * 0.7
- const bx = Math.floor(rx)
- const by = Math.floor(ry)
- let best = { x: bx, y: by }
- let best_d = 100
- for (let ox = -1; ox <= 1; ox++) for (let oy = -1; oy <= 1; oy++) {
- const t = tiles.get([bx + ox, by + oy] + "")
- if (!t) continue
- for (const hand of me.hands) {
- if (!can_interact_with_tile(t, hand)) continue
- const cx = (bx + ox + 0.5)
- const cy = (by + oy + 0.5)
- const dx = rx - cx
- const dy = ry - cy
- const pdx = me.position.x - cx
- const pdy = me.position.y - cy
- const d = Math.sqrt(dx * dx + dy * dy)
- const pd = Math.sqrt(pdx * pdx + pdy * pdy)
- if (pd < 2 && d < best_d) {
- best_d = d
- best = { x: bx + ox, y: by + oy }
- }
+ if (document.activeElement instanceof HTMLInputElement) return
+ for (const s of systems)
+ if (s.input(ev, down)) {
+ ev.preventDefault()
+ break
}
- }
- return best
-}
-
-function set_interact(edge: boolean, hand: number) {
- if (edge) interacting = get_interact_target()
- if (interacting) send({ player: my_id, type: "interact", target: edge ? { tile: [interacting.x, interacting.y] } : undefined, hand })
- if (!edge) interacting = undefined
-}
-
-function send_movement() {
- const p = players.get(my_id)
- if (!p) return
- send({ player: my_id, type: "movement", pos: [p.position.x, p.position.y], dir: [p.input_direction.x, p.input_direction.y], boost: p.input_boost })
-}
-
-let last_direction: V2 = { x: 0, y: 0 }
-let last_boost: boolean = false
-function frame_update(dt: number) {
- const p = players.get(my_id)
- if (!p) return
-
- score.time_remaining -= dt
-
- const direction = normalize({
- x: (+keys_down.has(KEY_RIGHT) - +keys_down.has(KEY_LEFT)),
- y: (+keys_down.has(KEY_DOWN) - +keys_down.has(KEY_UP))
- })
- if (interacting) direction.x *= 0, direction.y *= 0
- p.input_direction = direction
- p.input_boost = keys_down.has("KeyK")
- if (last_boost != p.input_boost || last_direction.x != p.input_direction.x || last_direction.y != p.input_direction.y)
- send_movement()
- last_direction = p.input_direction
- last_boost = p.input_boost
-
- players.forEach(p => update_movement(p, dt))
-
- for (const [_, a] of players)
- for (const [_, b] of players)
- collide_player_player(a, b, dt)
- const update_item = (item: ItemData) => {
- if (item.tracking) lerp_exp_v2_mut(item, item.tracking, dt * 10.)
- if (item.active) item.active.position += item.active.speed * dt
- }
- let pin_xo = 0
- for (const [pid, player] of players) {
- if (pid == my_id) player.anim_position.x = player.position.x, player.anim_position.y = player.position.y
- else lerp_exp_v2_mut(player.anim_position, player.position, dt * 15)
- player.hands.forEach(h => { if (h.item) update_item(h.item) })
- if (player.message && tick_message(player.message, dt)) delete player.message
- if (player.message_persist && tick_message(player.message_persist, dt)) delete player.message_persist
- if (player.message_pinned && tick_message(player.message_pinned, dt)) delete player.message_pinned
- if (player.message_pinned) lerp_exp_v2_mut(player.message_pinned.anim_position, { x: pin_xo++, y: 0 }, dt * 5)
- }
- for (const [_, tile] of tiles)
- if (tile.item !== undefined && tile.item !== null) update_item(tile.item)
-
- const remove: ItemData[] = []
- for (const item of items_removed) {
- update_item(item)
- if (item.remove_anim === undefined) item.remove_anim = 0
- item.remove_anim += dt * 4.
- if (item.remove_anim > 1.) remove.push(item)
- }
- remove.forEach(i => items_removed.delete(i))
-
- const remove_events: string[] = []
- for (const [key, ev] of debug_events.entries()) {
- ev.timeout -= dt
- if (ev.timeout < 0) remove_events.push(key)
- }
- remove_events.forEach(i => debug_events.delete(i))
-
- for (const [_, h] of server_hints)
- tick_message(h, dt);
-
- lerp_exp_v2_mut(camera, p.position, dt * 10.)
-
- if (global_message && tick_message(global_message, dt)) global_message = undefined
-
- const it = get_interact_target() ?? { x: 0, y: 0 };
- const target_tile = tiles.get([it.x, it.y].toString())
- const me = players.get(my_id)
- const possible = target_tile && me && me.hands.find(h => can_interact_with_tile(target_tile, h))
- lerp_exp_v2_mut(interact_target_anim, it, dt * 15.)
- interact_possible_anim = lerp_exp(interact_possible_anim, +!!possible, dt * 18.)
- interact_active_anim = lerp_exp(interact_active_anim, +!!interacting, dt * 15.)
-
- const zoom_target = Math.min(canvas.width, canvas.height) * (keys_down.has(KEY_ZOOM) ? 0.05 : 0.1)
- camera_scale = lerp_exp(camera_scale, zoom_target, dt * 5)
- overlay_vis_anim = lerp_exp(overlay_vis_anim, +keys_down.has(KEY_ZOOM), dt * 10)
-
- tick_particles(dt)
- vote.tick(dt)
-}
-
-function resize() {
- canvas.width = globalThis.innerWidth
- canvas.height = globalThis.innerHeight
+ if (down) keys_down.add(ev.code)
+ else keys_down.delete(ev.code)
}
let last_frame = performance.now()
function draw() {
const now = performance.now()
- frame_update((now - last_frame) / 1000)
+ let dt = (now - last_frame) / 1000
+ while (dt > 0) { // subdivide into multiple ticks of at most 40ms
+ const step_dt = Math.min(0.04, dt)
+ for (const s of systems) s.tick(step_dt)
+ dt -= step_dt
+ }
+
+ ctx.fillStyle = "#111"
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
+ ctx.save()
+ camera.apply_map_transform(ctx)
+ for (const l of map_layers) l.draw_map_layer(ctx)
+ ctx.restore()
+ for (const l of ui_layers) l.draw(ctx)
+
last_frame = now;
- if (ws.readyState == ws.CONNECTING) draw_wait("Connecting...")
- else if (ws.readyState == ws.CLOSING) draw_wait("Closing...")
- else if (ws.readyState == ws.CLOSED) draw_wait(disconnect_reason ?? "Disconnected")
- else if (ws.readyState == ws.OPEN) draw_ingame()
- else throw new Error(`ws state invalid`);
requestAnimationFrame(draw)
}
-
-function tick_message(m: MessageData | undefined, dt: number): boolean {
- if (!m) return true
- m.anim_size = lerp_exp(m.anim_size, m.timeout ? m.timeout.remaining > 0.3 ? 1 : 0 : 1, dt * 3)
- if (!m.timeout) return false
- m.timeout.remaining -= dt;
- return m.timeout.remaining <= 0
-}
diff --git a/test-client/map/camera.ts b/test-client/map/camera.ts
new file mode 100644
index 00000000..1ef09c03
--- /dev/null
+++ b/test-client/map/camera.ts
@@ -0,0 +1,54 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { canvas, keys_down } from "../main.ts";
+import { SPlayerController } from "../player_controller.ts";
+import { System } from "../system.ts";
+import { lerp_exp, lerp_exp_v2_mut, V2 } from "../util.ts";
+
+const KEY_ZOOM = "KeyI"
+
+export class SCamera extends System {
+ center: V2 = { x: 0, y: 0 }
+ scale = 0.05;
+ overlay_vis_anim: number = 0
+
+ constructor(public pcs: SPlayerController) { super() }
+
+ override tick(dt: number): void {
+ const p = this.pcs.ps.players.get(this.pcs.my_id!)
+ if (!p) return
+ lerp_exp_v2_mut(this.center, p.position, dt * 10.)
+ const zoom_target = Math.min(canvas.width, canvas.height) * (keys_down.has(KEY_ZOOM) ? 0.05 : 0.1)
+ this.scale = lerp_exp(this.scale, zoom_target, dt * 5)
+ this.overlay_vis_anim = lerp_exp(this.overlay_vis_anim, +keys_down.has(KEY_ZOOM), dt * 10)
+ }
+
+ apply_map_transform(ctx: CanvasRenderingContext2D) {
+ ctx.translate(canvas.width / 2, canvas.height / 2)
+ ctx.scale(this.scale, this.scale)
+ ctx.translate(-this.center.x, -this.center.y)
+ }
+
+ screen_to_world(screen: V2): V2 {
+ return {
+ x: ((screen.x - canvas.width / 2) / this.scale) + this.center.x,
+ y: ((screen.y - canvas.height / 2) / this.scale) + this.center.y,
+ }
+ }
+}
diff --git a/test-client/map/debug_events.ts b/test-client/map/debug_events.ts
new file mode 100644
index 00000000..9ba976ee
--- /dev/null
+++ b/test-client/map/debug_events.ts
@@ -0,0 +1,86 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { DebugEvent, PacketC } from "../protocol.ts";
+import { System } from "../system.ts";
+import { SMapItems } from "./item.ts";
+import { MapLayer } from "./mod.ts";
+
+export class SDebugEvents extends System implements MapLayer {
+ events: Map<string, DebugEvent & { timeout: number }> = new Map()
+
+ constructor(public is: SMapItems) { super() }
+
+ override tick(dt: number): void {
+ const remove_events: string[] = []
+ for (const [key, ev] of this.events.entries()) {
+ ev.timeout -= dt
+ if (ev.timeout < 0) remove_events.push(key)
+ }
+ remove_events.forEach(i => this.events.delete(i))
+ }
+
+ override packet(p: PacketC): void {
+ if (p.type == "debug")
+ this.events.set(p.key, p)
+ }
+
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ for (const ev of this.events.values()) {
+ const color = `oklch(${ev.color.join(" ")})`
+ if (ev.display.ty == "path") {
+ ctx.lineWidth = 0.1
+ ctx.lineCap = "round"
+ ctx.strokeStyle = color
+ ctx.beginPath()
+ let first = true
+ if (ev.display.player) {
+ const p = this.is.ps.players.get(ev.display.player)
+ if (p) ctx.moveTo(p.position.x, p.position.y)
+ first = false
+ }
+ for (let i = ev.display.points.length - 1; i >= 0; i--)
+ if (first) ctx.moveTo(...ev.display.points[i]), first = false
+ else ctx.lineTo(...ev.display.points[i])
+ ctx.stroke()
+ }
+ }
+ for (const ev of this.events.values()) {
+ const color = `oklch(${ev.color.join(" ")})`
+ if (ev.display.ty == "label") {
+ ctx.font = "0.15px sans-serif"
+ ctx.strokeStyle = "black"
+ ctx.fillStyle = color
+ ctx.lineWidth = 0.05
+ ctx.textAlign = "center"
+ ctx.textBaseline = "middle"
+ ctx.lineJoin = "round"
+ ctx.lineCap = "round"
+
+ const pos = this.is.get_item_location_tracking(ev.display.pos)
+ if (!pos) continue
+ const lines = ev.display.text.split("\n")
+ for (let i = 0; i < lines.length; i++)
+ ctx.strokeText(lines[i], pos.x, pos.y + i * 0.18)
+ for (let i = 0; i < lines.length; i++)
+ ctx.fillText(lines[i], pos.x, pos.y + i * 0.18)
+
+ }
+ }
+ }
+}
diff --git a/test-client/map/effect.ts b/test-client/map/effect.ts
new file mode 100644
index 00000000..11e763e2
--- /dev/null
+++ b/test-client/map/effect.ts
@@ -0,0 +1,85 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { int, PacketC } from "../protocol.ts";
+import { System } from "../system.ts";
+import { V2 } from "../util.ts";
+import { SMapItems } from "./item.ts";
+import { MapLayer } from "./mod.ts";
+
+
+export class SEffect extends System implements MapLayer {
+ effects: Set<EffectRenderer> = new Set()
+
+ constructor(public is: SMapItems) { super() }
+
+ override packet(p: PacketC): void {
+ if (p.type != "effect") return
+ const pos = { ...this.is.get_item_location_tracking(p.location)! }
+ if (p.effect == "points") this.effects.add(new PointsEffect(pos, p.amount))
+ }
+
+ override tick(dt: number): void {
+ const remove = []
+ for (const e of this.effects)
+ if (!e.tick(dt)) remove.push(e)
+ for (const e of remove)
+ this.effects.delete(e)
+ }
+
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ for (const e of this.effects)
+ e.draw(ctx)
+ }
+}
+
+abstract class EffectRenderer {
+ timer = 0
+ constructor(public pos: V2) { }
+ abstract draw(ctx: CanvasRenderingContext2D): void;
+ tick(dt: number): boolean {
+ this.timer += dt * 2
+ return this.timer < 3
+ }
+}
+
+class PointsEffect extends EffectRenderer {
+ constructor(pos: V2, public amount: int) { super(pos) }
+ override draw(ctx: CanvasRenderingContext2D): void {
+ ctx.save()
+ ctx.translate(this.pos.x, this.pos.y)
+
+ ctx.translate(0, this.timer * -0.2)
+
+ ctx.scale(0.03, 0.03)
+ if (this.timer < 0.5) ctx.scale(this.timer * 2, this.timer * 2)
+ ctx.globalAlpha = this.timer > 2 ? 3 - this.timer : 1
+ ctx.font = "10px sans-serif"
+ ctx.fillStyle = this.amount < 0 ? "rgb(255, 106, 106)" : "rgb(157, 255, 124)"
+ ctx.strokeStyle = "black"
+ ctx.lineWidth = 2
+ ctx.lineJoin = "round"
+ ctx.lineCap = "round"
+ const text = this.amount < 0 ? `${this.amount}` : `+${this.amount}`
+ ctx.strokeText(text, 0, 0)
+ ctx.fillText(text, 0, 0)
+
+
+ ctx.restore()
+ }
+} \ No newline at end of file
diff --git a/test-client/map/item.ts b/test-client/map/item.ts
new file mode 100644
index 00000000..1405513b
--- /dev/null
+++ b/test-client/map/item.ts
@@ -0,0 +1,134 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { ItemIndex, ItemLocation, PacketC } from "../protocol.ts";
+import { System } from "../system.ts";
+import { lerp_exp_v2_mut, V2 } from "../util.ts";
+import { MapLayer } from "./mod.ts";
+import { SPlayers } from "./players.ts";
+import { SSprites } from "./sprites.ts";
+import { ItemSlot, SMapTiles } from "./tiles.ts";
+
+export interface ItemData {
+ kind: ItemIndex,
+ x: number,
+ y: number,
+ tracking?: V2,
+ active?: Involvement
+ remove_anim?: number
+}
+export interface Involvement {
+ position: number,
+ speed: number,
+ warn: boolean
+}
+
+export class SMapItems extends System implements MapLayer {
+ items_removed: Set<ItemData> = new Set()
+
+ constructor(
+ public ss: SSprites,
+ public ts: SMapTiles,
+ public ps: SPlayers,
+ ) { super() }
+
+ override packet(p: PacketC): void {
+ if (p.type == "move_item") {
+ const from = this.get_item_location(p.from)
+ const to = this.get_item_location(p.to)
+
+ to.item = from.item
+ to.item!.tracking = this.get_item_location_tracking(p.to)
+ from.item = undefined
+
+ }
+ else if (p.type == "set_item") {
+ const slot = this.get_item_location(p.location)
+ const slotpos = this.get_item_location_tracking(p.location)!
+ if (slot.item !== undefined && slot.item !== null) this.items_removed.add(slot.item)
+ delete slot.item
+ if (p.item !== undefined && p.item !== null) slot.item = { kind: p.item, x: slotpos.x, y: slotpos.y, tracking: slotpos }
+ } else if (p.type == "clear_progress") {
+ delete this.get_item_location(p.item).item!.active
+ } else if (p.type == "set_progress") {
+ const slot = this.get_item_location(p.item)
+ if (!slot.item) return
+ slot.item.active = {
+ position: p.position,
+ speed: p.speed,
+ warn: p.warn
+ }
+ }
+ }
+
+ override tick(dt: number): void {
+ const update_item = (item: ItemData) => {
+ if (item.tracking) lerp_exp_v2_mut(item, item.tracking, dt * 10.)
+ if (item.active) item.active.position += item.active.speed * dt
+ }
+
+ for (const [_, tile] of this.ts.tiles)
+ if (tile.item !== undefined && tile.item !== null) update_item(tile.item)
+
+ const remove: ItemData[] = []
+ for (const item of this.items_removed) {
+ update_item(item)
+ if (item.remove_anim === undefined) item.remove_anim = 0
+ item.remove_anim += dt * 4.
+ if (item.remove_anim > 1.) remove.push(item)
+ }
+ remove.forEach(i => this.items_removed.delete(i))
+
+ }
+
+ get_item_location(loc: ItemLocation): ItemSlot {
+ if ("tile" in loc) return this.ts.tiles.get(loc.tile.toString())!
+ if ("player" in loc) return this.ps.players.get(loc.player[0])!.hands[loc.player[1]]
+ throw new Error("invalid item location");
+ }
+ get_item_location_tracking(loc: ItemLocation): V2 | undefined {
+ if ("tile" in loc) return this.ts.tiles.get(loc.tile.toString())?.position
+ if ("player" in loc) return this.ps.players.get(loc.player[0])?.position
+ throw new Error("invalid item location");
+ }
+
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ for (const item of this.items_removed)
+ draw_item(ctx, this.ss, item)
+
+ for (const [_, tile] of this.ts.tiles)
+ if (tile.item) draw_item(ctx, this.ss, tile.item)
+ }
+}
+
+export function draw_item(ctx: CanvasRenderingContext2D, sprites: SSprites, item: ItemData) {
+ ctx.save()
+ ctx.translate(item.x, item.y)
+ if (item.remove_anim) ctx.scale(1 - item.remove_anim, 1 - item.remove_anim)
+ sprites.draw_item(ctx, item.kind)
+ if (item.active) {
+ ctx.beginPath()
+ ctx.strokeStyle = item.active.warn
+ ? `rgba(230, 58, 58, ${Math.sin(item.active.position * 100.) * 0.3 + 0.7})`
+ : "rgb(115, 230, 58)"
+ ctx.lineWidth = 0.1
+ ctx.arc(0, 0, 0.45, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * item.active.position)
+ ctx.stroke()
+ }
+ ctx.restore()
+}
diff --git a/test-client/map/message.ts b/test-client/map/message.ts
new file mode 100644
index 00000000..ff492e48
--- /dev/null
+++ b/test-client/map/message.ts
@@ -0,0 +1,107 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { message_str } from "../locale.ts";
+import { camera } from "../main.ts";
+import { float, Message, MessageTimeout } from "../protocol.ts";
+import { lerp_exp, V2 } from "../util.ts";
+import { SSprites } from "./sprites.ts";
+
+const MESSAGE_BG: { [key in MessageStyle]: string } = { normal: "#fff", hint: "#111", error: "#fff", pinned: "rgb(4, 32, 0)" }
+const MESSAGE_FG: { [key in MessageStyle]: string } = { normal: "#000", hint: "#fff", error: "#a00", pinned: "#000" }
+
+export type MessageStyle = "hint" | "normal" | "error" | "pinned"
+
+export class MessageRenderer {
+ constructor(
+ public style: MessageStyle,
+ public inner: Message,
+ public anim_position: V2,
+ public anim_size: number,
+ public timeout?: MessageTimeout,
+ ) { }
+
+ tick(dt: float): boolean {
+ this.anim_size = lerp_exp(this.anim_size, this.timeout ? this.timeout.remaining > 0.3 ? 1 : 0 : 1, dt * 8)
+ if (!this.timeout) return false
+ this.timeout.remaining -= dt;
+ return this.timeout.remaining <= 0
+ }
+ draw(ctx: CanvasRenderingContext2D, sprites?: SSprites, world?: boolean) {
+ ctx.save()
+ ctx.translate(this.anim_position.x, this.anim_position.y)
+ const scale = Math.min(this.anim_size, 1 - camera.overlay_vis_anim);
+ ctx.scale(scale, scale)
+ if ("item" in this.inner) {
+ ctx.fillStyle = MESSAGE_BG[this.style]
+ if (this.style == "pinned") {
+ ctx.translate(0, 1)
+ ctx.beginPath()
+ ctx.arc(0, -1, 0.5, 0, Math.PI * 2)
+ ctx.fill()
+ } else {
+ ctx.beginPath()
+ ctx.moveTo(0, -0.3)
+ ctx.arc(0, -1, 0.5, Math.PI / 4, Math.PI - Math.PI / 4, true)
+ ctx.closePath()
+ ctx.fill()
+ }
+
+ if (this.timeout) {
+ const t = this.timeout.remaining / this.timeout.initial;
+ ctx.beginPath()
+ ctx.strokeStyle = `hsl(${Math.sqrt(t) * 0.3}turn, 100%, 50%)`
+ ctx.lineWidth = 0.1
+ ctx.arc(0, -1, 0.45, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * (1 - t))
+ ctx.stroke()
+ }
+
+ ctx.translate(0, -1)
+ ctx.save()
+ sprites?.draw_item(ctx, this.inner.item)
+ ctx.restore()
+ ctx.translate(0, 1)
+ }
+ if ("text" in this.inner || "translation" in this.inner) {
+ ctx.translate(0, -1)
+
+ ctx.textAlign = "center"
+ ctx.font = "15px " + (world ? "sans-serif" : "monospace")
+ if (world) ctx.scale(2 / camera.scale, 2 / camera.scale)
+
+ const lines = message_str(this.inner).split("\n")
+ const w = lines.reduce((a, v) => Math.max(a, ctx.measureText(v).width), 0) + 10
+
+ if (world) ctx.translate(0, -lines.length * 15 / 2)
+
+ ctx.fillStyle = MESSAGE_BG[this.style]
+ ctx.beginPath()
+ ctx.roundRect(-w / 2, -5, w, lines.length * 15 + 10, 5)
+ ctx.fill()
+
+ ctx.fillStyle = MESSAGE_FG[this.style]
+ ctx.textAlign = "left"
+ ctx.textBaseline = "top"
+ for (let i = 0; i < lines.length; i++)
+ ctx.fillText(lines[i], -w / 2 + 5, i * 15)
+
+ ctx.translate(0, 1)
+ }
+ ctx.restore()
+ }
+}
diff --git a/test-client/map/mod.ts b/test-client/map/mod.ts
new file mode 100644
index 00000000..76ee2c0c
--- /dev/null
+++ b/test-client/map/mod.ts
@@ -0,0 +1,43 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { camera } from "../main.ts";
+import { ceil_v2, floor_v2 } from "../util.ts";
+
+export interface MapLayer {
+ draw_map_layer(ctx: CanvasRenderingContext2D): void
+}
+
+export class GridLayer implements MapLayer {
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ ctx.strokeStyle = "#333"
+ ctx.lineWidth = 0.01
+ ctx.beginPath()
+ const min = floor_v2(camera.screen_to_world({ x: 0, y: 0 }))
+ const max = ceil_v2(camera.screen_to_world({ x: ctx.canvas.width, y: ctx.canvas.height }))
+ for (let x = min.x; x < max.x; x++) {
+ ctx.moveTo(x, min.y)
+ ctx.lineTo(x, max.y)
+ }
+ for (let y = min.y; y < max.y; y++) {
+ ctx.moveTo(min.x, y)
+ ctx.lineTo(max.x, y)
+ }
+ ctx.stroke()
+ }
+}
diff --git a/test-client/map/particles.ts b/test-client/map/particles.ts
new file mode 100644
index 00000000..0d5da6fc
--- /dev/null
+++ b/test-client/map/particles.ts
@@ -0,0 +1,90 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+import { MapLayer } from "./mod.ts";
+import { System } from "../system.ts";
+import { V2 } from "../util.ts";
+import { player_controller } from "../main.ts";
+
+export class SParticles extends System implements MapLayer {
+ particles: Set<Particle> = new Set()
+ particles_to_remove: Set<Particle> = new Set()
+ override tick(dt: number): void {
+ this.particles.forEach(p => p.tick(dt) ? {} : this.particles_to_remove.add(p))
+ this.particles_to_remove.forEach(p => this.particles.delete(p))
+ this.particles_to_remove = new Set()
+ }
+
+ override input(ev: KeyboardEvent, down: boolean): boolean {
+ if (down && ev.code == "KeyE") {
+ this.create_splash(player_controller.get_interact_target() ?? { x: 0, y: 0 }, 0.8)
+ return true
+ }
+ return false
+ }
+
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ ctx.save()
+ this.particles.forEach(p => p.draw(ctx))
+ ctx.restore()
+ }
+
+ create_splash(pos: V2, hue: number) {
+ for (let i = 0; i < 64; i++) {
+ const p = new Particle()
+ p.px = pos.x + 0.5
+ p.py = pos.y + 0.5
+ const a = Math.random() * Math.PI * 2
+ const r = Math.sqrt(Math.random()) * 5.
+ p.vx = Math.sin(a) * r
+ p.vy = Math.cos(a) * r
+ p.decay = 4.
+ p.tr = Math.sqrt(Math.random()) * 1.
+ p.c = `hsl(${Math.random() * 0.2 - 0.1 + hue}turn, 100%, 50%)`
+ p.r = Math.random() * 0.1 + 0.03
+ this.particles.add(p)
+ }
+ }
+}
+
+class Particle {
+ c = "red"
+ r = 0.05
+ tr = 0
+ px = 0
+ py = 0
+ vx = 0
+ vy = 0
+ decay = 1
+
+ tick(dt: number) {
+ this.tr -= dt
+ this.px += this.vx * dt;
+ this.py += this.vy * dt;
+ this.vx *= Math.exp(-dt * this.decay)
+ this.vy *= Math.exp(-dt * this.decay)
+ return this.tr > 0.
+ }
+ draw(ctx: CanvasRenderingContext2D) {
+ ctx.fillStyle = this.c
+ ctx.globalAlpha = Math.min(1, this.tr * 5.)
+ ctx.beginPath()
+ ctx.arc(this.px, this.py, this.r, 0, Math.PI * 2)
+ ctx.fill()
+ }
+}
+
diff --git a/test-client/map/players.ts b/test-client/map/players.ts
new file mode 100644
index 00000000..46336955
--- /dev/null
+++ b/test-client/map/players.ts
@@ -0,0 +1,209 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { camera, data, player_controller } from "../main.ts";
+import { collide_player_player, MovementBase, PLAYER_SIZE, update_movement } from "../movement.ts";
+import { PacketC, PlayerClass, PlayerID } from "../protocol.ts";
+import { System } from "../system.ts";
+import { lerp_exp_v2_mut, V2 } from "../util.ts";
+import { draw_item } from "./item.ts";
+import { MessageRenderer } from "./message.ts";
+import { MapLayer } from "./mod.ts";
+import { SSprites } from "./sprites.ts";
+import { ItemSlot, SMapTiles } from "./tiles.ts";
+
+export interface PlayerData extends MovementBase {
+ id: number,
+ name: string,
+ hands: ItemSlot[],
+ class: PlayerClass,
+ character: number,
+ anim_position: V2,
+ message_persist?: MessageRenderer,
+ message_pinned?: MessageRenderer,
+ message?: MessageRenderer,
+}
+
+export class SPlayers extends System implements MapLayer {
+ players: Map<PlayerID, PlayerData> = new Map()
+
+ constructor(
+ public ss: SSprites,
+ public ts: SMapTiles
+ ) { super() }
+
+ override tick(dt: number): void {
+ this.players.forEach(p => update_movement(this.ts, p, dt))
+ for (const [_, a] of this.players)
+ for (const [_, b] of this.players)
+ collide_player_player(a, b, dt)
+
+ for (const [pid, player] of this.players) {
+ if (pid == player_controller.my_id) player.anim_position.x = player.position.x, player.anim_position.y = player.position.y
+ else lerp_exp_v2_mut(player.anim_position, player.position, dt * 15)
+ player.hands.forEach(h => {
+ if (h.item) {
+ if (h.item.tracking) lerp_exp_v2_mut(h.item, h.item.tracking, dt * 10.)
+ if (h.item.active) h.item.active.position += h.item.active.speed * dt
+ }
+ })
+ if (player.message && player.message.tick(dt)) delete player.message
+ if (player.message_persist && player.message_persist.tick(dt)) delete player.message_persist
+ }
+ }
+
+ override packet(p: PacketC): void {
+ if (p.type == "add_player") {
+ this.players.set(p.id, {
+ id: p.id,
+ hands: new Array(data.hand_count).fill(null).map(() => ({})),
+ position: { x: p.position[0], y: p.position[1], },
+ anim_position: { x: p.position[0], y: p.position[1] },
+ character: p.character.color,
+ class: p.class,
+ name: p.name,
+ rotation: 0,
+ facing: { x: 0, y: 1 },
+ velocity: { x: 0, y: 0 },
+ stamina: 0,
+ boosting: false,
+ input_boost: false,
+ input_direction: { x: 0, y: 0 },
+ })
+ } else if (p.type == "remove_player") {
+ this.players.delete(p.id)
+ } else if (p.type == "movement") {
+ const pl = this.players.get(p.player)!
+ if (p.player == player_controller.my_id) return
+ pl.position.x = p.pos[0]
+ pl.position.y = p.pos[1]
+ pl.rotation = p.rot
+ pl.input_direction.x = p.dir[0]
+ pl.input_direction.y = p.dir[1]
+ pl.input_boost = p.boost
+ } else if (p.type == "communicate") {
+ const player = this.players.get(p.player)!
+ if (p.message) {
+ const message = new MessageRenderer(
+ "normal",
+ p.message,
+ player.anim_position,
+ player.message_persist?.anim_size ?? 0,
+ p.timeout ?? { initial: 5, remaining: 5, pinned: false },
+ )
+ if (p.timeout) player.message_persist = message
+ else player.message = message
+ if (p.timeout?.pinned) player.message_pinned = new MessageRenderer(
+ "pinned",
+ p.message,
+ { x: 20, y: 0 },
+ player.message_pinned?.anim_size ?? 0,
+ { ...p.timeout }
+ )
+ } else {
+ delete player.message_persist
+ delete player.message_pinned
+ }
+ }
+ }
+
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ for (const [_, player] of this.players)
+ draw_player(ctx, this.ss, player)
+ }
+}
+
+export class SPlayersOverlay extends System implements MapLayer {
+ constructor(
+ public ss: SSprites,
+ public ps: SPlayers,
+ ) { super() }
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ for (const [_, player] of this.ps.players)
+ if (player.message) player.message.draw(ctx, this.ss, true)
+
+ for (const [_, player] of this.ps.players)
+ if (player.message_persist) player.message_persist.draw(ctx, this.ss, true)
+
+ for (const [_, player] of this.ps.players)
+ draw_player_nametag(ctx, player)
+
+ }
+}
+
+function draw_player(ctx: CanvasRenderingContext2D, sprites: SSprites, player: PlayerData) {
+ ctx.save()
+ ctx.translate(player.anim_position.x, player.anim_position.y)
+ ctx.rotate(-player.rotation)
+ if (player.boosting) ctx.scale(1.3, 1.3)
+ draw_character(ctx, player.class, player.character)
+ // // show moving players
+ // if (length(player.input_direction) > 0.1) {
+ // ctx.fillStyle = "white"
+ // ctx.fillRect(-0.1, -0.1, 0.2, 0.2)
+ // }
+ ctx.restore()
+ ctx.save()
+ for (const h of player.hands) {
+ if (h.item) draw_item(ctx, sprites, h.item)
+ ctx.translate(0.2, 0.0)
+ }
+ ctx.restore()
+}
+
+function draw_character(ctx: CanvasRenderingContext2D, pclass: PlayerClass, character: number) {
+ ctx.fillStyle = `hsl(${character}rad, 50%, 50%)`
+ ctx.beginPath()
+ ctx.arc(0, 0, PLAYER_SIZE, 0, Math.PI * 2)
+ ctx.fill()
+
+ if (pclass != "customer") {
+ ctx.fillStyle = `hsl(${character}rad, 80%, 10%)`
+ ctx.beginPath()
+ ctx.arc(0, -0.2, PLAYER_SIZE, 0, Math.PI * 2)
+ ctx.fill()
+ }
+ if (pclass != "bot") {
+ ctx.fillStyle = `hsl(${character}rad, 80%, 70%)`
+ ctx.beginPath()
+ ctx.moveTo(-0.04, 0.25)
+ ctx.lineTo(0.04, 0.25)
+ ctx.lineTo(0, 0.4)
+ }
+ ctx.fill()
+}
+
+function draw_player_nametag(ctx: CanvasRenderingContext2D, player: PlayerData) {
+ if (camera.overlay_vis_anim > 0.01 && player.name.length) {
+ ctx.save()
+ ctx.translate(player.anim_position.x, player.anim_position.y)
+ ctx.translate(0, -1)
+ ctx.textAlign = "center"
+ ctx.font = "15px sans-serif"
+ ctx.scale(camera.overlay_vis_anim / camera.scale, camera.overlay_vis_anim / camera.scale)
+ const w = ctx.measureText(player.name).width + 20
+ ctx.fillStyle = "#fffa"
+ ctx.beginPath()
+ ctx.roundRect(-w / 2, -10, w, 20, 5)
+ ctx.fill()
+ ctx.fillStyle = "black"
+ ctx.textBaseline = "middle"
+ ctx.fillText(player.name, 0, 0)
+ ctx.restore()
+ }
+}
diff --git a/test-client/map/server_hints.ts b/test-client/map/server_hints.ts
new file mode 100644
index 00000000..67c19ff0
--- /dev/null
+++ b/test-client/map/server_hints.ts
@@ -0,0 +1,58 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+import { player_controller } from "../main.ts";
+import { PacketC } from "../protocol.ts";
+import { System } from "../system.ts";
+import { MessageRenderer } from "./message.ts";
+import { MapLayer } from "./mod.ts";
+import { SPlayers } from "./players.ts";
+import { SSprites } from "./sprites.ts";
+
+export class SServerHints extends System implements MapLayer {
+ hints: Map<string, MessageRenderer> = new Map()
+
+ constructor(
+ public ss: SSprites,
+ public ps: SPlayers,
+ ) { super() }
+
+ override packet(p: PacketC): void {
+ if (p.type == "server_hint") {
+ if (p.player != player_controller.my_id) return;
+ if (p.message) this.hints.set(p.position + "", new MessageRenderer(
+ "hint",
+ p.message,
+ p.position ?
+ { x: p.position[0] + 0.5, y: p.position[1] + 0.5 } :
+ this.ps.players.get(player_controller.my_id)?.anim_position ?? { x: 0, y: 0 },
+ 0.,
+ ))
+ else this.hints.delete(p.position + "")
+ }
+ }
+
+ override tick(dt: number): void {
+ for (const [_, h] of this.hints)
+ h.tick(dt)
+ }
+
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ for (const [_, message] of this.hints)
+ message.draw(ctx, this.ss, true)
+ }
+}
diff --git a/test-client/map/sprites.ts b/test-client/map/sprites.ts
new file mode 100644
index 00000000..43095631
--- /dev/null
+++ b/test-client/map/sprites.ts
@@ -0,0 +1,215 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { float, Gamedata, int, ItemIndex, PacketC } from "../protocol.ts";
+import { System } from "../system.ts";
+
+import item_sprites_raw from "../assets/item_sprites.ini"
+import tile_sprites_raw from "../assets/tile_sprites.ini"
+
+let sprites_raw: { [key: string]: string[] } = {}
+
+export class SSprites extends System {
+ items: Map<number, Sprite> = new Map()
+ tiles: Map<number, Sprite> = new Map()
+
+ override packet(p: PacketC): void {
+ if (p.type == "game_data")
+ this.load(p)
+ }
+
+ load(data: Gamedata) {
+ if (!Object.keys(sprites_raw).length)
+ sprites_raw = parse_sprite_file(item_sprites_raw + tile_sprites_raw)
+ for (let i = 0; i < data.tile_names.length; i++)
+ this.tiles.set(i, load_sprite(data.tile_names[i]))
+ for (let i = 0; i < data.item_names.length; i++)
+ this.items.set(i, load_sprite(data.item_names[i]))
+ }
+ draw_item(ctx: CanvasRenderingContext2D, i: ItemIndex) { this.items.get(i)!.draw(ctx) }
+ draw_tile(ctx: CanvasRenderingContext2D, i: ItemIndex) { this.tiles.get(i)!.draw(ctx) }
+}
+
+interface SpritePart {
+ fill?: string
+ stroke?: string,
+ line_width: number,
+ line_join: number,
+ path: Path2D
+ path_raw: string,
+}
+class Sprite {
+ constructor(public parts: SpritePart[] = []) { }
+ draw(ctx: CanvasRenderingContext2D) {
+ for (const p of this.parts) {
+ if (p.fill) ctx.fillStyle = p.fill
+ if (p.stroke) ctx.strokeStyle = p.stroke
+ ctx.lineWidth = p.line_width
+ ctx.lineJoin = (["miter", "round", "bevel"] as const)[p.line_join]
+ if (p.fill) ctx.fill(p.path)
+ if (p.stroke) ctx.stroke(p.path)
+ }
+ }
+}
+
+function parse_sprite_file(f: string): { [key: string]: string[] } {
+ let item = ""
+ const out: { [key: string]: string[] } = {}
+ for (const line of f.split("\n")) {
+ if (line.startsWith("#")) continue
+ else if (!line.trim().length) continue
+ else if (line.startsWith("[") && line.endsWith("]"))
+ item = line.substring(1, line.length - 1)
+ else {
+ out[item] = out[item] ?? []
+ out[item].push(line.trim())
+ }
+ }
+ return out
+}
+
+function fallback_sprite(name: string): Sprite {
+ console.warn(`missing sprite for "${name}"`);
+ const path_raw = "m -.5 -.5 h 1 v 1 h -1 z"
+ return new Sprite([{ path: new Path2D(path_raw), path_raw, fill: "#f0f7", line_width: 0.05, line_join: 1 }])
+}
+
+function calc_color_shade(color: string): string {
+ const prefix = color.startsWith("rgba") ? "rgba(" : "rgb("
+ if (!color.startsWith(prefix) || !color.endsWith(")")) throw "a"
+ color = color.substring(prefix.length, color.length - ")".length)
+ const [r, g, b, a] = color.split(",")
+ const [r2, g2, b2] = [r, g, b]
+ .map(e => parseInt(e))
+ .map(e => e / 1.3)
+ .map(e => Math.max(Math.min(e, 255), 0) | 0)
+
+ return a != undefined
+ ? `rgba(${r2}, ${g2}, ${b2}, ${a})`
+ : `rgb(${r2}, ${g2}, ${b2})`
+}
+
+function variant_handler(_p: SpritePart, ps: SpritePart[], mode: string, variant: string) {
+ if (mode == "sprite") {
+ if (variant == "") return
+ ps.push(...load_sprite(variant).parts)
+ } else if (mode == "arrange_sprites") {
+ if (variant == "") return
+ const items = variant.search(":") == -1 ? variant.split(",") : [variant];// dont split if nesting
+ for (let i = 0; i < items.length; i++) {
+ const t = i / items.length * Math.PI * 2.
+ const radius = Math.sqrt(items.length - 1) * 0.15
+ const off_x = Math.sin(t) * radius
+ const off_y = Math.cos(t) * radius
+ const scale = 1. / Math.sqrt(Math.max(2, items.length))
+
+ const item_sprite = load_sprite(items[i])
+ for (const p of item_sprite.parts) {
+ p.line_width *= scale
+ p.path_raw = transform_path(p.path_raw, scale, off_x, off_y)
+ ps.push(p)
+ }
+ }
+ } else if (mode == "stack_sprites") {
+ if (variant == "") return
+ for (const item of variant.split(",")) {
+ const item_sprite = load_sprite(item)
+ for (const p of item_sprite.parts) {
+ p.line_width *= 0.8
+ p.path_raw = transform_path(p.path_raw, 0.8, 0, 0)
+ ps.push(p)
+ }
+ }
+ } else {
+ console.warn(`unknown sprite variant handler: "${mode}"`);
+ }
+}
+
+function load_sprite(name: string): Sprite {
+ let main = ""
+ let variant = ""
+ if (name in sprites_raw) main = name // special case or no variant
+ else {
+ const [a, ...b] = name.split(":")
+ main = a, variant = b.join(":")
+ }
+ const parts_raw = sprites_raw[main]
+ if (!parts_raw) return fallback_sprite(name)
+ const parts: SpritePart[] = []
+ for (const pr of parts_raw) {
+ const p: SpritePart = { path: new Path2D(), path_raw: "", line_width: 0.03, line_join: 1 }
+ let scale = 0.25
+ for (const t of pr.split("|")) {
+ const [ty, ...pd_a] = t.split(" ")
+ const pd = pd_a.join(" ")
+ if (ty == "p") { p.path_raw = transform_path(pd, scale, 0, 0) }
+ else if (ty == "f") p.fill = pd
+ else if (ty == "s" && !pd.length) p.stroke = p.fill
+ else if (ty == "s") p.stroke = pd
+ else if (ty == "sc") scale = parseFloat(pd)
+ else if (ty == "lw") p.line_width = parseFloat(pd)
+ else if (ty == "lj") p.line_join = parseInt(pd)
+ else if (ty == "as") p.stroke = calc_color_shade(p.fill!)
+ else if (ty == "af") p.fill = calc_color_shade(p.stroke!)
+ else if (ty == "v") variant_handler(p, parts, pd, variant)
+ else console.warn(`unknown sprite part token "${t}"`)
+ }
+ if (p.path_raw.length) parts.push(p)
+ }
+ for (const p of parts) p.path = new Path2D(p.path_raw)
+ return new Sprite(parts)
+}
+
+const PATH_COMMANDS: { [key: string]: ("x" | "y" | "dx" | "dy" | "aux")[] } = {
+ "M": ["x", "y"], "m": ["dx", "dy"],
+ "L": ["x", "y"], "l": ["dx", "dy"],
+ "H": ["x"], "h": ["dx"],
+ "V": ["y"], "v": ["dy"],
+ "Z": [], "z": [],
+ "C": ["x", "y", "x", "y", "x", "y"], "c": ["dx", "dy", "dx", "dy", "dx", "dy"],
+ "S": ["x", "y", "x", "y"], "s": ["dx", "dy", "dx", "dy"],
+ "Q": ["x", "y", "x", "y"], "q": ["dx", "dy", "dx", "dy"],
+ "T": ["x", "y"], "t": ["dx", "dy"],
+ "A": ["dx", "dy", "aux", "aux", "aux", "x", "y"], "a": ["dx", "dy", "aux", "aux", "aux", "dx", "dy"],
+}
+
+
+function transform_path(path: string, scale: float, xoff: float, yoff: float): string {
+ const toks = path.split(" ")
+ let i = 0
+ const sc = (i: int) => toks[i] = (parseFloat(toks[i]) * scale).toString()
+ const xo = (i: int) => toks[i] = (parseFloat(toks[i]) + xoff).toString()
+ const yo = (i: int) => toks[i] = (parseFloat(toks[i]) + yoff).toString()
+ while (i < toks.length) {
+ const first = i == 0;
+ const cmd = toks[i++]
+ const args = PATH_COMMANDS[cmd]
+ if (!args) throw `invalid path command "${cmd}" in "${path}"`
+ for (const a of args) {
+ switch (a) {
+ case "x": sc(i); xo(i); break;
+ case "y": sc(i); yo(i); break;
+ case "dx": sc(i); if (first) xo(i); break;
+ case "dy": sc(i); if (first) yo(i); break;
+ case "aux": break;
+ }
+ i++
+ }
+ }
+ return toks.join(" ")
+}
diff --git a/test-client/map/tiles.ts b/test-client/map/tiles.ts
new file mode 100644
index 00000000..d90d0741
--- /dev/null
+++ b/test-client/map/tiles.ts
@@ -0,0 +1,65 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { PacketC, TileIndex } from "../protocol.ts";
+import { System } from "../system.ts";
+import { MapLayer } from "./mod.ts";
+import { ItemData } from "./item.ts";
+import { V2 } from "../util.ts";
+import { SSprites } from "./sprites.ts";
+
+export interface ItemSlot {
+ item?: ItemData,
+}
+export interface TileData extends ItemSlot {
+ x: number, y: number // tile position
+ position: V2, // center position
+ parts: TileIndex[]
+}
+
+export class SMapTiles extends System implements MapLayer {
+ tiles: Map<string, TileData> = new Map()
+
+ constructor(public ss: SSprites) { super() }
+
+ override packet(p: PacketC): void {
+ if (p.type == "update_map") {
+ for (const [pos, parts] of p.changes) {
+ this.tiles.set(pos.toString(), {
+ x: pos[0],
+ y: pos[1],
+ position: { x: pos[0] + 0.5, y: pos[1] + 0.5 },
+ parts
+ })
+ if (!parts.length)
+ this.tiles.delete(pos.toString())
+ }
+ }
+ }
+
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ for (const [_, tile] of this.tiles) {
+ ctx.save()
+ ctx.translate(tile.x + 0.5, tile.y + 0.5)
+ for (const p of tile.parts)
+ this.ss.draw_tile(ctx, p)
+ ctx.restore()
+
+ }
+ }
+}
diff --git a/test-client/movement.ts b/test-client/movement.ts
index 7c320b5f..eed7bdd9 100644
--- a/test-client/movement.ts
+++ b/test-client/movement.ts
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -16,7 +16,7 @@
*/
import { data } from "./main.ts";
-import { tiles } from "./main.ts";
+import { SMapTiles } from "./map/tiles.ts";
import { V2, normalize, length, sub_v2, lerp_exp_v2_mut } from "./util.ts";
export const PLAYER_SIZE = 0.4
@@ -37,7 +37,7 @@ export interface MovementBase {
stamina: number
}
-export function update_movement(p: MovementBase, dt: number) {
+export function update_movement(ts: SMapTiles, p: MovementBase, dt: number) {
const direction = p.input_direction;
let boost = p.input_boost;
if (length(direction) > 0.05) lerp_exp_v2_mut(p.facing, direction, dt * 10.)
@@ -52,15 +52,15 @@ export function update_movement(p: MovementBase, dt: number) {
lerp_exp_v2_mut(p.velocity, { x: direction.x * speed, y: direction.y * speed }, dt * PLAYER_ACCELERATION)
p.position.x += p.velocity.x * dt
p.position.y += p.velocity.y * dt
- collide_player(p, dt)
+ collide_player(ts, p, dt)
}
-function collide_player(p: MovementBase, _dt: number) {
+function collide_player(ts: SMapTiles, p: MovementBase, _dt: number) {
for (let xo = -1; xo <= 1; xo++) {
for (let yo = -1; yo <= 1; yo++) {
const x = Math.floor(p.position.x) + xo
const y = Math.floor(p.position.y) + yo
- const tile = tiles.get([x, y].toString())
+ const tile = ts.tiles.get([x, y].toString())
if (!tile) continue
let has_coll = false
for (const p of tile.parts) {
diff --git a/test-client/particles.ts b/test-client/particles.ts
deleted file mode 100644
index e84e9c91..00000000
--- a/test-client/particles.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { ctx } from "./main.ts";
-import { V2 } from "./util.ts";
-
-const particles = new Set<Particle>()
-let particles_to_remove = new Set<Particle>()
-
-export function tick_particles(dt: number) {
- particles.forEach(p => p.tick(dt) ? {} : particles_to_remove.add(p))
- particles_to_remove.forEach(p => particles.delete(p))
- particles_to_remove = new Set()
-}
-export function draw_particles() {
- particles.forEach(p => p.draw())
-}
-export function particle_count() { return particles.size }
-
-export function particle_splash(pos: V2, hue: number) {
- for (let i = 0; i < 64; i++) {
- const p = new Particle()
- p.px = pos.x + 0.5
- p.py = pos.y + 0.5
- const a = Math.random() * Math.PI * 2
- const r = Math.sqrt(Math.random()) * 5.
- p.vx = Math.sin(a) * r
- p.vy = Math.cos(a) * r
- p.decay = 4.
- p.tr = Math.sqrt(Math.random()) * 1.
- p.c = `hsl(${Math.random() * 0.2 - 0.1 + hue}turn, 100%, 50%)`
- p.r = Math.random() * 0.1 + 0.03
- particles.add(p)
- }
-}
-
-class Particle {
- c = "red"
- r = 0.05
- tr = 0
- px = 0
- py = 0
- vx = 0
- vy = 0
- decay = 1
-
- tick(dt: number) {
- this.tr -= dt
- this.px += this.vx * dt;
- this.py += this.vy * dt;
- this.vx *= Math.exp(-dt * this.decay)
- this.vy *= Math.exp(-dt * this.decay)
- return this.tr > 0.
- }
- draw() {
- ctx.fillStyle = this.c
- ctx.globalAlpha = Math.min(1, this.tr * 5.)
- ctx.beginPath()
- ctx.arc(this.px, this.py, this.r, 0, Math.PI * 2)
- ctx.fill()
- }
-}
-
diff --git a/test-client/pinned_messages.ts b/test-client/pinned_messages.ts
new file mode 100644
index 00000000..e0ef47d7
--- /dev/null
+++ b/test-client/pinned_messages.ts
@@ -0,0 +1,48 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { SPlayers } from "./map/players.ts";
+import { SSprites } from "./map/sprites.ts";
+import { System } from "./system.ts";
+import { lerp_exp_v2_mut } from "./util.ts";
+
+export class SPinnedMessages extends System {
+ constructor(
+ public ss: SSprites,
+ public ps: SPlayers
+ ) { super() }
+
+ override tick(dt: number): void {
+ let pin_xo = 0
+ for (const [_, player] of this.ps.players) {
+ if (player.message_pinned && player.message_pinned.tick(dt)) delete player.message_pinned
+ if (player.message_pinned) lerp_exp_v2_mut(player.message_pinned.anim_position, { x: pin_xo++, y: 0 }, dt * 5)
+ }
+ }
+ override draw(ctx: CanvasRenderingContext2D): void {
+ const scale = Math.max(80, ctx.canvas.width / 20)
+ ctx.save()
+ ctx.scale(scale, scale)
+ ctx.translate(0.8, 0.8)
+
+ for (const [_, player] of this.ps.players)
+ if (player.message_pinned) player.message_pinned.draw(ctx, this.ss, false)
+
+ ctx.restore()
+ }
+}
diff --git a/test-client/player_controller.ts b/test-client/player_controller.ts
new file mode 100644
index 00000000..00613ab2
--- /dev/null
+++ b/test-client/player_controller.ts
@@ -0,0 +1,174 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { data, keys_down, send } from "./main.ts";
+import { MapLayer } from "./map/mod.ts";
+import { PlayerData, SPlayers } from "./map/players.ts";
+import { ItemSlot, SMapTiles, TileData } from "./map/tiles.ts";
+import { PacketC } from "./protocol.ts";
+import { System } from "./system.ts";
+import { lerp_exp, lerp_exp_v2_mut, normalize, V2 } from "./util.ts";
+
+const KEY_INTERACT_LEFT = "KeyJ"
+const KEY_INTERACT_RIGHT = "KeyL"
+const KEY_BOOST = "KeyK"
+const KEY_UP = "KeyW"
+const KEY_DOWN = "KeyS"
+const KEY_LEFT = "KeyA"
+const KEY_RIGHT = "KeyD"
+
+export class SPlayerController extends System implements MapLayer {
+ my_id?: number = undefined
+ last_direction: V2 = { x: 0, y: 0 }
+ last_boost: boolean = false
+ interacting?: V2;
+ last_server_sent_position: V2 = { x: 0, y: 0 }
+ paused = false
+
+ interact_target_anim: V2 = { x: 0, y: 0 }
+ interact_possible_anim: number = 0
+ interact_active_anim: number = 0
+
+ constructor(
+ public ts: SMapTiles,
+ public ps: SPlayers
+ ) { super() }
+
+ override input(ev: KeyboardEvent, down: boolean): boolean {
+ if (!keys_down.has(KEY_INTERACT_LEFT) && ev.code == KEY_INTERACT_LEFT && down) this.set_interact(true, 0)
+ else if (keys_down.has(KEY_INTERACT_LEFT) && ev.code == KEY_INTERACT_LEFT && !down) this.set_interact(false, 0)
+ else if (!keys_down.has(KEY_INTERACT_RIGHT) && ev.code == KEY_INTERACT_RIGHT && down) this.set_interact(true, 1)
+ else if (keys_down.has(KEY_INTERACT_RIGHT) && ev.code == KEY_INTERACT_RIGHT && !down) this.set_interact(false, 1)
+ else return false
+ return true
+ }
+ override packet(p: PacketC): void {
+ if (p.type == "joined") {
+ this.my_id = p.id
+ } else if (p.type == "movement" && p.player == this.my_id && p.sync) {
+ const me = this.ps.players.get(this.my_id!)
+ if (me) { // ensuring object has same ref
+ me.position.x = p.pos[0]
+ me.position.y = p.pos[1]
+ }
+ } else if (p.type == "pause") {
+ this.paused = p.state
+ }
+ }
+
+ override tick(dt: number): void {
+ if (this.my_id === undefined) return
+ const p = this.ps.players.get(this.my_id)
+ if (!p) return
+
+ const direction = normalize({
+ x: (+keys_down.has(KEY_RIGHT) - +keys_down.has(KEY_LEFT)),
+ y: (+keys_down.has(KEY_DOWN) - +keys_down.has(KEY_UP))
+ })
+ if (this.interacting || this.paused) direction.x *= 0, direction.y *= 0
+ p.input_direction = direction
+ p.input_boost = keys_down.has(KEY_BOOST)
+ if (this.last_boost != p.input_boost || this.last_direction.x != p.input_direction.x || this.last_direction.y != p.input_direction.y)
+ this.send_movement(p)
+ this.last_direction = p.input_direction
+ this.last_boost = p.input_boost
+
+
+ const it = this.get_interact_target() ?? { x: 0, y: 0 };
+ const target_tile = this.ts.tiles.get([it.x, it.y].toString())
+ const me = this.ps.players.get(this.my_id!)
+ const possible = target_tile && me && me.hands.find(h => can_interact_with_tile(target_tile, h))
+ lerp_exp_v2_mut(this.interact_target_anim, it, dt * 15.)
+ this.interact_possible_anim = lerp_exp(this.interact_possible_anim, +!!possible, dt * 18.)
+ this.interact_active_anim = lerp_exp(this.interact_active_anim, +!!this.interacting, dt * 15.)
+ }
+
+ get_interact_target(): V2 | undefined {
+ if (this.interacting) return this.interacting
+ const me = this.ps.players.get(this.my_id!)
+ if (!me) return
+
+ if (location.hash.search("oldinteract") != -1)
+ return {
+ x: Math.floor(me.position.x + Math.sin(me.rotation)),
+ y: Math.floor(me.position.y + Math.cos(me.rotation))
+ }
+
+ const rx = me.position.x + Math.sin(me.rotation) * 0.7
+ const ry = me.position.y + Math.cos(me.rotation) * 0.7
+ const bx = Math.floor(rx)
+ const by = Math.floor(ry)
+ let best = { x: bx, y: by }
+ let best_d = 100
+ for (let ox = -1; ox <= 1; ox++) for (let oy = -1; oy <= 1; oy++) {
+ const t = this.ts.tiles.get([bx + ox, by + oy] + "")
+ if (!t) continue
+ for (const hand of me.hands) {
+ if (!can_interact_with_tile(t, hand)) continue
+ const cx = (bx + ox + 0.5)
+ const cy = (by + oy + 0.5)
+ const dx = rx - cx
+ const dy = ry - cy
+ const pdx = me.position.x - cx
+ const pdy = me.position.y - cy
+ const d = Math.sqrt(dx * dx + dy * dy)
+ const pd = Math.sqrt(pdx * pdx + pdy * pdy)
+ if (pd < 2 && d < best_d) {
+ best_d = d
+ best = { x: bx + ox, y: by + oy }
+ }
+ }
+ }
+ return best
+ }
+
+ send_movement(p: PlayerData) {
+ send({ player: this.my_id!, type: "movement", pos: [p.position.x, p.position.y], dir: [p.input_direction.x, p.input_direction.y], boost: p.input_boost })
+ }
+
+ set_interact(edge: boolean, hand: number) {
+ if (edge) this.interacting = this.get_interact_target()
+ if (this.interacting) send({ player: this.my_id!, type: "interact", target: edge ? { tile: [this.interacting.x, this.interacting.y] } : undefined, hand })
+ if (!edge) this.interacting = undefined
+ }
+
+ draw_map_layer(ctx: CanvasRenderingContext2D): void {
+ ctx.save()
+ ctx.translate(this.interact_target_anim.x, this.interact_target_anim.y)
+ ctx.lineCap = "round"
+ ctx.lineJoin = "round"
+ ctx.lineWidth = 0.06 + 0.03 * Math.sin(Date.now() / 100) * this.interact_possible_anim
+ ctx.strokeStyle = `hsla(${(1 - this.interact_active_anim) * 225 + this.interact_active_anim * 125}deg, ${this.interact_possible_anim * 100}%, 62.70%, ${this.interact_possible_anim * 0.7 + 0.3})`
+ ctx.strokeRect(0, 0, 1, 1)
+ ctx.restore()
+ }
+}
+
+function can_interact_with_tile(t: TileData, hand: ItemSlot) {
+ if (t.item) return true
+ let can_interact = false
+ for (const part of t.parts) {
+ if (hand.item && data.tile_placeable_items[part])
+ can_interact = data.tile_placeable_items[part].includes(hand.item.kind)
+ if (hand.item && data.tile_placeable_any.includes(part))
+ can_interact = true
+ if (!hand.item && data.tile_interactable_empty.includes(part))
+ can_interact = true
+ }
+ return can_interact
+}
diff --git a/test-client/protocol.ts b/test-client/protocol.ts
index 26c7f47b..10cd055b 100644
--- a/test-client/protocol.ts
+++ b/test-client/protocol.ts
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -25,19 +25,22 @@ export type TileIndex = int // index used primarily for tile_names in Gamedata
export type Hand = int
export interface MapMetadata {
- name: string,
+ name: string, // name used for identification and filename
+ display_name: string, // name displayed to users
players: int,
difficulty: int,
+ hand_count: int,
+ demand_items: string[]
}
export interface Serverdata {
- maps: [string, MapMetadata][], // Metadata for most available maps
+ maps: MapMetadata[], // Metadata for most available maps
bot_algos: string[],
name: string,
motd?: string
}
export interface Gamedata {
- current_map: string,
+ metadata: MapMetadata,
item_names: string[], // Look-up table for ItemIndex
tile_names: string[], // Look-up table for TileIndex
tile_collide: TileIndex[], // List of TileIndex that have collision
@@ -68,20 +71,20 @@ export type PacketC =
| { type: "server_data" } & Serverdata // Server data was changed
| { type: "add_player", id: PlayerID, name: string, position: Vec2, character: Character, class: PlayerClass } // Somebody else joined (or was already in the game)
| { type: "remove_player", id: PlayerID } // Somebody left
- | { type: "movement", player: PlayerID, pos: Vec2, rot: float, boost: boolean, dir: Vec2 } // Update the movement of a players (your own position is included here)
- | { type: "movement_sync", player: PlayerID } // Your movement is difference on the server, you should update your position from a `position` packet
+ | { type: "movement", player: PlayerID, pos: Vec2, rot: float, boost: boolean, dir: Vec2, sync?: boolean } // Update the movement of a players (your own position is included here)
| { type: "move_item", from: ItemLocation, to: ItemLocation } // Item moved
| { type: "set_item", location: ItemLocation, item?: ItemIndex } // the item contained in a tile or player changed
| { type: "set_progress", item: ItemLocation, position: float, speed: float, warn: boolean, players: PlayerID[] } // An item is doing something. position goes from 0 to 1, speed unit is in 1 per second
| { type: "clear_progress", item: ItemLocation }
| { type: "update_map", changes: [IVec2, TileIndex[]][] } // List of position-tiles-pairs of the map that changed
| { type: "communicate", player: PlayerID, message?: Message, timeout?: MessageTimeout } // A player wants to communicate something, message is null when cleared
- | { type: "effect2", location: ItemLocation, name: string } // Player sent an effect
+ | { type: "effect", location: ItemLocation } & Effect
| { type: "server_message", message: Message, error: boolean } // Text message from the server
| { type: "server_hint", message?: Message, position?: IVec2, player: PlayerID } // Hint message from server with optional position. Message is unset to clear previous message
| { type: "vote_started", initiated_by: PlayerID, subject: VoteSubject, message: Message, timeout: float }
| { type: "vote_updated", total: int, agree: int, reject: int }
| { type: "vote_ended", result: boolean }
+ | { type: "spectator_count", count: int }
| { type: "score" } & Score // Supplies information for score OSD
| { type: "menu" } & Menu // Open a menu on the client-side
| { type: "environment", effects: string[] }
@@ -94,9 +97,14 @@ export type PacketC =
export interface Character {
color: int,
headwear: int,
- hairstyle: int
+ hairstyle: int,
}
+export type Effect =
+ { effect: "angry" }
+ | { effect: "satisfied" }
+ | { effect: "points", amount: int }
+
export type VoteSubject =
{ action: "start_game", config: GameConfig }
| { action: "end_game" }
@@ -104,8 +112,9 @@ export type VoteSubject =
export interface GameConfig {
map: string,
- hand_count: int
- timer?: float
+ hand_count: int,
+ timer?: float,
+ bots?: string[],
}
export type Menu =
@@ -113,6 +122,7 @@ export type Menu =
| { menu: "scoreboard", data: Scoreboard }
| { menu: "book", data: Book }
| { menu: "announce_start" }
+ | { menu: "map_selector" }
export interface MessageTimeout {
initial: float,
@@ -123,11 +133,11 @@ export interface MessageTimeout {
export interface Scoreboard {
map: string,
plays: int,
- best: ScoreboardEntry
+ best: ScoreboardEntry,
}
export interface ScoreboardEntry {
players: string[],
- score: Score
+ score: Score,
}
export interface Score {
@@ -162,13 +172,13 @@ export type BookPage =
| { page_type: "recipe", title: Message, description: Message, diagram: Diagram }
export interface Diagram {
- nodes: DiagramNode[]
- edges: DiagramEdge[]
+ nodes: DiagramNode[],
+ edges: DiagramEdge[],
}
export interface DiagramNode {
position: Vec2,
label: Message,
- style: NodeStyle
+ style: NodeStyle,
}
export interface DiagramEdge {
src: int,
@@ -183,10 +193,10 @@ export type NodeStyle =
export interface DebugEvent {
key: string,
- color: [float, float, float]
- display: DebugEventDisplay
- timeout: float
+ color: [float, float, float],
+ display: DebugEventDisplay,
+ timeout: float,
}
export type DebugEventDisplay =
- { ty: "path", points: Vec2[] }
- | { ty: "label", pos: Vec2, text: string }
+ { ty: "path", player?: PlayerID, points: Vec2[] }
+ | { ty: "label", pos: ItemLocation, text: string }
diff --git a/test-client/quick_commands.ts b/test-client/quick_commands.ts
new file mode 100644
index 00000000..1051ad20
--- /dev/null
+++ b/test-client/quick_commands.ts
@@ -0,0 +1,44 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { System } from "./system.ts";
+import { player_controller, send } from "./main.ts";
+
+const QUICK_COMMANDS: { [key: string]: string } = {
+ "Numpad1": "/start junior",
+ "Numpad2": "/start -b simple -b dishwasher -b waiter",
+ "Numpad3": "/start sophomore",
+ "Numpad4": "/start debug",
+ "Numpad5": "/start 5star",
+ "Numpad6": "/start campaign/lobby",
+ "Numpad8": "/start-tutorial plate:seared-patty,sliced-bun",
+ "Numpad9": "/start-tutorial plate:sliced-bun",
+ "Numpad7": "/end-tutorial",
+ "Numpad0": "/end",
+ "NumpadEnter": "/r",
+}
+
+export class SQuickCommands extends System {
+ override input(ev: KeyboardEvent, down: boolean): boolean {
+ if (down && ev.code in QUICK_COMMANDS) {
+ send({ player: player_controller.my_id!, type: "communicate", message: { text: QUICK_COMMANDS[ev.code] } })
+ return true
+ }
+ return false
+ }
+} \ No newline at end of file
diff --git a/test-client/score_display.ts b/test-client/score_display.ts
new file mode 100644
index 00000000..3f5b3a6a
--- /dev/null
+++ b/test-client/score_display.ts
@@ -0,0 +1,48 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { tr } from "./locale.ts";
+import { PacketC, Score } from "./protocol.ts";
+import { System } from "./system.ts";
+
+export class SScoreDisplay extends System {
+ score: Score = { active_recipes: 0, demands_completed: 0, demands_failed: 0, instant_recipes: 0, passive_recipes: 0, players: 0, points: 0, stars: 0, time_remaining: 0 }
+ override packet(p: PacketC): void {
+ if (p.type != "score") return
+ this.score.demands_completed = p.demands_completed
+ this.score.demands_failed = p.demands_failed
+ this.score.points = p.points
+ this.score.time_remaining = p.time_remaining ?? null
+ }
+ override tick(dt: number): void {
+ this.score.time_remaining -= dt
+ this.score.time_remaining = Math.max(this.score.time_remaining, 0)
+ }
+ override draw(ctx: CanvasRenderingContext2D): void {
+ ctx.fillStyle = "white"
+ ctx.textAlign = "left"
+ ctx.textBaseline = "bottom"
+ ctx.font = "20px sans-serif"
+ ctx.fillText(`${tr("c.score.time_remaining")}: ${this.score.time_remaining?.toFixed(2)}`, 10, ctx.canvas.height - 90)
+ ctx.font = "30px sans-serif"
+ ctx.fillText(`${tr("c.score.points")}: ${this.score.points}`, 10, ctx.canvas.height - 60)
+ ctx.font = "20px sans-serif"
+ ctx.fillText(`${tr("c.score.demands_completed")}: ${this.score.demands_completed}`, 10, ctx.canvas.height - 30)
+ ctx.fillText(`${tr("c.score.demands_failed")}: ${this.score.demands_failed}`, 10, ctx.canvas.height - 10)
+ }
+}
diff --git a/test-client/server_message.ts b/test-client/server_message.ts
new file mode 100644
index 00000000..2396a3ba
--- /dev/null
+++ b/test-client/server_message.ts
@@ -0,0 +1,57 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2026 Hurry Curry! Contributors
+
+ 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/>.
+
+*/
+
+import { MessageRenderer } from "./map/message.ts";
+import { PacketC } from "./protocol.ts";
+import { System } from "./system.ts";
+
+export class SServerMessage extends System {
+ inner?: MessageRenderer
+ override packet(p: PacketC): void {
+ if (p.type == "server_message") {
+ this.inner = new MessageRenderer(
+ p.error ? "error" : "normal",
+ p.message,
+ { x: 0, y: 0 },
+ 0.,
+ { initial: 5, remaining: 5, pinned: false }
+ )
+ } else if (p.type == "menu" && p.menu == "score") {
+ this.inner = new MessageRenderer(
+ "normal",
+ { text: `Score: ${JSON.stringify(p.data, null, 4)}` },
+ { x: 0, y: 0 },
+ 0,
+ { initial: 5, remaining: 5, pinned: false },
+ )
+ }
+ }
+
+ override tick(dt: number): void {
+ if (this.inner && this.inner.tick(dt)) delete this.inner
+ }
+
+ override draw(ctx: CanvasRenderingContext2D): void {
+ if (!this.inner) return
+ ctx.save()
+ ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 6)
+ ctx.scale(2, 2)
+ this.inner.draw(ctx, undefined, false)
+ ctx.restore()
+ }
+}
diff --git a/test-client/sprites.ts b/test-client/sprites.ts
deleted file mode 100644
index c7cd769d..00000000
--- a/test-client/sprites.ts
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
-
- 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/>.
-
-*/
-
-type Component = (ctx: CanvasRenderingContext2D) => void
-function base(fill: string, stroke?: string, stroke_width?: number): Component {
- return c => {
- c.fillStyle = fill;
- c.strokeStyle = stroke ?? "black";
- c.lineWidth = stroke_width ?? 0.05
- c.lineJoin = "miter"
- c.lineCap = "square"
- c.fillRect(
- -0.5,
- -0.5,
- 1,
- 1
- )
- if (stroke) c.strokeRect(
- -0.5 + c.lineWidth / 2,
- -0.5 + c.lineWidth / 2,
- 1 - c.lineWidth,
- 1 - c.lineWidth
- )
- }
-}
-function rect(inset: number, fill: string, stroke?: string, stroke_width?: number): Component {
- return c => {
- c.fillStyle = fill;
- c.strokeStyle = stroke ?? "black";
- c.lineWidth = stroke_width ?? 0.05
- c.lineJoin = "round"
- c.lineCap = "round"
- c.fillRect(
- -0.5 + inset,
- -0.5 + inset,
- 1 - inset * 2,
- 1 - inset * 2
- )
- if (stroke) c.strokeRect(
- -0.5 + inset,
- -0.5 + inset,
- 1 - inset * 2,
- 1 - inset * 2
- )
- }
-}
-function circle(radius: number, fill: string, stroke?: string, stroke_width?: number): Component {
- return c => {
- c.fillStyle = fill;
- c.strokeStyle = stroke ?? "black";
- c.lineWidth = stroke_width ?? 0.05
- c.beginPath()
- c.arc(0.0, 0.0, radius, 0, Math.PI * 2)
- if (stroke) c.stroke()
- c.fill()
- }
-}
-function cross(size: number, stroke: string, stroke_width = 0.05): Component {
- return c => {
- c.strokeStyle = stroke
- c.lineWidth = stroke_width
- c.lineCap = "round"
- c.beginPath()
- c.moveTo(-size, -size)
- c.lineTo(size, size)
- c.moveTo(size, -size)
- c.lineTo(-size, size)
- c.stroke()
- }
-}
-function checkmark(size: number, stroke: string, stroke_width = 0.05): Component {
- return c => {
- c.strokeStyle = stroke
- c.lineWidth = stroke_width
- c.lineCap = "round"
- c.beginPath()
- c.moveTo(-size, 0)
- c.lineTo(-size * 0.3, size)
- c.lineTo(size, -size)
- c.stroke()
- }
-}
-function text(s: string): Component {
- return c => {
- c.font = "0.8px sans-serif"
- c.strokeStyle = "#e38242"
- c.fillStyle = "white"
- c.lineWidth = 0.05
- c.textAlign = "center"
- c.textBaseline = "middle"
- c.lineJoin = "round"
- c.lineCap = "round"
- c.strokeText(s, 0, 0)
- c.fillText(s, 0, 0)
- }
-}
-
-function arrange_items(...items: ItemName[]): Component {
- return c => {
- for (let index = 0; index < items.length; index++) {
- const item = items[index];
-
- const t = index / items.length * Math.PI * 2.
- const radius = items.length == 1 ? 0 : (0.4 / items.length)
- const off_x = Math.sin(t) * radius
- const off_y = Math.cos(t) * radius
- const scale = 1. / Math.sqrt(items.length)
-
- c.save()
- c.translate(off_x, off_y)
- c.scale(scale, scale)
- iref(item)(c)
- c.restore()
- }
- }
-}
-
-const door: Component = c => {
- c.fillStyle = "#ff9843"
- c.fillRect(-0.5, -0.1, 1, 0.2)
-}
-
-const iref = (name: ItemName): Component => c => draw_item_sprite(c, name)
-const tref = (name: ItemName): Component => c => draw_tile_sprite(c, name)
-
-export type ItemName = string
-export type TileName = string
-
-const ITEMS: { [key in ItemName]: (c: string[]) => Component } = {
- "bun": () => circle(0.3, "#853e20"),
- "burned": () => c => (circle(0.3, "rgb(61, 18, 0)")(c), cross(0.2, "red", 0.02)(c)),
- "coconut": () => circle(0.3, "rgb(75, 49, 25)"),
- "cooked-rice": () => circle(0.3, "rgb(233, 233, 233)"),
- "curry": () => circle(0.3, "rgb(185, 67, 37)"),
- "dough": () => circle(0.3, "#b38d7d"),
- "fish": () => circle(0.3, "rgb(62, 66, 104)"),
- "flour": () => circle(0.3, "#d8c9c2"),
- "leek": () => circle(0.3, "rgb(50, 133, 17)"),
- "milk": () => circle(0.3, "rgb(252, 243, 208)"),
- "nigiri": () => circle(0.25, "rgb(233, 233, 233)", "salmon"),
- "rice": () => circle(0.3, "rgb(163, 163, 163)"),
- "seared-steak": () => circle(0.3, "#702200"),
- "sliced-bun": () => circle(0.3, "#853e20"),
- "sliced-fish": () => circle(0.3, "salmon", "rgb(62, 66, 104)"),
- "steak": () => circle(0.3, "#ca3510"),
- "patty": () => circle(0.3, "#c26149"),
- "seared-patty": () => circle(0.3, "#502c23"),
- "strawberry-icecream": () => circle(0.2, "rgb(250, 148, 236)"),
- "strawberry-mochi": () => circle(0.2, "rgb(161, 111, 132)"),
- "strawberry-shake": () => circle(0.3, "rgb(255, 180, 180)"),
- "strawberry": () => circle(0.3, "rgb(228, 79, 111)"),
- "tomato-juice": () => circle(0.3, "#b80000"),
- "tomato-soup": () => circle(0.3, "#ff2600"),
- "water": () => circle(0.3, "rgb(86, 92, 206)"),
- "tomato": () => circle(0.3, "#d63838"),
- "sliced-tomato": () => circle(0.3, "#d16363", "#d63838", 0.08),
- "lettuce": () => circle(0.3, "#64a30b"),
- "sliced-lettuce": () => circle(0.3, "#a0da4f", "#64a30b", 0.08),
- "cheese": () => circle(0.3, "#b3b615"),
- "sliced-cheese": () => rect(0.25, "#dcdf29", "#b3b615", 0.05),
- "dirty-plate": () => circle(0.4, "#947a6f", "#d3a187", 0.02),
- "mochi-dough": () => circle(0.3, "rgb(172, 162, 151)"),
- "rice-flour": () => iref("rice"),
- "unknown-order": () => text("!"),
-
- "pan": i => c => (circle(0.35, "rgb(29, 29, 29)", "rgb(39, 39, 39)", 0.04)(c), arrange_items(...i)(c)),
- "pot": i => c => (circle(0.27, "rgb(29, 29, 29)", "rgb(56, 56, 56)", 0.2)(c), arrange_items(...i)(c)),
- "foodprocessor": i => c => (circle(0.35, "rgb(86, 168, 189)", "rgb(88, 222, 255)", 0.04)(c), arrange_items(...i)(c)),
- "plate": i => c => (circle(0.4, "#b6b6b6", "#f7f7f7", 0.02)(c), arrange_items(...i)(c)),
- "glass": i => c => (circle(0.35, "rgb(150, 255, 237)", "rgb(52, 129, 155)", 0.02), arrange_items(...i)(c)),
-
-}
-
-const counter = tref("counter");
-const TILES: { [key in TileName]: (param: string) => Component } = {
- "floor": () => base("#333", "#222", 0.05),
- "street": () => base("rgb(19, 19, 19)"),
- "table": () => base("rgb(133, 76, 38)"),
- "door": () => door,
- "chair": () => circle(0.45, "rgb(136, 83, 41)"),
- "wall": () => base("rgb(0, 14, 56)"),
- "wall-window": () => base("rgb(19, 40, 102)"),
- "counter": () => base("rgb(182, 172, 164)"),
- "counter-window": () => base("rgb(233, 233, 233)"),
- "grass": () => base("rgb(0, 107, 4)"),
- "path": () => base("rgb(100, 80, 55)"),
- "conveyor": () => base("rgb(107, 62, 128)"),
- "tree": () => base("rgb(1, 82, 4)"),
- "cutting-board": () => rect(0.3, "#9eec44ff", "#9eec44ff", 0.2),
- "rolling-board": () => rect(0.3, "#ece644ff", "#ece644ff", 0.2),
- "trash": () => c => (circle(0.4, "rgb(20, 20, 20)")(c), cross(0.3, "rgb(90, 36, 36)")(c)),
- "sink": () => base("rgb(131, 129, 161)", "rgb(177, 174, 226)", 0.2),
- "oven": () => base("rgb(241, 97, 61)", "rgb(109, 84, 84)", 0.3),
- "freezer": () => base("rgb(61, 97, 241)", "rgb(84, 88, 109)", 0.3),
- "stove": () => c => (counter(c), circle(0.4, "#444", "#999")(c)),
- "book": () => c => (counter(c), rect(0.2, "rgb(88, 44, 7)")(c)),
- "lamp": () => rect(0.3, "rgb(255, 217, 127)", "rgb(32, 32, 32)", 0.1),
- "crate": name => c => (base("#60701e", "#b9da37", 0.05)(c), iref(name)(c)),
- "button-base": () => base("#272727", "#272727", 0.05),
- "button": name => BUTTON_ICONS[name],
- "map-selector": () => rect(0.2, "rgb(56, 99, 239)", "rgb(44, 79, 194)", 0.1),
- "screen": () => () => { }
-}
-const BUTTON_ICONS: { [key: string]: Component } = {
- "accept": c => (circle(0.35, "rgb(81, 255, 0)", "rgb(57, 179, 0)")(c), checkmark(0.2, "#fff", 0.05)(c)),
- "reject": c => (circle(0.35, "rgb(255, 0, 0)", "rgb(181, 0, 0)")(c), cross(0.2, "#fff", 0.05)(c)),
-}
-
-
-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()
-}
-
-// TODO performance
-export function draw_item_sprite(ctx: CanvasRenderingContext2D, name: ItemName) {
- const [base, cont] = name.split(":")
- if (ITEMS[base]) {
- ITEMS[base](cont?.split(",") ?? [])(ctx)
- } else {
- circle(0.4, "#f0f")(ctx)
- debug_label(ctx, name)
- }
-}
-export function draw_tile_sprite(ctx: CanvasRenderingContext2D, name: TileName) {
- if (!name) return
- const [kind, param] = name.split(":", 2)
- if (TILES[kind]) {
- TILES[kind](param)(ctx)
- } else {
- base("#f0f")(ctx)
- debug_label(ctx, name)
- }
-
-}
diff --git a/test-client/system.ts b/test-client/system.ts
index fed6db06..6434a9b4 100644
--- a/test-client/system.ts
+++ b/test-client/system.ts
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -22,4 +22,5 @@ export abstract class System {
tick(_dt: number) { }
draw(_ctx: CanvasRenderingContext2D) { }
packet(_p: PacketC) { }
+ input(_ev: KeyboardEvent, _down: boolean): boolean { return false }
}
diff --git a/test-client/util.ts b/test-client/util.ts
index e435b2c3..20118b16 100644
--- a/test-client/util.ts
+++ b/test-client/util.ts
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
diff --git a/test-client/visual.ts b/test-client/visual.ts
deleted file mode 100644
index 30ff970f..00000000
--- a/test-client/visual.ts
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
-
- 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/>.
-
-*/
-import { message_str, tr } from "./locale.ts";
-import { ItemData, MessageData, MessageStyle, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, debug_events, get_interact_target, global_message, interact_active_anim, interact_possible_anim, interact_target_anim, is_lobby, items_removed, keys_down, my_id, overlay_vis_anim, players, score, server_hints, tiles, vote } from "./main.ts";
-import { PLAYER_SIZE } from "./movement.ts";
-import { draw_item_sprite, draw_tile_sprite, ItemName, TileName } from "./sprites.ts";
-import { V2, ceil_v2, floor_v2 } from "./util.ts";
-import { PlayerClass } from "./protocol.ts";
-import { draw_particles, particle_count } from "./particles.ts";
-
-export function draw_wait(text: string) {
- ctx.fillStyle = "#444"
- ctx.fillRect(0, 0, canvas.width, canvas.height)
- ctx.fillStyle = "#555"
- ctx.font = "50px sans-serif"
- ctx.strokeStyle = "black"
- ctx.fillStyle = "white"
- ctx.lineWidth = 10
- ctx.textAlign = "center"
- ctx.textBaseline = "middle"
- ctx.lineJoin = "round"
- ctx.lineCap = "round"
- ctx.strokeText(text, canvas.width / 2, canvas.height / 2)
- ctx.fillText(text, canvas.width / 2, canvas.height / 2)
-}
-
-export function draw_ingame() {
- ctx.fillStyle = "#111"
- ctx.fillRect(0, 0, canvas.width, canvas.height)
-
- ctx.save()
- ctx.translate(canvas.width / 2, canvas.height / 2)
- ctx.scale(camera_scale, camera_scale)
- ctx.translate(-camera.x, -camera.y)
-
- draw_grid()
-
- for (const [_, tile] of tiles)
- draw_tile(tile)
-
- for (const [_, player] of players)
- draw_player(player)
-
- for (const item of items_removed)
- draw_item(item)
-
- for (const [_, tile] of tiles)
- if (tile.item) draw_item(tile.item)
-
- draw_particles()
-
- draw_interact_target()
-
- for (const [_, player] of players)
- if (player.message) draw_message(player.message, true)
-
- for (const [_, player] of players)
- if (player.message_persist) draw_message(player.message_persist, true)
-
- for (const [_, player] of players)
- draw_player_nametag(player)
-
- for (const [_, message] of server_hints)
- draw_message(message, true)
-
- draw_debug_events()
-
- ctx.restore()
- ctx.save()
- ctx.translate(50, 50)
- ctx.scale(80, 80)
-
- for (const [_, player] of players)
- if (player.message_pinned) draw_message(player.message_pinned, false)
-
- ctx.restore()
-
- draw_global_message()
-
- if (!is_lobby)
- draw_score()
-
- vote.draw(ctx)
-
- if (keys_down.has("KeyP"))
- draw_info_overlay()
-}
-
-function draw_debug_events() {
- for (const ev of debug_events.values()) {
- const color = `rgb(${ev.color[0] * 100}%,${ev.color[1] * 100}%,${ev.color[2] * 100}%)`
- if (ev.display.ty == "path") {
- ctx.lineWidth = 0.1
- ctx.lineCap = "round"
- ctx.strokeStyle = color
- ctx.beginPath()
- if (ev.display.points.length)
- ctx.moveTo(...ev.display.points[0])
- for (let i = 1; i < ev.display.points.length; i++)
- ctx.lineTo(...ev.display.points[i])
- ctx.stroke()
- } else if (ev.display.ty == "label") {
- ctx.font = "0.2px sans-serif"
- ctx.strokeStyle = "white"
- ctx.fillStyle = color
- ctx.lineWidth = 0.05
- ctx.textAlign = "center"
- ctx.textBaseline = "middle"
- ctx.lineJoin = "round"
- ctx.lineCap = "round"
- ctx.strokeText(ev.display.text, ev.display.pos[0], ev.display.pos[1])
- ctx.fillText(ev.display.text, ev.display.pos[0], ev.display.pos[1])
- }
- }
-}
-
-function draw_score() {
- ctx.fillStyle = "white"
- ctx.textAlign = "left"
- ctx.textBaseline = "bottom"
- ctx.font = "20px sans-serif"
- ctx.fillText(`${tr("c.score.time_remaining")}: ${score.time_remaining?.toFixed(2)}`, 10, canvas.height - 90)
- ctx.font = "30px sans-serif"
- ctx.fillText(`${tr("c.score.points")}: ${score.points}`, 10, canvas.height - 60)
- ctx.font = "20px sans-serif"
- ctx.fillText(`${tr("c.score.demands_completed")}: ${score.demands_completed}`, 10, canvas.height - 30)
- ctx.fillText(`${tr("c.score.demands_failed")}: ${score.demands_failed}`, 10, canvas.height - 10)
-}
-
-function draw_info_overlay() {
- ctx.fillStyle = "white"
- ctx.textAlign = "left"
- ctx.textBaseline = "bottom"
- ctx.font = "20px sans-serif"
- ctx.fillText(`position = ${JSON.stringify(players.get(my_id)?.anim_position)}`, 10, 30)
- ctx.fillText(`velocity = ${JSON.stringify(players.get(my_id)?.velocity)}`, 10, 50)
- ctx.fillText(`interact = ${JSON.stringify(get_interact_target())}`, 10, 70)
- ctx.fillText(`particle_count = ${particle_count()}`, 10, 90)
-}
-
-function draw_tile(tile: TileData) {
- ctx.save()
- ctx.translate(tile.x + 0.5, tile.y + 0.5)
- for (const p of tile.parts)
- draw_tile_sprite(ctx, data.tile_names[p] as TileName)
- ctx.restore()
-}
-
-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)
- draw_item_sprite(ctx, data.item_names[item.kind] as ItemName)
- if (item.active) {
- ctx.fillStyle = item.active.warn ? "rgba(230, 58, 58, 0.66)" : "rgba(115, 230, 58, 0.66)"
- ctx.fillRect(-0.5, -0.5, 1, item.active.position)
- }
- ctx.restore()
-}
-
-function draw_player(player: PlayerData) {
- ctx.save()
- ctx.translate(player.anim_position.x, player.anim_position.y)
- ctx.rotate(-player.rotation)
- if (player.boosting) ctx.scale(1.3, 1.3)
- draw_character(player.class, player.character)
- // // show moving players
- // if (length(player.input_direction) > 0.1) {
- // ctx.fillStyle = "white"
- // ctx.fillRect(-0.1, -0.1, 0.2, 0.2)
- // }
- ctx.restore()
- ctx.save()
- for (const h of player.hands) {
- if (h.item) draw_item(h.item)
- ctx.translate(0.2, 0.0)
- }
- ctx.restore()
-}
-
-function draw_player_nametag(player: PlayerData) {
- if (overlay_vis_anim > 0.01) {
- ctx.save()
- ctx.translate(player.anim_position.x, player.anim_position.y)
- ctx.translate(0, -1)
- ctx.textAlign = "center"
- ctx.font = "15px sans-serif"
- ctx.scale(overlay_vis_anim / camera_scale, overlay_vis_anim / camera_scale)
- const w = ctx.measureText(player.name).width + 20
- ctx.fillStyle = "#fffa"
- ctx.beginPath()
- ctx.roundRect(-w / 2, -10, w, 20, 5)
- ctx.fill()
- ctx.fillStyle = "black"
- ctx.textBaseline = "middle"
- ctx.fillText(player.name, 0, 0)
- ctx.restore()
- }
-}
-
-function draw_interact_target() {
- ctx.save()
- ctx.translate(interact_target_anim.x, interact_target_anim.y)
-
- ctx.lineCap = "round"
- ctx.lineJoin = "round"
- ctx.lineWidth = 0.06 + 0.03 * Math.sin(Date.now() / 100) * interact_possible_anim
- ctx.strokeStyle = `hsla(${(1 - interact_active_anim) * 225 + interact_active_anim * 125}deg, ${interact_possible_anim * 100}%, 62.70%, ${interact_possible_anim * 0.7 + 0.3})`
- ctx.strokeRect(0, 0, 1, 1)
-
- ctx.restore()
-}
-
-function draw_grid() {
- ctx.strokeStyle = "#333"
- ctx.lineWidth = 0.01
- ctx.beginPath()
- const min = floor_v2(map_screen_to_world({ x: 0, y: 0 }))
- const max = ceil_v2(map_screen_to_world({ x: canvas.width, y: canvas.height }))
- for (let x = min.x; x < max.x; x++) {
- ctx.moveTo(x, min.y)
- ctx.lineTo(x, max.y)
- }
- for (let y = min.y; y < max.y; y++) {
- ctx.moveTo(min.x, y)
- ctx.lineTo(max.x, y)
- }
- ctx.stroke()
-}
-
-function draw_character(pclass: PlayerClass, character: number) {
- ctx.fillStyle = `hsl(${character}rad, 50%, 50%)`
- ctx.beginPath()
- ctx.arc(0, 0, PLAYER_SIZE, 0, Math.PI * 2)
- ctx.fill()
-
- if (pclass != "customer") {
- ctx.fillStyle = `hsl(${character}rad, 80%, 10%)`
- ctx.beginPath()
- ctx.arc(0, -0.2, PLAYER_SIZE, 0, Math.PI * 2)
- ctx.fill()
- }
- if (pclass != "bot") {
- ctx.fillStyle = `hsl(${character}rad, 80%, 70%)`
- ctx.beginPath()
- ctx.moveTo(-0.04, 0.25)
- ctx.lineTo(0.04, 0.25)
- ctx.lineTo(0, 0.4)
- }
- ctx.fill()
-}
-
-const MESSAGE_BG: { [key in MessageStyle]: string } = { normal: "#fff", hint: "#111", error: "#fff", pinned: "rgb(4, 32, 0)" }
-const MESSAGE_FG: { [key in MessageStyle]: string } = { normal: "#000", hint: "#fff", error: "#a00", pinned: "#000" }
-function draw_message(m: MessageData, world: boolean) {
- ctx.save()
- ctx.translate(m.anim_position.x, m.anim_position.y)
- const scale = Math.min(m.anim_size, 1 - overlay_vis_anim);
- ctx.scale(scale, scale)
- if ("item" in m.inner) {
- ctx.fillStyle = MESSAGE_BG[m.style]
- if (m.style == "pinned") {
- ctx.translate(0, 1)
- ctx.beginPath()
- ctx.arc(0, -1, 0.5, 0, Math.PI * 2)
- ctx.fill()
- } else {
- ctx.beginPath()
- ctx.moveTo(0, -0.3)
- ctx.arc(0, -1, 0.5, Math.PI / 4, Math.PI - Math.PI / 4, true)
- ctx.closePath()
- ctx.fill()
- }
-
- if (m.timeout) {
- const t = m.timeout.remaining / m.timeout.initial;
- ctx.beginPath()
- ctx.strokeStyle = `hsl(${Math.sqrt(t) * 0.3}turn, 100%, 50%)`
- ctx.lineWidth = 0.1
- ctx.arc(0, -1, 0.45, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * (1 - t))
- ctx.stroke()
- }
-
- ctx.translate(0, -1)
- draw_item_sprite(ctx, data.item_names[m.inner.item] as ItemName)
- ctx.translate(0, 1)
- }
- if ("text" in m.inner || "translation" in m.inner) {
- ctx.translate(0, -1)
-
- ctx.textAlign = "center"
- ctx.font = "15px " + (world ? "sans-serif" : "monospace")
- if (world) ctx.scale(2 / camera_scale, 2 / camera_scale)
-
- const lines = message_str(m.inner).split("\n")
- const w = lines.reduce((a, v) => Math.max(a, ctx.measureText(v).width), 0) + 10
-
- if (world) ctx.translate(0, -lines.length * 15 / 2)
-
- ctx.fillStyle = MESSAGE_BG[m.style]
- ctx.beginPath()
- ctx.roundRect(-w / 2, -5, w, lines.length * 15 + 10, 5)
- ctx.fill()
-
- ctx.fillStyle = MESSAGE_FG[m.style]
- ctx.textAlign = "left"
- ctx.textBaseline = "top"
- for (let i = 0; i < lines.length; i++)
- ctx.fillText(lines[i], -w / 2 + 5, i * 15)
-
- ctx.translate(0, 1)
- }
- ctx.restore()
-}
-
-function draw_global_message() {
- if (!global_message) return
- ctx.save()
- ctx.translate(canvas.width / 2, canvas.height / 6)
- ctx.scale(2, 2)
- draw_message(global_message, false)
- ctx.restore()
-}
-
-function map_screen_to_world(screen: V2): V2 {
- return {
- x: ((screen.x - canvas.width / 2) / camera_scale) + camera.x,
- y: ((screen.y - canvas.height / 2) / camera_scale) + camera.y,
- }
-}
-
diff --git a/test-client/vote.ts b/test-client/vote.ts
index c4081180..b5a58d91 100644
--- a/test-client/vote.ts
+++ b/test-client/vote.ts
@@ -1,6 +1,6 @@
/*
Hurry Curry! - a game about cooking
- Copyright (C) 2025 Hurry Curry! Contributors
+ Copyright (C) 2026 Hurry Curry! Contributors
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
@@ -17,10 +17,14 @@
*/
import { message_str } from "./locale.ts";
+import { player_controller, send } from "./main.ts";
import { float, int, Message, PacketC, PlayerID, VoteSubject } from "./protocol.ts";
import { System } from "./system.ts";
import { lerp_exp } from "./util.ts";
+const KEY_VOTE_AGREE = "Digit1"
+const KEY_VOTE_REJECT = "Digit2"
+
interface ActiveVote {
total: int,
agree: int,
@@ -48,6 +52,12 @@ export class SVote extends System {
a.timeout = Math.max(a.timeout, 0)
if (a.anim_box > 1) delete this.active
}
+ override input(ev: KeyboardEvent, down: boolean): boolean {
+ if (down && ev.code == KEY_VOTE_AGREE) send({ type: "cast_vote", player: player_controller.my_id!, agree: true })
+ else if (down && ev.code == KEY_VOTE_REJECT) send({ type: "cast_vote", player: player_controller.my_id!, agree: false })
+ else return false
+ return true
+ }
override draw(ctx: CanvasRenderingContext2D) {
if (!this.active) return
const a = this.active