aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--client/game.gd33
-rw-r--r--client/global.gd6
-rw-r--r--client/menu/communicate/popup_message/popup_message.gd2
-rw-r--r--client/menu/settings/input/input_value_node.gd1
-rw-r--r--client/multiplayer.gd8
-rw-r--r--client/player/controllable_player.gd35
-rw-r--r--client/player/onscreen_controls/controls.gd4
-rw-r--r--client/player/player.gd74
-rw-r--r--client/project.godot8
-rw-r--r--data/maps/village.yaml1
-rw-r--r--locale/en.ini1
-rw-r--r--pixel-client/src/game.rs72
-rw-r--r--server/bot/src/algos/customer.rs14
-rw-r--r--server/bot/src/algos/simple.rs4
-rw-r--r--server/bot/src/main.rs3
-rw-r--r--server/client-lib/src/lib.rs62
-rw-r--r--server/protocol/Cargo.toml2
-rw-r--r--server/protocol/src/helpers.rs11
-rw-r--r--server/protocol/src/lib.rs9
-rw-r--r--server/src/data/mod.rs2
-rw-r--r--server/src/entity/bot.rs3
-rw-r--r--server/src/entity/tutorial.rs4
-rw-r--r--server/src/server.rs142
24 files changed, 319 insertions, 184 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 967b9358..b3d1ec3e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1194,7 +1194,7 @@ dependencies = [
[[package]]
name = "hurrycurry-protocol"
-version = "7.5.0"
+version = "8.0.0"
dependencies = [
"bincode",
"glam",
diff --git a/client/game.gd b/client/game.gd
index e7d39aa2..6c152d8a 100644
--- a/client/game.gd
+++ b/client/game.gd
@@ -53,6 +53,7 @@ var tile_collide: Array = []
var tile_interact: Array = []
var maps: Array = []
var bot_algos: Array
+var hand_count = 0
var text_message_history: Array[TextMessage] = []
var join_state: JoinState = JoinState.SPECTATING
@@ -95,6 +96,7 @@ func handle_packet(p):
tile_interact = p["data"]["tile_interact"]
maps = p["data"]["maps"]
bot_algos = p["data"]["bot_algos"]
+ hand_count = p["data"]["hand_count"]
tile_index_by_name.clear()
for id in tile_names.size():
@@ -128,8 +130,9 @@ func handle_packet(p):
if p.id == player_id:
set_join_state(JoinState.SPECTATING)
camera.target = $Center
- if player.hand != null:
- player.hand.queue_free()
+ for h in player.hand:
+ if h != null:
+ h.queue_free()
players.erase(p.id)
player.queue_free()
update_players.emit(players)
@@ -143,13 +146,13 @@ func handle_packet(p):
player_instance.position_ = last_position
"move_item":
if "player" in p.from and "player" in p.to:
- players[p.from.player].pass_to(players[p.to.player])
+ players[p.from.player[0]].pass_to(players[p.to.player[0]], int(p.from.player[1]), int(p.to.player[1]))
elif "tile" in p.from and "player" in p.to:
var t: Tile = map.get_tile_instance(p.from.tile)
- players[p.to.player].take_item(t)
+ players[p.to.player[0]].take_item(t, int(p.to.player[1]))
elif "player" in p.from and "tile" in p.to:
var t: Tile = map.get_tile_instance(p.to.tile)
- players[p.from.player].put_item(t)
+ players[p.from.player[0]].put_item(t, int(p.from.player[1]))
elif "tile" in p.from and "tile" in p.to:
var from_tile2: Tile = map.get_tile_instance(p.from.tile)
var to_tile2: Tile = map.get_tile_instance(p.to.tile)
@@ -159,13 +162,13 @@ func handle_packet(p):
var t: Tile = map.get_tile_instance(p.item.tile)
t.progress(p.position, p.speed, p.warn, players.get(p.player))
else:
- players[p.item.player].progress(p.position, p.speed, p.warn)
+ players[p.item.player[0]].progress(p.position, p.speed, p.warn, int(p.item.player[1]))
"clear_progress":
if "tile" in p.item:
var t: Tile = map.get_tile_instance(p.item.tile)
t.finish()
else:
- players[p.item.player].finish()
+ players[p.item.player[0]].finish(int(p.item.player[1]))
"set_item":
var location: Dictionary = p["location"]
if p.item != null:
@@ -177,21 +180,23 @@ func handle_packet(p):
i.name = item_names[p.item]
t.set_item(i)
else:
- var pl: Player = players[p.location.player]
- var i = ItemFactory.produce(item_names[p.item], pl.hand_base)
- i.position = pl.hand_base.global_position
+ var pl: Player = players[p.location.player[0]]
+ var h = p.location.player[1]
+ var i = ItemFactory.produce(item_names[p.item], pl.hand_base[h])
+ i.position = pl.hand_base[h].global_position
add_child(i)
i.name = item_names[p.item]
- pl.set_item(i)
+ pl.set_item(i, h)
else:
if "tile" in p.location:
var t: Tile = map.get_tile_instance(p.location.tile)
t.finish()
t.set_item(null)
else:
- var player: Player = players[p.location.player]
- player.finish()
- player.set_item(null)
+ var pl: Player = players[p.location.player[0]]
+ var h = p.location.player[1]
+ pl.finish(h)
+ pl.set_item(null, h)
"update_map":
var neighbors: Array = p["neighbors"]
if p.kind != null:
diff --git a/client/global.gd b/client/global.gd
index 374f0e44..93c73e13 100644
--- a/client/global.gd
+++ b/client/global.gd
@@ -268,3 +268,9 @@ func configure_viewport_aa(vp: Viewport, aa: String) -> void:
"ms4x":
vp.msaa_3d = Viewport.MSAA_4X
vp.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED
+
+static func index_to_hand(i):
+ match i:
+ 0: return "left"
+ 1: return "right"
+ _: return "unknown"
diff --git a/client/menu/communicate/popup_message/popup_message.gd b/client/menu/communicate/popup_message/popup_message.gd
index 98bd94e3..56c8961a 100644
--- a/client/menu/communicate/popup_message/popup_message.gd
+++ b/client/menu/communicate/popup_message/popup_message.gd
@@ -141,7 +141,7 @@ func _input(_event):
Global.set_hint("has_rotated", true)
if any_action_just_pressed(["zoom_in", "zoom_out"]):
Global.set_hint("has_zoomed", true)
- if Input.is_action_just_pressed("interact"):
+ if Input.is_action_just_pressed("interact_left") or Input.is_action_just_pressed("interact_right"):
Global.set_hint("has_interacted", true)
if Input.is_action_just_pressed("reset"):
Global.set_hint("has_reset", true)
diff --git a/client/menu/settings/input/input_value_node.gd b/client/menu/settings/input/input_value_node.gd
index 44a65ec5..9cf8327a 100644
--- a/client/menu/settings/input/input_value_node.gd
+++ b/client/menu/settings/input/input_value_node.gd
@@ -72,5 +72,4 @@ func events_equal(e1: InputEvent, e2: InputEvent) -> bool:
func _on_add_pressed() -> void:
listening = not listening
-
add_button.text = tr("c.settings.input.press_any_key") if listening else add_text
diff --git a/client/multiplayer.gd b/client/multiplayer.gd
index 57031221..5a9406ab 100644
--- a/client/multiplayer.gd
+++ b/client/multiplayer.gd
@@ -21,8 +21,8 @@ extends Node
signal packet(packet: Dictionary)
signal connection_closed()
-static var VERSION_MAJOR: int = 7
-static var VERSION_MINOR: int = 5
+static var VERSION_MAJOR: int = 8
+static var VERSION_MINOR: int = 0
var connected := false
var socket := WebSocketPeer.new()
@@ -66,6 +66,7 @@ func fix_packet_types(val):
if typeof(val[k]) == TYPE_ARRAY and val[k].size() == 2 and typeof(val[k][0]) == TYPE_FLOAT and typeof(val[k][1]) == TYPE_FLOAT:
if k in ["tile"]: newval[k] = Vector2i(val[k][0], val[k][1])
elif k in ["pos", "position"]: newval[k] = Vector2(val[k][0], val[k][1])
+ else: newval[k] = val[k]
# TODO reenable when fixed
# elif k in ["player", "id"] and typeof(val[k]) == TYPE_FLOAT:
# newval[k] = int(val[k])
@@ -106,11 +107,12 @@ func send_movement(player, pos: Vector2, direction: Vector2, boost: bool):
"boost": boost
})
-func send_tile_interact(player, pos: Vector2i, edge: bool):
+func send_tile_interact(player, pos: Vector2i, edge: bool, hand: int):
@warning_ignore("incompatible_ternary")
send_packet({
"type": "interact",
"player": player,
+ "hand": hand,
"pos": [pos.x, pos.y] if edge else null,
})
diff --git a/client/player/controllable_player.gd b/client/player/controllable_player.gd
index 9db36ad4..99625762 100644
--- a/client/player/controllable_player.gd
+++ b/client/player/controllable_player.gd
@@ -83,7 +83,7 @@ func _process_movement(delta):
var boost = Input.is_action_pressed("boost") or (Global.get_setting("gameplay.latch_boost") and boosting)
- if Input.is_action_pressed("interact") or Input.is_action_just_released("interact"):
+ if Input.is_action_pressed("interact_left") or Input.is_action_just_released("interact_left") or Input.is_action_pressed("interact_right") or Input.is_action_just_released("interact_right"):
input *= 0
else:
target = Vector2i(
@@ -167,8 +167,8 @@ func aabb_point_distance(mi: Vector2, ma: Vector2, p: Vector2) -> float:
func update_position(_new_position: Vector2, _new_rotation: float, _new_boosting: bool):
pass
-func progress(position__: float, speed: float, warn: bool):
- super(position__, speed, warn)
+func progress(position__: float, speed: float, warn: bool, h):
+ super(position__, speed, warn, h)
if warn:
current_vibration_strength = position__
current_vibration_change = speed
@@ -190,14 +190,14 @@ func _on_vibration_timeout():
Input.vibrate_handheld(100, vibration_strength)
vibration_timer.start()
-func put_item(tile: Tile):
- super(tile)
+func put_item(tile: Tile, h: int):
+ super(tile, h)
if Global.get_setting("gameplay.vibration"):
Input.start_joy_vibration(0, 0.1, 0.0, 0.075)
Input.vibrate_handheld(75, 0.1)
-func take_item(tile: Tile):
- super(tile)
+func take_item(tile: Tile, h: int):
+ super(tile, h)
if Global.get_setting("gameplay.vibration"):
Input.start_joy_vibration(0, 0.1, 0.0, 0.075)
Input.vibrate_handheld(75, 0.1)
@@ -210,19 +210,20 @@ func interact():
# clear last interaction if target has moved since
if last_interaction != null and not last_interaction == target:
- game.mp.send_tile_interact(game.player_id, last_interaction, false)
+ game.mp.send_tile_interact(game.player_id, last_interaction, false, 0)
marker.set_interacting(false)
last_interaction = null
marker.set_interactive(game.get_tile_interactive(target))
marker_target = tile.item_base.global_position
- if Input.is_action_just_pressed("interact") and last_interaction == null:
- last_interaction = target
- game.mp.send_tile_interact(game.player_id, target, true)
- tile.interact()
- marker.set_interacting(true)
- if Input.is_action_just_released("interact"):
- last_interaction = null
- game.mp.send_tile_interact(game.player_id, target, false)
- marker.set_interacting(false)
+ for h in [0, 1]:
+ if Input.is_action_just_pressed("interact_"+G.index_to_hand(h)) and last_interaction == null:
+ last_interaction = target
+ game.mp.send_tile_interact(game.player_id, target, true, h)
+ tile.interact()
+ marker.set_interacting(true)
+ if Input.is_action_just_released("interact_"+G.index_to_hand(h)):
+ last_interaction = null
+ game.mp.send_tile_interact(game.player_id, target, false, h)
+ marker.set_interacting(false)
else:
marker.visible = false
diff --git a/client/player/onscreen_controls/controls.gd b/client/player/onscreen_controls/controls.gd
index 0d240ddb..06efb82e 100644
--- a/client/player/onscreen_controls/controls.gd
+++ b/client/player/onscreen_controls/controls.gd
@@ -42,11 +42,11 @@ func _on_boost_released():
boost.modulate = Color.WHITE
func _on_interact_pressed():
- Input.action_press("interact")
+ Input.action_press("interact_left")
interact.modulate = modulate_color
func _on_interact_released():
- Input.action_release("interact")
+ Input.action_release("interact_left")
interact.modulate = Color.WHITE
func _on_pause_pressed():
diff --git a/client/player/player.gd b/client/player/player.gd
index c314dad2..031cba29 100644
--- a/client/player/player.gd
+++ b/client/player/player.gd
@@ -41,16 +41,17 @@ var marker_target = Vector3(0, 0, 0)
var clear_timer: Timer = Timer.new()
-var hand: Item = null
-var hand_base: Node3D = Node3D.new()
+var hand = [null, null]
+var hand_base
var character_idx: int
var is_customer: bool
var current_item_message = null
var _anim_angle: float = 0.0
-var hand_base_position: Vector3 = DEFAULT_HAND_BASE_POSITION
-const DEFAULT_HAND_BASE_POSITION: Vector3 = Vector3(0, .425, .4)
+const DEFAULT_HAND_BASE_POSITION_CENTER: Vector3 = Vector3(0, .425, .4)
+const DEFAULT_HAND_BASE_POSITION_LEFT: Vector3 = Vector3(.3, .425, .4)
+const DEFAULT_HAND_BASE_POSITION_RIGHT: Vector3 = Vector3(-.3, .425, .4)
func _init(_id: int, new_name: String, pos: Vector2, new_character_idx: int, new_game: Game):
add_child(movement_base)
@@ -62,9 +63,22 @@ func _init(_id: int, new_name: String, pos: Vector2, new_character_idx: int, new
game = new_game
username = new_name
- hand_base.name = "HandBase"
- hand_base.position = hand_base_position
- movement_base.add_child(hand_base)
+ if game.hand_count == 1:
+ var center = Node3D.new()
+ center.name = "HandBaseCenter"
+ center.position = DEFAULT_HAND_BASE_POSITION_CENTER
+ hand_base = [center]
+ else:
+ var left = Node3D.new()
+ var right = Node3D.new()
+ left.name = "HandBaseLeft"
+ right.name = "HandBaseRight"
+ left.position = DEFAULT_HAND_BASE_POSITION_LEFT
+ right.position = DEFAULT_HAND_BASE_POSITION_RIGHT
+ hand_base = [left, right]
+
+ for h in hand_base:
+ movement_base.add_child(h)
movement_base.add_child(chat_bubble)
movement_base.add_child(item_bubble)
@@ -96,45 +110,47 @@ func update_username_tag(state):
tag.text = username
tag.visible = state
-func set_item(i: Item):
- if hand != null: hand.remove()
- if i != null:
- @warning_ignore("static_called_on_instance")
- hand_base_position = DEFAULT_HAND_BASE_POSITION - Vector3(0.,i.height() * 0.5, 0.)
+func set_item(i: Item, h: int):
+ if hand[h] != null: hand[h].remove()
+ # if i != null:
+ # @warning_ignore("static_called_on_instance")
+ # hand_base_position[h] = DEFAULT_HAND_BASE_POSITION_LEFT - Vector3(0.,i.height() * 0.5, 0.)
+ # @warning_ignore("static_called_on_instance")
+ # hand_base_position[1] = DEFAULT_HAND_BASE_POSITION_RIGHT - Vector3(0.,i.height() * 0.5, 0.)
character.holding = i != null
- hand = i
- if hand != null: hand.owned_by = hand_base
+ hand[h] = i
+ if hand[h] != null: hand[h].owned_by = hand_base[h]
-func remove_item():
- var i = hand
+func remove_item(h: int):
+ var i = hand[h]
if i == null: push_error("holding nothing")
- hand = null
+ hand[h] = null
character.holding = false
return i
-func progress(position__: float, speed: float, warn: bool):
- if hand != null: hand.progress(position__, speed, warn)
+func progress(position__: float, speed: float, warn: bool, h: int):
+ if hand[h] != null: hand[h].progress(position__, speed, warn)
-func finish():
- if hand != null: hand.finish()
+func finish(h: int):
+ if hand[h] != null: hand[h].finish()
-func take_item(tile: Tile):
- if hand != null: push_error("already holding an item")
+func take_item(tile: Tile, h: int):
+ if hand[h] != null: push_error("already holding an item")
var i = tile.take_item()
i.take()
- set_item(i)
+ set_item(i, h)
-func put_item(tile: Tile):
- var i = remove_item()
+func put_item(tile: Tile, h: int):
+ var i = remove_item(h)
i.put()
tile.put_item(i)
-func pass_to(player: Player):
- var i = remove_item()
+func pass_to(player: Player, hfrom: int, hto: int):
+ var i = remove_item(hfrom)
i.player_owned_timer = 0
if player.hand != null:
push_error("target is already holding an item")
- player.set_item(i)
+ player.set_item(i, hto)
func _process(delta):
_anim_angle = fmod(_anim_angle + delta, TAU)
diff --git a/client/project.godot b/client/project.godot
index a0b11050..2239120b 100644
--- a/client/project.godot
+++ b/client/project.godot
@@ -108,13 +108,19 @@ rotate_down={
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null)
]
}
-interact={
+interact_left={
"deadzone": 0.5,
"events": [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":32,"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)
]
}
+interact_right={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"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":76,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null)
+]
+}
boost={
"deadzone": 0.5,
"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":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
diff --git a/data/maps/village.yaml b/data/maps/village.yaml
index 9538ae9d..56cd49b0 100644
--- a/data/maps/village.yaml
+++ b/data/maps/village.yaml
@@ -17,6 +17,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
score_baseline: 200
+hand_count: 2
map:
- "''''''''''''''~_''''''''''''''"
- "''''''''''''''__''''''''''''''"
diff --git a/locale/en.ini b/locale/en.ini
index c99b8e31..041e79bc 100644
--- a/locale/en.ini
+++ b/locale/en.ini
@@ -223,6 +223,7 @@ s.error.must_be_alone=You must be alone in this server to reload
s.error.no_info=No information available.
s.error.no_player=Player does not exist.
s.error.no_tile=Tile does not exist.
+s.error.no_hand=Hand does not exist.
s.error.packet_not_supported=Packet not supported in this session.
s.error.packet_sender_invalid=Packet sent to a player that is not owned by this connection.
s.error.quoting_invalid=Command quoting invalid
diff --git a/pixel-client/src/game.rs b/pixel-client/src/game.rs
index e7754366..cbcc62d5 100644
--- a/pixel-client/src/game.rs
+++ b/pixel-client/src/game.rs
@@ -30,8 +30,8 @@ use hurrycurry_client_lib::{network::sync::Network, spatial_index::SpatialIndex,
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
movement::MovementBase,
- Gamedata, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PacketS, PlayerClass,
- PlayerID, RecipeIndex, Score, TileIndex,
+ Gamedata, Hand, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PacketS,
+ PlayerClass, PlayerID, RecipeIndex, Score, TileIndex,
};
use log::{info, warn};
use sdl2::{
@@ -67,7 +67,7 @@ pub struct Tile {
pub struct Player {
movement: MovementBase,
- item: Option<Item>,
+ items: [Option<Item>; 2],
message_persist: Option<(Message, MessageTimeout)>,
_name: String,
_character: i32,
@@ -156,11 +156,13 @@ impl Game {
self.network.queue_out.push_back(PacketS::Interact {
player: self.my_id,
pos: Some(self.players[&self.my_id].movement.get_interact_target()),
+ hand: Hand(0),
});
} else {
self.network.queue_out.push_back(PacketS::Interact {
player: self.my_id,
pos: None,
+ hand: Hand(0),
});
}
self.interacting = interact;
@@ -209,9 +211,11 @@ impl Game {
});
for player in self.players.values_mut() {
- if let Some(item) = &mut player.item {
- item.parent_position = player.movement.position;
- item.tick(1., dt);
+ for item in &mut player.items {
+ if let Some(item) = item {
+ item.parent_position = player.movement.position;
+ item.tick(1., dt);
+ }
}
}
for tile in self.tiles.values_mut() {
@@ -292,7 +296,7 @@ impl Game {
_character: character,
_name: name,
message_persist: None,
- item: None,
+ items: [const { None }; 2],
movement: MovementBase {
position,
input_direction: Vec2::ZERO,
@@ -327,17 +331,23 @@ impl Game {
}
}
PacketC::MoveItem { from, to } => {
- let mut item = self.get_item(from).take();
+ let mut item = self.get_item(from).unwrap().take();
if let Some(item) = &mut item {
item.parent_position = self.get_location_position(to);
}
- *self.get_item(to) = item;
+ *self.get_item(to).unwrap() = item;
}
PacketC::SetItem { location, item } => {
let position = self.get_location_position(location);
let slot = match location {
ItemLocation::Tile(pos) => &mut self.tiles.get_mut(&pos).unwrap().item,
- ItemLocation::Player(pid) => &mut self.players.get_mut(&pid).unwrap().item,
+ ItemLocation::Player(pid, hand) => self
+ .players
+ .get_mut(&pid)
+ .unwrap()
+ .items
+ .get_mut(hand.0)
+ .unwrap(),
};
self.items_removed.extend(slot.take());
*slot = item.map(|kind| Item {
@@ -348,7 +358,13 @@ impl Game {
active: None,
})
}
- PacketC::ClearProgress { item } => self.get_item(item).as_mut().unwrap().active = None,
+ PacketC::ClearProgress { item } => {
+ if let Some(slot) = self.get_item(item) {
+ if let Some(item) = slot {
+ item.active = None;
+ }
+ }
+ }
PacketC::SetProgress {
item,
position,
@@ -356,13 +372,17 @@ impl Game {
player,
warn,
} => {
- self.get_item(item).as_mut().unwrap().active = Some(Involvement {
- position,
- speed,
- player,
- warn,
- recipe: RecipeIndex(0),
- });
+ if let Some(slot) = self.get_item(item) {
+ if let Some(item) = slot {
+ item.active = Some(Involvement {
+ position,
+ speed,
+ player,
+ warn,
+ recipe: RecipeIndex(0),
+ });
+ }
+ }
}
PacketC::ServerMessage { .. } => {
// TODO
@@ -386,16 +406,18 @@ impl Game {
}
}
- pub fn get_item(&mut self, location: ItemLocation) -> &mut Option<Item> {
+ pub fn get_item(&mut self, location: ItemLocation) -> Option<&mut Option<Item>> {
match location {
- ItemLocation::Tile(pos) => &mut self.tiles.get_mut(&pos).unwrap().item,
- ItemLocation::Player(pid) => &mut self.players.get_mut(&pid).unwrap().item,
+ ItemLocation::Tile(pos) => Some(&mut self.tiles.get_mut(&pos)?.item),
+ ItemLocation::Player(pid, hand) => {
+ Some(self.players.get_mut(&pid)?.items.get_mut(hand.0)?)
+ }
}
}
pub fn get_location_position(&self, location: ItemLocation) -> Vec2 {
match location {
ItemLocation::Tile(pos) => pos.as_vec2() + 0.5,
- ItemLocation::Player(p) => self.players[&p].movement.position,
+ ItemLocation::Player(p, _) => self.players[&p].movement.position,
}
}
@@ -494,8 +516,10 @@ impl Player {
_ => (),
}
}
- if let Some(item) = &self.item {
- item.draw(ctx, item_sprites)
+ for item in &self.items {
+ if let Some(item) = item {
+ item.draw(ctx, item_sprites)
+ }
}
}
}
diff --git a/server/bot/src/algos/customer.rs b/server/bot/src/algos/customer.rs
index b243bd55..b0ece9dd 100644
--- a/server/bot/src/algos/customer.rs
+++ b/server/bot/src/algos/customer.rs
@@ -22,7 +22,7 @@ use crate::{
use hurrycurry_client_lib::Game;
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
- DemandIndex, Message, PacketS, PlayerClass, PlayerID, Score,
+ DemandIndex, Hand, Message, PacketS, PlayerClass, PlayerID, Score,
};
use log::info;
use rand::{random, seq::IndexedRandom, thread_rng};
@@ -313,6 +313,7 @@ impl CustomerState {
PacketS::Interact {
pos: Some(pos),
player: me,
+ hand: Hand(0),
},
PacketS::ApplyScore(Score {
demands_completed: 1,
@@ -322,6 +323,7 @@ impl CustomerState {
PacketS::Interact {
pos: None,
player: me,
+ hand: Hand(0),
},
],
..Default::default()
@@ -354,6 +356,7 @@ impl CustomerState {
extra: vec![PacketS::ReplaceHand {
player: me,
item: demand.output,
+ hand: Hand(0),
}],
..Default::default()
};
@@ -369,7 +372,12 @@ impl CustomerState {
cooldown,
} => {
*cooldown -= dt;
- if game.players.get(&me).is_some_and(|pl| pl.item.is_none()) {
+ if game
+ .players
+ .get(&me)
+ .is_some_and(|pl| pl.items[0].is_none())
+ // TODO index out of bounds?
+ {
if let Some(path) = find_path(&game.walkable, pos.as_ivec2(), *origin) {
*self = CustomerState::Exiting { path };
}
@@ -383,10 +391,12 @@ impl CustomerState {
PacketS::Interact {
player: me,
pos: Some(*table),
+ hand: Hand(0),
},
PacketS::Interact {
player: me,
pos: None,
+ hand: Hand(0),
},
],
direction,
diff --git a/server/bot/src/algos/simple.rs b/server/bot/src/algos/simple.rs
index 14eb38c4..452f59d3 100644
--- a/server/bot/src/algos/simple.rs
+++ b/server/bot/src/algos/simple.rs
@@ -109,13 +109,13 @@ impl<S> Context<'_, S> {
self.game
.players
.get(&self.me)
- .is_some_and(|p| p.item.as_ref().is_some_and(|i| i.kind == item))
+ .is_some_and(|p| p.items[0].as_ref().is_some_and(|i| i.kind == item))
}
pub fn is_hand_occupied(&self) -> bool {
self.game
.players
.get(&self.me)
- .map(|p| p.item.is_some())
+ .map(|p| p.items[0].is_some())
.unwrap_or(false)
}
pub fn find_demand(&self) -> Option<(ItemIndex, IVec2)> {
diff --git a/server/bot/src/main.rs b/server/bot/src/main.rs
index 61ae1c1c..918be7e1 100644
--- a/server/bot/src/main.rs
+++ b/server/bot/src/main.rs
@@ -19,7 +19,7 @@ use anyhow::Result;
use clap::Parser;
use hurrycurry_bot::{algos::ALGO_CONSTRUCTORS, BotAlgo, BotInput};
use hurrycurry_client_lib::{network::sync::Network, Game};
-use hurrycurry_protocol::{PacketC, PacketS, PlayerClass, PlayerID};
+use hurrycurry_protocol::{Hand, PacketC, PacketS, PlayerClass, PlayerID};
use log::warn;
use std::{thread::sleep, time::Duration};
@@ -109,6 +109,7 @@ fn main() -> Result<()> {
network.queue_out.push_back(PacketS::Interact {
player: b.id,
pos: interact,
+ hand: Hand(0),
})
}
network.queue_out.push_back(PacketS::Movement {
diff --git a/server/client-lib/src/lib.rs b/server/client-lib/src/lib.rs
index 5d5e55d5..a40eafc1 100644
--- a/server/client-lib/src/lib.rs
+++ b/server/client-lib/src/lib.rs
@@ -20,7 +20,7 @@ pub mod network;
pub mod spatial_index;
use hurrycurry_protocol::{
- glam::IVec2, movement::MovementBase, Gamedata, ItemIndex, ItemLocation, Message,
+ glam::IVec2, movement::MovementBase, Gamedata, Hand, ItemIndex, ItemLocation, Message,
MessageTimeout, PacketC, PlayerClass, PlayerID, RecipeIndex, Score, TileIndex,
};
use spatial_index::SpatialIndex;
@@ -54,8 +54,8 @@ pub struct Player {
pub name: String,
pub class: PlayerClass,
pub character: i32,
- pub interacting: Option<IVec2>,
- pub item: Option<Item>,
+ pub interacting: Option<(IVec2, Hand)>,
+ pub items: Vec<Option<Item>>,
pub communicate_persist: Option<(Message, MessageTimeout)>,
pub movement: MovementBase,
@@ -95,7 +95,7 @@ impl Game {
character,
class,
interacting: None,
- item: None,
+ items: (0..self.data.hand_count).map(|_| None).collect(),
communicate_persist: None,
movement: MovementBase::new(position),
},
@@ -117,15 +117,27 @@ impl Game {
p.movement.rotation = rot;
}
}
-
PacketC::MoveItem { from, to } => {
- *self.get_item(to) = self.get_item(from).take();
+ if let Some(item) = self.get_item(to).map(|e| e.take()) {
+ if let Some(to) = self.get_item(from) {
+ *to = item;
+ } else {
+ // TODO perhaps restore to original position?
+ }
+ }
}
PacketC::SetItem { location, item } => {
- *self.get_item(location) = item.map(|kind| Item { kind, active: None });
+ let location = self.get_item(location);
+ if let Some(location) = location {
+ *location = item.map(|kind| Item { kind, active: None });
+ }
}
PacketC::ClearProgress { item } => {
- self.get_item(item).as_mut().unwrap().active = None;
+ if let Some(slot) = self.get_item(item) {
+ if let Some(item) = slot {
+ item.active = None;
+ }
+ }
}
PacketC::SetProgress {
item,
@@ -134,13 +146,17 @@ impl Game {
speed,
warn,
} => {
- self.get_item(item).as_mut().unwrap().active = Some(Involvement {
- player,
- speed,
- warn,
- position,
- recipe: RecipeIndex(0),
- });
+ if let Some(slot) = self.get_item(item) {
+ if let Some(item) = slot {
+ item.active = Some(Involvement {
+ player,
+ speed,
+ warn,
+ position,
+ recipe: RecipeIndex(0),
+ });
+ }
+ }
}
PacketC::UpdateMap {
tile,
@@ -200,9 +216,11 @@ impl Game {
}
for player in self.players.values_mut() {
- if let Some(item) = &mut player.item {
- if let Some(active) = &mut item.active {
- active.position += active.speed;
+ for item in &mut player.items {
+ if let Some(item) = item {
+ if let Some(active) = &mut item.active {
+ active.position += active.speed;
+ }
}
}
}
@@ -223,10 +241,12 @@ impl Game {
});
}
- pub fn get_item(&mut self, location: ItemLocation) -> &mut Option<Item> {
+ pub fn get_item(&mut self, location: ItemLocation) -> Option<&mut Option<Item>> {
match location {
- ItemLocation::Tile(pos) => &mut self.tiles.get_mut(&pos).unwrap().item,
- ItemLocation::Player(pid) => &mut self.players.get_mut(&pid).unwrap().item,
+ ItemLocation::Tile(pos) => Some(&mut self.tiles.get_mut(&pos)?.item),
+ ItemLocation::Player(pid, hand) => {
+ Some(self.players.get_mut(&pid)?.items.get_mut(hand.0)?)
+ }
}
}
}
diff --git a/server/protocol/Cargo.toml b/server/protocol/Cargo.toml
index 44533f9d..b6da120a 100644
--- a/server/protocol/Cargo.toml
+++ b/server/protocol/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "hurrycurry-protocol"
-version = "7.5.0"
+version = "8.0.0"
edition = "2021"
[dependencies]
diff --git a/server/protocol/src/helpers.rs b/server/protocol/src/helpers.rs
index 924d0886..b85c2f84 100644
--- a/server/protocol/src/helpers.rs
+++ b/server/protocol/src/helpers.rs
@@ -1,7 +1,8 @@
use std::fmt::Display;
use crate::{
- DocumentElement, Gamedata, ItemIndex, ItemLocation, PlayerID, Recipe, RecipeIndex, TileIndex,
+ DocumentElement, Gamedata, Hand, ItemIndex, ItemLocation, PlayerID, Recipe, RecipeIndex,
+ TileIndex,
};
impl Gamedata {
@@ -98,11 +99,17 @@ impl Display for ItemLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ItemLocation::Tile(pos) => write!(f, "tile({pos})"),
- ItemLocation::Player(PlayerID(id)) => write!(f, "player({id})"),
+ ItemLocation::Player(PlayerID(id), hand) => write!(f, "player({id}_{hand})"),
}
}
}
+impl Display for Hand {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "h{}", self.0)
+ }
+}
+
impl Default for DocumentElement {
fn default() -> Self {
Self::Document { es: vec![] }
diff --git a/server/protocol/src/lib.rs b/server/protocol/src/lib.rs
index 2ef07015..74d463a1 100644
--- a/server/protocol/src/lib.rs
+++ b/server/protocol/src/lib.rs
@@ -71,6 +71,10 @@ pub struct RecipeIndex(pub usize);
#[serde(transparent)]
pub struct DemandIndex(pub usize);
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, Hash)]
+#[serde(transparent)]
+pub struct Hand(pub usize);
+
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct MapMetadata {
pub name: String,
@@ -98,6 +102,7 @@ pub struct Gamedata {
pub bot_algos: Vec<String>,
pub recipes: Vec<Recipe>,
pub demands: Vec<Demand>,
+ pub hand_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
@@ -124,6 +129,7 @@ pub enum PacketS {
},
Interact {
player: PlayerID,
+ hand: Hand,
#[bincode(with_serde)]
pos: Option<IVec2>,
},
@@ -144,6 +150,7 @@ pub enum PacketS {
/// For internal use only (customers)
ReplaceHand {
player: PlayerID,
+ hand: Hand,
item: Option<ItemIndex>,
},
#[serde(skip)]
@@ -344,7 +351,7 @@ pub enum Recipe {
#[serde(rename_all = "snake_case")]
pub enum ItemLocation {
Tile(#[bincode(with_serde)] IVec2),
- Player(PlayerID),
+ Player(PlayerID, Hand),
}
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
diff --git a/server/src/data/mod.rs b/server/src/data/mod.rs
index 57051fe9..64f40217 100644
--- a/server/src/data/mod.rs
+++ b/server/src/data/mod.rs
@@ -73,6 +73,7 @@ pub struct MapDecl {
walkable: Vec<String>,
chef_spawn: char,
customer_spawn: char,
+ #[serde(default)] hand_count: Option<usize>,
#[serde(default)] entities: Vec<EntityDecl>,
#[serde(default)] tile_entities: HashMap<char, EntityDecl>,
#[serde(default)] score_baseline: i64,
@@ -314,6 +315,7 @@ pub fn build_data(
item_names,
demands,
tile_names,
+ hand_count: map_in.hand_count.unwrap_or(1),
},
Serverdata {
initial_map,
diff --git a/server/src/entity/bot.rs b/server/src/entity/bot.rs
index fe4d711f..6e6c9162 100644
--- a/server/src/entity/bot.rs
+++ b/server/src/entity/bot.rs
@@ -18,7 +18,7 @@
use super::{Entity, EntityContext};
use anyhow::Result;
use hurrycurry_bot::{BotAlgo, DynBotAlgo};
-use hurrycurry_protocol::{PacketS, PlayerClass, PlayerID};
+use hurrycurry_protocol::{Hand, PacketS, PlayerClass, PlayerID};
use log::info;
use rand::random;
use std::any::Any;
@@ -72,6 +72,7 @@ impl<T: BotAlgo + Any> Entity for BotDriver<T> {
c.packet_in.push_back(PacketS::Interact {
player: self.id,
pos: input.interact,
+ hand: Hand(0),
})
}
c.packet_in.push_back(PacketS::Movement {
diff --git a/server/src/entity/tutorial.rs b/server/src/entity/tutorial.rs
index 44244862..33c0e507 100644
--- a/server/src/entity/tutorial.rs
+++ b/server/src/entity/tutorial.rs
@@ -144,7 +144,7 @@ impl StepContext<'_> {
.game
.players
.get(&self.player)
- .is_some_and(|p| p.item.as_ref().is_some_and(|i| i.kind == item))
+ .is_some_and(|p| p.items.iter().flatten().any(|i| i.kind == item))
}
pub fn find_demand(&self, item: ItemIndex) -> Option<IVec2> {
self.ent
@@ -228,7 +228,7 @@ impl StepContext<'_> {
.game
.players
.get(&self.player)
- .is_some_and(|p| p.item.as_ref().is_some_and(|i| i.kind == item))
+ .is_some_and(|p| p.items.iter().flatten().any(|i| i.kind == item))
{
if let Some(pos) = self.find_demand(item) {
Err((Some(pos), trm!("s.tutorial.serve")))
diff --git a/server/src/server.rs b/server/src/server.rs
index 462e95d4..0889cd71 100644
--- a/server/src/server.rs
+++ b/server/src/server.rs
@@ -28,8 +28,8 @@ use hurrycurry_client_lib::{Game, Involvement, Item, Player, Tile};
use hurrycurry_protocol::{
glam::{IVec2, Vec2},
movement::MovementBase,
- Gamedata, ItemLocation, Menu, MessageTimeout, PacketC, PacketS, PlayerClass, PlayerID, Score,
- TileIndex,
+ Gamedata, Hand, ItemLocation, Menu, MessageTimeout, PacketC, PacketS, PlayerClass, PlayerID,
+ Score, TileIndex,
};
use log::{info, warn};
use rand::random;
@@ -163,26 +163,28 @@ impl GameServerExt for Game {
character: player.character,
name: player.name.clone(),
});
- if let Some(item) = &player.item {
- out.push(PacketC::SetItem {
- location: ItemLocation::Player(id),
- item: Some(item.kind),
- });
- if let Some(Involvement {
- player,
- position,
- speed,
- warn,
- ..
- }) = item.active
- {
- out.push(PacketC::SetProgress {
+ for (i, item) in player.items.iter().enumerate() {
+ if let Some(item) = &item {
+ out.push(PacketC::SetItem {
+ location: ItemLocation::Player(id, Hand(i)),
+ item: Some(item.kind),
+ });
+ if let Some(Involvement {
player,
- item: ItemLocation::Player(id),
position,
speed,
warn,
- });
+ ..
+ }) = item.active
+ {
+ out.push(PacketC::SetProgress {
+ player,
+ item: ItemLocation::Player(id, Hand(i)),
+ position,
+ speed,
+ warn,
+ });
+ }
}
}
if let Some((message, timeout)) = &player.communicate_persist {
@@ -252,7 +254,7 @@ impl GameServerExt for Game {
self.players.insert(
id,
Player {
- item: None,
+ items: (0..self.data.hand_count).map(|_| None).collect(),
character,
class,
movement: MovementBase::new(position),
@@ -403,15 +405,18 @@ impl Server {
self.game.players_spatial_index.remove_entry(player);
- if let Some(item) = p.item {
- let pos = p.movement.position.floor().as_ivec2();
- if let Some(tile) = self.game.tiles.get_mut(&pos) {
- if tile.item.is_none() {
- self.packet_out.push_back(PacketC::SetItem {
- location: ItemLocation::Tile(pos),
- item: Some(item.kind),
- });
- tile.item = Some(item);
+ // TODO if holding two, one is destroyed
+ for item in p.items {
+ if let Some(item) = item {
+ let pos = p.movement.position.floor().as_ivec2();
+ if let Some(tile) = self.game.tiles.get_mut(&pos) {
+ if tile.item.is_none() {
+ self.packet_out.push_back(PacketC::SetItem {
+ location: ItemLocation::Tile(pos),
+ item: Some(item.kind),
+ });
+ tile.item = Some(item);
+ }
}
}
}
@@ -449,7 +454,7 @@ impl Server {
}
}
}
- PacketS::Interact { pos, player } => {
+ PacketS::Interact { pos, player, hand } => {
for e in &mut self.entities {
if e.interact(
EntityContext {
@@ -477,7 +482,9 @@ impl Server {
.get_mut(&pid)
.ok_or(tre!("s.error.no_player"))?;
- let (pos, edge) = match (pos, player.interacting) {
+ let pos = pos.map(|p| (p, hand));
+
+ let ((pos, hand), edge) = match (pos, player.interacting) {
(None, None) => return Ok(()), // this is silent because of auto release
(None, Some(pos)) => (pos, false),
(Some(pos), None) => (pos, true),
@@ -497,7 +504,7 @@ impl Server {
// No going back from here on
- player.interacting = if edge { Some(pos) } else { None };
+ player.interacting = if edge { Some((pos, hand)) } else { None };
let other_pid = if !self.game.data.is_tile_interactable(tile.kind) {
self.game
@@ -523,15 +530,18 @@ impl Server {
return Err(tre!("s.error.customer_interact"));
}
+ let this_hslot = this.items.get_mut(hand.0).ok_or(tre!("s.error.no_hand"))?;
+ let other_hslot = other.items.get_mut(hand.0).ok_or(tre!("s.error.no_hand"))?;
+
interact(
&self.game.data,
edge,
None,
Some(pid),
- &mut this.item,
- ItemLocation::Player(base_pid),
- &mut other.item,
- ItemLocation::Player(pid),
+ this_hslot,
+ ItemLocation::Player(base_pid, hand),
+ other_hslot,
+ ItemLocation::Player(pid, hand),
&mut self.game.score,
&mut self.score_changed,
false,
@@ -544,6 +554,11 @@ impl Server {
.get_mut(&pid)
.ok_or(tre!("s.error.no_player"))?;
+ let hslot = player
+ .items
+ .get_mut(hand.0)
+ .ok_or(tre!("s.error.no_hand"))?;
+
interact(
&self.game.data,
edge,
@@ -551,8 +566,8 @@ impl Server {
Some(pid),
&mut tile.item,
ItemLocation::Tile(pos),
- &mut player.item,
- ItemLocation::Player(pid),
+ hslot,
+ ItemLocation::Player(pid, hand),
&mut self.game.score,
&mut self.score_changed,
false,
@@ -592,14 +607,16 @@ impl Server {
timeout,
});
}
- PacketS::ReplaceHand { item, player } => {
+ PacketS::ReplaceHand { item, player, hand } => {
let pdata = self.game.players.get_mut(&player).ok_or(tre!(""))?;
- pdata.item = item.map(|i| Item {
- kind: i,
- active: None,
- });
+ if let Some(slot) = pdata.items.get_mut(hand.0) {
+ *slot = item.map(|i| Item {
+ kind: i,
+ active: None,
+ });
+ }
self.packet_out.push_back(PacketC::SetItem {
- location: ItemLocation::Player(player),
+ location: ItemLocation::Player(player, hand),
item,
})
}
@@ -665,17 +682,19 @@ impl Server {
rot: player.movement.rotation,
});
- tick_slot(
- dt,
- &self.game.data,
- &self.gamedata_index,
- None,
- &mut player.item,
- ItemLocation::Player(pid),
- &mut self.game.score,
- &mut self.score_changed,
- &mut self.packet_out,
- );
+ for (i, item) in player.items.iter_mut().enumerate() {
+ tick_slot(
+ dt,
+ &self.game.data,
+ &self.gamedata_index,
+ None,
+ item,
+ ItemLocation::Player(pid, Hand(i)),
+ &mut self.game.score,
+ &mut self.score_changed,
+ &mut self.packet_out,
+ );
+ }
}
let mut players_auto_release = Vec::new();
@@ -686,20 +705,27 @@ impl Server {
player.communicate_persist = None;
}
}
- if let Some(pos) = player.interacting {
+ if let Some((pos, hand)) = player.interacting {
if let Some(tile) = self.game.tiles.get(&pos) {
if let Some(item) = &tile.item {
if let Some(involvement) = &item.active {
if involvement.position >= 1. {
- players_auto_release.push(*pid);
+ players_auto_release.push((*pid, hand));
}
}
}
}
}
}
- for player in players_auto_release.drain(..) {
- let _ = self.packet_in(PacketS::Interact { pos: None, player }, &mut vec![]);
+ for (player, hand) in players_auto_release.drain(..) {
+ let _ = self.packet_in(
+ PacketS::Interact {
+ pos: None,
+ player,
+ hand,
+ },
+ &mut vec![],
+ );
}
let mut load_map = None;