aboutsummaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authortpart <tpart120@proton.me>2025-10-20 21:09:08 +0200
committertpart <tpart120@proton.me>2025-10-20 21:09:16 +0200
commita3b0879a98bf5a0881b426913d7dd4cb9010e327 (patch)
tree7495d5c208cd3454ae14c26f42f72629df60496d /client
parenta52785f4869a09e05417f97aff1c0d5b19587463 (diff)
downloadhurrycurry-a3b0879a98bf5a0881b426913d7dd4cb9010e327.tar
hurrycurry-a3b0879a98bf5a0881b426913d7dd4cb9010e327.tar.bz2
hurrycurry-a3b0879a98bf5a0881b426913d7dd4cb9010e327.tar.zst
Refactor interact system; Implement new player interact logic
Diffstat (limited to 'client')
-rw-r--r--client/game.gd4
-rw-r--r--client/multiplayer.gd12
-rw-r--r--client/player/controllable_player.gd226
-rw-r--r--client/player/marker/marker.gd6
-rw-r--r--client/system/settings.gd1
5 files changed, 110 insertions, 139 deletions
diff --git a/client/game.gd b/client/game.gd
index 18b23e08..d490e61e 100644
--- a/client/game.gd
+++ b/client/game.gd
@@ -396,7 +396,7 @@ func handle_packet(p):
overlay_score.set_paused(p.state)
Global.game_paused = p.state
- _: push_error("Unrecognized packet type: %s" % p.type)
+ _: push_warning("Unrecognized packet type: %s" % p.type)
func system_message(s: String):
var message = TextMessage.new()
@@ -431,7 +431,7 @@ func get_tile_collision(pos: Vector2i) -> bool:
if t == null: return true
else: return not tile_walkable.has(tile_index_by_name[t])
-func get_tile_interactive(pos: Vector2i, hands: Array) -> bool:
+func is_tile_interactive(pos: Vector2i, hands: Array) -> bool:
var tile_name = map.get_tile_name(pos)
if tile_name == null: return false
var tile = tile_index_by_name[tile_name]
diff --git a/client/multiplayer.gd b/client/multiplayer.gd
index d635db41..d5221b15 100644
--- a/client/multiplayer.gd
+++ b/client/multiplayer.gd
@@ -110,12 +110,18 @@ func send_tile_interact(player, pos: Vector2i, edge: bool, hand: int):
send_packet({
"type": "interact",
"player": player,
+ "target": {"tile": [pos.x, pos.y]} if edge else null,
"hand": hand,
- "pos": [pos.x, pos.y] if edge else null,
})
-func send_player_interact(_player, _edge: bool):
- push_error("not yet implemented")
+func send_player_interact(player, target_player, target_hand: int, edge: bool, hand: int):
+ @warning_ignore("incompatible_ternary")
+ send_packet({
+ "type": "interact",
+ "player": player,
+ "target": {"player": [target_player, target_hand]} if edge else null,
+ "hand": hand,
+ })
func send_chat(player, message: String):
send_packet({
diff --git a/client/player/controllable_player.gd b/client/player/controllable_player.gd
index 83d1bdce..3ab60590 100644
--- a/client/player/controllable_player.gd
+++ b/client/player/controllable_player.gd
@@ -16,6 +16,29 @@
class_name ControllablePlayer
extends Player
+@abstract class InteractTarget:
+ var target_visual_pos: Vector3
+ var target_visual_rot: float
+
+class EmptyTarget extends InteractTarget:
+ pass
+
+class PlayerTarget extends InteractTarget:
+ var player: Player
+ func _init(player_: Player, target_visual_pos_: Vector3, target_visual_rot_: float) -> void:
+ player = player_
+ target_visual_pos = target_visual_pos_
+ target_visual_rot = target_visual_rot_
+
+class TileTarget extends InteractTarget:
+ var pos: Vector2i
+ var tile: Tile
+ func _init(pos_: Vector2i, tile_: Tile, target_visual_pos_: Vector3, target_visual_rot_: float) -> void:
+ pos = pos_
+ tile = tile_
+ target_visual_pos = target_visual_pos_
+ target_visual_rot = target_visual_rot_
+
const PLAYER_SPEED = 55
const PLAYER_FRICTION = 15
const BOOST_FACTOR = 2.5
@@ -26,6 +49,7 @@ const MAX_PLAYER_INTERACT_DIST := 1.9
var onscreen_controls = preload("res://player/onscreen_controls/controls.tscn").instantiate()
var marker: Marker = preload("res://player/marker/marker.tscn").instantiate()
+var current_interact_target: InteractTarget = EmptyTarget.new()
var facing := Vector2(1, 0)
var velocity_ := Vector2(0, 0)
var direction := Vector2(0, 0)
@@ -37,12 +61,6 @@ var vibration_timer := Timer.new()
var current_vibration_strength := 0.
var current_vibration_change := 0.
-var target_tile: Vector2i = Vector2i.ZERO
-var target_visual: Vector3 = Vector3.ZERO
-var found_interact_target := false
-
-var last_interaction = null # : Vector2i?
-
func _ready():
vibration_timer = Timer.new()
vibration_timer.wait_time = 0.1
@@ -68,7 +86,7 @@ func _ready():
const MAX_DT = 1. / 50.
func _process(delta):
super(delta)
- marker.position = G.interpolate(marker.position, target_visual, delta * 30.)
+ marker.position = G.interpolate(marker.position, current_interact_target.target_visual_pos, delta * 30.)
if is_despawning: return
while delta > 0.001:
var dt = min(delta, MAX_DT)
@@ -91,7 +109,6 @@ func _input(event: InputEvent):
_: return
if event.pressed: Input.action_press(action)
else: Input.action_release(action)
-
var fps_look = Vector2(0., 0.)
func _process_movement(delta):
@@ -100,7 +117,6 @@ func _process_movement(delta):
if Settings.read("gameplay.first_person"): input = (input + Vector2(0.0, -0.1)).rotated(fps_look.y)
else: input = input.rotated(input_rotation)
- var boost = Input.is_action_pressed("boost") or (Settings.read("gameplay.latch_boost") and boosting)
if (Input.is_action_pressed("interact_left")
or Input.is_action_just_released("interact_left")
@@ -108,12 +124,15 @@ func _process_movement(delta):
or Input.is_action_just_released("interact_right")):
input *= 0
else:
- update_interact_target()
+ if Settings.read("gameplay.first_person"): current_interact_target = get_interact_target_fps()
+ else: current_interact_target = get_interact_target()
- interact()
+ interact(current_interact_target)
+
+ var boost = Input.is_action_pressed("boost") or (Settings.read("gameplay.latch_boost") and boosting)
var was_boosting = boosting
direction = input
- update(delta, boost)
+ update_movement(delta, boost)
if boosting and not was_boosting and Settings.read("gameplay.vibration"):
Input.start_joy_vibration(0, 0, input.length(), 0.15)
Input.vibrate_handheld(75, input.length() * 0.1)
@@ -121,7 +140,9 @@ func _process_movement(delta):
position_anim = position_
rotation_anim = rotation_
-func update(dt: float, boost: bool):
+func update_movement(dt: float, boost: bool):
+ # This function implements the server's movement system.
+ # It may not be modified, so that it matches the server's logic.
direction = direction.limit_length(1.)
if direction.length() > 0.05:
facing = direction + (facing - direction) * exp(-dt * 10.)
@@ -154,7 +175,6 @@ func collide(dt: float):
position_ += (PLAYER_SIZE - d) * grad;
velocity_ -= grad * grad.dot(velocity_)
-
for player: Player in game.players.values():
var diff = position_ - player.position_
var d = diff.length()
@@ -175,12 +195,15 @@ func update_touch_scrolls():
onscreen_controls.visible = is_input_enabled()
func aabb_point_distance(mi: Vector2, ma: Vector2, p: Vector2) -> float:
+ # Calculates the signed distance of a point p to an axis aligned bounding box
return (p - p.clamp(mi, ma)).length()
func update_position(_new_position: Vector2, _new_rotation: float, _new_boosting: bool):
+ # Ignore movement packets from server. (This is not a movement sync)
pass
func progress(position__: float, speed: float, warn: bool, h):
+ # An item the player is holding is making progress
super(position__, speed, warn, h)
if warn:
current_vibration_strength = position__
@@ -215,135 +238,78 @@ func take_item(tile: Tile, h: int):
Input.start_joy_vibration(0, 0.1, 0.0, 0.075)
Input.vibrate_handheld(75, 0.1)
-func interact():
+func interact(interact_target: InteractTarget):
if not is_input_enabled(): return
- var tile = game.map.get_tile_instance(target_tile)
- if tile != null:
+ if interact_target is EmptyTarget:
+ marker.visible = false
+ marker.rotation.y = 0.
+ return
+ else:
if not marker.visible:
+ # New target acquired!
marker.visible = true
- marker.position = target_visual
-
- # clear last interaction if target_tile has moved since
- if last_interaction != null and not last_interaction == target_tile:
- game.mp.send_tile_interact(game.my_player_id, last_interaction, false, 0)
- marker.set_interacting(false)
- last_interaction = null
- marker.set_interactive(found_interact_target)
- if found_interact_target:
- for h in [0, 1]:
- if Input.is_action_just_pressed("interact_" + G.index_to_hand(h)) and last_interaction == null:
- last_interaction = target_tile
- game.mp.send_tile_interact(game.my_player_id, target_tile, true, h if h < Global.hand_count else 0)
- 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.my_player_id, target_tile, false, h if h < Global.hand_count else 0)
- marker.set_interacting(false)
- else:
- marker.visible = false
-
-func update_interact_target():
- if Settings.read("gameplay.first_person"):
- return update_interact_target_fps()
- match Settings.read("gameplay.interact_target"):
- "dirsnap": return update_interact_target_dirsnap()
- "dir": return update_interact_target_dir()
- _: return update_interact_target_dir()
+ marker.position = interact_target.target_visual_pos
+ marker.rotation.y = interact_target.target_visual_rot
+
+ if interact_target is TileTarget:
+ for h in [0, 1]:
+ var hand_: int = h if h < Global.hand_count else 0
+ if Input.is_action_just_pressed("interact_" + G.index_to_hand(h)) and not marker.interacting:
+ game.mp.send_tile_interact(game.my_player_id, interact_target.pos, true, hand_)
+ interact_target.tile.interact()
+ marker.set_interacting(true)
+ if Input.is_action_just_released("interact_" + G.index_to_hand(h)):
+ game.mp.send_tile_interact(game.my_player_id, interact_target.pos, false, hand_)
+ marker.set_interacting(false)
+ elif interact_target is PlayerTarget:
+ for h in [0, 1]:
+ var hand_: int = h if h < Global.hand_count else 0
+ if Input.is_action_just_pressed("interact_" + G.index_to_hand(h)) and not marker.interacting:
+ game.mp.send_player_interact(game.my_player_id, interact_target.player.id, hand_, true, hand_)
+ marker.set_interacting(true)
+ if Input.is_action_just_released("interact_" + G.index_to_hand(h)):
+ game.mp.send_player_interact(game.my_player_id, interact_target.player.id, hand_, false, hand_)
+ marker.set_interacting(false)
-func update_interact_target_dir():
- target_tile = Vector2i(
- int(floor(movement_base.position.x + sin(movement_base.rotation.y))),
- int(floor(movement_base.position.z + cos(movement_base.rotation.y)))
- )
- var tile = game.map.get_tile_instance(target_tile)
- if tile != null:
- found_interact_target = game.get_tile_interactive(target_tile, hand)
- target_visual = tile.item_base.global_position
- else:
- found_interact_target = false
- target_visual = Vector3(float(target_tile.x), 1., float(target_tile.y))
+func get_interact_target_fps() -> InteractTarget:
+ # TODO!
+ return EmptyTarget.new()
-func update_interact_target_fps():
- target_tile = Vector2i(
- int(floor(movement_base.position.x + sin(-fps_look.y + PI))),
- int(floor(movement_base.position.z + cos(-fps_look.y + PI)))
+func get_interact_target() -> InteractTarget:
+ var movement_base_2d := Vector2(movement_base.position.x, movement_base.position.z)
+ var interact_target_pos := movement_base_2d + Vector2(
+ sin(movement_base.rotation.y) * 0.7,
+ cos(movement_base.rotation.y) * 0.7
)
- var tile = game.map.get_tile_instance(target_tile)
- if tile != null:
- found_interact_target = game.get_tile_interactive(target_tile, hand)
- target_visual = tile.item_base.global_position
- else:
- found_interact_target = false
- target_visual = Vector3(float(target_tile.x), 1., float(target_tile.y))
+ var interact_target_pos_i := interact_target_pos.floor()
-
-func update_interact_target_dirsnap():
- var interact_target := Vector2(
- movement_base.position.x + sin(movement_base.rotation.y) * 0.7,
- movement_base.position.z + cos(movement_base.rotation.y) * 0.7
- )
- var interact_target_i := interact_target.floor()
- target_visual = Vector3(interact_target_i.x, 0, interact_target_i.y)
- target_tile = interact_target_i
+ var best_interact_target: InteractTarget = EmptyTarget.new()
var best_distance := 100.
- found_interact_target = false
- # Calculate player positions. Tiles with players on them are valid interact targets.
- var player_positions: Dictionary = {} # Dictionary[id: int, pos: Vector3]
for p: Player in game.players.values():
- if not p.is_chef: continue
- player_positions[p.id] = Vector3(p.position_anim.x, 0, p.position_anim.y)
+ if not p.is_chef: continue # Customers cannot be interacted with
+ if p.id == game.my_player_id: continue # I can't interact with myself
+ var distance: float = p.position_anim.distance_to(interact_target_pos)
+ var self_target_distance: float = p.position_anim.distance_to(movement_base_2d)
+ if self_target_distance < MAX_PLAYER_INTERACT_DIST and distance < best_distance:
+ best_distance = distance
+ best_interact_target = PlayerTarget.new(p, Vector3(p.position_anim.x, 0, p.position_anim.y), p.rotation_anim)
# Test all tiles in a 3x3 square around the player for interactible tiles.
- # Return the one which is closest to interact_target.
+ # Return the one which is closest to interact_target_pos.
for offset_x in range(-1, 2):
- for offset_y in range (-1, 2):
- var offset_cursor := interact_target_i + Vector2(offset_x, offset_y)
+ for offset_y in range(-1, 2):
+ var offset_cursor := interact_target_pos_i + Vector2(offset_x, offset_y)
var tile_center := Vector2(offset_cursor) + Vector2(0.5, 0.5)
- if game.get_tile_interactive(offset_cursor, hand):
- var cursor_tile_distance := interact_target.distance_to(tile_center)
- var player_tile_distance := Vector2(
- movement_base.position.x - tile_center.x,
- movement_base.position.z - tile_center.y
- ).length()
- if player_tile_distance < MAX_PLAYER_INTERACT_DIST && cursor_tile_distance < best_distance:
- found_interact_target = true
- best_distance = cursor_tile_distance
- var tile = game.map.get_tile_instance(offset_cursor)
- target_visual = tile.item_base.global_position
- if tile.item != null and not tile.item.is_round():
- marker.rotation.y = tile.item.rotation.y
- else: marker.rotation.y = 0.
- target_tile = offset_cursor
- continue
-
- # Check if there are any players on this tile.
- # If there are multilpe, remember the player closest to the center of the tile
- # (They will be the interact target)
- var best_cursor_tile_distance := 100.
- var best_player_tile_distance := 100.
- var best_player_pos: Vector3
- var best_player_rot: float
- for p_id in player_positions.keys():
- if p_id == game.my_player_id: continue # I can't interact with myself
- var p_pos: Vector3 = player_positions[p_id]
- var p_pos_2d := Vector2(p_pos.x, p_pos.z)
- var tile_center_distance := p_pos_2d.distance_to(tile_center)
- var cursor_tile_distance := interact_target.distance_to(tile_center)
- var player_tile_distance := Vector2(movement_base.position.x, movement_base.position.z).distance_to(tile_center)
- if tile_center_distance < 0.7:
- if cursor_tile_distance < best_cursor_tile_distance:
- best_cursor_tile_distance = cursor_tile_distance
- best_player_tile_distance = player_tile_distance
- best_player_pos = p_pos
- best_player_rot = game.players[p_id].rotation_anim
-
- if best_player_tile_distance < MAX_PLAYER_INTERACT_DIST && best_cursor_tile_distance < best_distance:
- found_interact_target = true
- best_distance = best_cursor_tile_distance
- target_visual = best_player_pos
- marker.rotation.y = best_player_rot
- target_tile = offset_cursor
+ if game.is_tile_interactive(offset_cursor, hand):
+ var distance := interact_target_pos.distance_to(tile_center)
+ var self_target_distance := movement_base_2d.distance_to(tile_center)
+ if self_target_distance < MAX_PLAYER_INTERACT_DIST && distance < best_distance:
+ best_distance = distance
+ var tile := game.map.get_tile_instance(offset_cursor)
+ var target_rot := tile.item.rotation.y if tile.item != null and not tile.item.is_round() else 0.
+ best_interact_target = TileTarget.new(offset_cursor, tile, tile.item_base.global_position, target_rot)
+
+ return best_interact_target
diff --git a/client/player/marker/marker.gd b/client/player/marker/marker.gd
index 31c99e2d..7f9616c2 100644
--- a/client/player/marker/marker.gd
+++ b/client/player/marker/marker.gd
@@ -16,11 +16,11 @@
class_name Marker
extends Node3D
+var interacting := false
+
@onready var _cube: MeshInstance3D = $Cube
@onready var mat: ShaderMaterial = _cube.get_active_material(0)
-func set_interactive(val: bool):
- visible = val
-
func set_interacting(val: bool):
mat.set_shader_parameter("interacting", val)
+ interacting = val
diff --git a/client/system/settings.gd b/client/system/settings.gd
index 5fb9de82..4ab16fbf 100644
--- a/client/system/settings.gd
+++ b/client/system/settings.gd
@@ -27,7 +27,6 @@ static func get_root():
ButtonSetting.new("setup_completed", false, launch_setup),
ToggleSetting.new("tutorial_disabled", false),
ToggleSetting.new("first_person", false),
- DropdownSetting.new("interact_target", "dirsnap", ["dir", "dirsnap"]),
PathSetting.new("screenshot_path", "", FileDialog.FileMode.FILE_MODE_OPEN_DIR),
]),
SettingsCategory.new("graphics", [