diff options
434 files changed, 5540 insertions, 3409 deletions
@@ -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 @@ -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 Binary files differindex 866ba92c..866ba92c 100644 --- a/client/player/particles/angry/angry.webp +++ b/client/map/effects/angry/angry.webp 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 Binary files differindex c1d2e8ff..c1d2e8ff 100644 --- a/client/player/particles/satisfied/star.webp +++ b/client/map/effects/satisfied/star.webp 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} å玩家) @@ -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, ®)?; + let (mut recipes, mut demands, mut recipe_groups) = load_recipes(recipes_in, ®)?; 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 |