aboutsummaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authortpart <tpart120@proton.me>2025-12-20 01:52:46 +0100
committertpart <tpart120@proton.me>2025-12-20 01:52:46 +0100
commit202a63be327bc62b514a73e325b73c055026fa48 (patch)
tree90f444c138a043b3ab32787fe972c9007f262819 /client
parent163cb0e76a5c4a70b0bf0fc591fe02e585731e29 (diff)
downloadhurrycurry-202a63be327bc62b514a73e325b73c055026fa48.tar
hurrycurry-202a63be327bc62b514a73e325b73c055026fa48.tar.bz2
hurrycurry-202a63be327bc62b514a73e325b73c055026fa48.tar.zst
Player prediction part 1
Diffstat (limited to 'client')
-rw-r--r--client/game.gd2
-rw-r--r--client/multiplayer.gd3
-rw-r--r--client/player/controllable_player.gd91
-rw-r--r--client/player/player.gd78
4 files changed, 87 insertions, 87 deletions
diff --git a/client/game.gd b/client/game.gd
index d57099ed..c1a99a6c 100644
--- a/client/game.gd
+++ b/client/game.gd
@@ -154,7 +154,7 @@ func handle_packet(p):
"movement":
if not players.has(p.player): return
var player_instance: Player = players[p.player]
- player_instance.update_position(p.pos, p.rot, p.boost)
+ 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
diff --git a/client/multiplayer.gd b/client/multiplayer.gd
index d5d31322..a0260df5 100644
--- a/client/multiplayer.gd
+++ b/client/multiplayer.gd
@@ -107,7 +107,7 @@ func fix_packet_types(val):
for k in val.keys():
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])
+ elif k in ["pos", "position", "dir"]: 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:
@@ -148,6 +148,7 @@ func send_movement(player, pos: Vector2, direction: Vector2, boost: bool):
"dir": [direction.x, direction.y],
"boost": boost
})
+
func send_tile_interact(player, pos: Vector2i, edge: bool, hand: int):
@warning_ignore("incompatible_ternary")
diff --git a/client/player/controllable_player.gd b/client/player/controllable_player.gd
index a9cdba2b..15567d65 100644
--- a/client/player/controllable_player.gd
+++ b/client/player/controllable_player.gd
@@ -41,42 +41,26 @@ class TileTarget extends InteractTarget:
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
-const BOOST_DURATION = 0.3
-const BOOST_RESTORE = 0.5
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)
-var stamina := 0.
var chat_open := false
var input_rotation = 0
-var movement_update_timer := Timer.new()
-
var vibration_timer := Timer.new()
var current_vibration_strength := 0.
var current_vibration_change := 0.
+var was_boosting = boosting
+
func _ready():
vibration_timer = Timer.new()
vibration_timer.wait_time = 0.1
vibration_timer.timeout.connect(_on_vibration_timeout)
add_child(vibration_timer)
- movement_update_timer.one_shot = false
- add_child(movement_update_timer)
- movement_update_timer.start()
- movement_update_timer.connect("timeout", func():
- if game.mp != null and game.join_state == Game.JoinState.JOINED:
- game.mp.send_movement(game.my_player_id, position_, direction, boosting)
- )
add_child(onscreen_controls)
add_child(marker)
super()
@@ -85,22 +69,10 @@ func _ready():
character.username_tag.visible = not fps
)
-const MAX_DT = 1. / 50.
func _process(delta):
super(delta)
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)
- _process_movement(dt)
- delta -= dt
- if velocity_.length() > 0.01:
- movement_update_timer.timeout.emit()
- movement_update_timer.wait_time = 1. / 50.
- else:
- movement_update_timer.timeout.emit()
- movement_update_timer.wait_time = 1. / 5.
-
update_touch_scrolls()
func _input(event: InputEvent):
@@ -139,7 +111,13 @@ func _process_movement(delta):
interact(current_interact_target)
var boost = Input.is_action_pressed("boost") or (Settings.read("gameplay.latch_boost") and boosting)
- var was_boosting = boosting
+
+ if direction != input or boosting != was_boosting:
+ if game.mp != null and game.join_state == Game.JoinState.JOINED:
+ # print("sending %s %s" % [boost, was_boosting])
+ game.mp.send_movement(game.my_player_id, position_, input, boost)
+
+ was_boosting = boosting
direction = input
update_movement(delta, boost)
if boosting and not was_boosting and Settings.read("gameplay.vibration"):
@@ -149,51 +127,6 @@ func _process_movement(delta):
position_anim = position_
rotation_anim = rotation_
-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.)
- if direction.length() < 0.5:
- direction *= 0
- rotation_ = atan2(facing.x, facing.y);
- boost = boost and direction.length() > 0.1
- boosting = boost and (boosting or stamina >= 1.0) and stamina > 0
- if boosting: stamina -= dt / BOOST_DURATION
- else: stamina += dt / BOOST_RESTORE
- stamina = max(min(stamina, 1.0), 0.0)
- var speed = PLAYER_SPEED * (BOOST_FACTOR if boosting else 1.)
- velocity_ += direction * dt * speed
- position_ += velocity_ * dt
- velocity_ = velocity_ * exp(-dt * PLAYER_FRICTION)
- collide(dt)
-
-func collide(dt: float):
- for xo in range(-1, 2):
- for yo in range(-1, 2):
- var tile = Vector2i(xo, yo) + Vector2i(position_)
- if !game.get_tile_collision(tile): continue
- tile = Vector2(tile)
- var d = aabb_point_distance(tile, tile + Vector2.ONE, position_)
- if d > PLAYER_SIZE: continue
- var h = 0.01;
- var d_sample_x = aabb_point_distance(tile, tile + Vector2.ONE, position_ + Vector2(h, 0))
- var d_sample_y = aabb_point_distance(tile, tile + Vector2.ONE, position_ + Vector2(0, h))
- var grad = (Vector2(d_sample_x - d, d_sample_y - d)) / h
-
- 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()
- if d < 0.01: continue
- if d >= PLAYER_SIZE * 2: continue
- var norm = diff.normalized();
- var f = 100 / (1 + d)
- velocity_.x += norm.x * f * dt
- velocity_.y += norm.y * f * dt
-
func is_input_enabled() -> bool:
return not game.menu.covered and not Global.game_paused
@@ -203,11 +136,7 @@ func update_touch_scrolls():
if onscreen_controls.touch_enabled:
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):
+func update_position(_new_position: Vector2, _new_direction: Vector2, _new_rotation: float, _new_boosting: bool):
# Ignore movement packets from server. (This is not a movement sync)
pass
diff --git a/client/player/player.gd b/client/player/player.gd
index 18ebcc7f..645fa72b 100644
--- a/client/player/player.gd
+++ b/client/player/player.gd
@@ -21,7 +21,11 @@ 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 SPEED: float = 25.
+const PLAYER_SPEED = 55
+const PLAYER_FRICTION = 15
+const BOOST_FACTOR = 2.5
+const BOOST_DURATION = 0.3
+const BOOST_RESTORE = 0.5
var game: Game
var id := -1. # float because json
@@ -54,6 +58,11 @@ var is_customer: bool
var is_chef: bool
var current_item_message = null
+var velocity_ := Vector2(0, 0)
+var direction := Vector2(0, 0)
+var facing := Vector2(1, 0)
+var stamina := 0.
+
var _anim_angle: float = 0.0
const DEFAULT_HAND_BASE_POSITION_CENTER: Vector3 = Vector3(0, .425, .4)
@@ -115,10 +124,61 @@ func _ready():
Settings.hook_changed_init("gameplay.usernames", self, update_username_tag)
-func update_position(new_position: Vector2, new_rotation: float, new_boosting: bool):
+func update_position(new_position: Vector2, new_direction: Vector2, new_rotation: float, new_boosting: bool):
position_ = new_position
rotation_ = new_rotation
boosting = new_boosting
+ direction = new_direction
+ print("BOOSTING: ", boosting)
+
+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.)
+ if direction.length() < 0.5:
+ direction *= 0
+ rotation_ = atan2(facing.x, facing.y);
+ boost = boost and direction.length() > 0.1
+ boosting = boost and (boosting or stamina >= 1.0) and stamina > 0
+ if boosting: stamina -= dt / BOOST_DURATION
+ else: stamina += dt / BOOST_RESTORE
+ stamina = max(min(stamina, 1.0), 0.0)
+ var speed = PLAYER_SPEED * (BOOST_FACTOR if boosting else 1.)
+ velocity_ += direction * dt * speed
+ position_ += velocity_ * dt
+ velocity_ = velocity_ * exp(-dt * PLAYER_FRICTION)
+ collide(dt)
+
+func collide(dt: float):
+ for xo in range(-1, 2):
+ for yo in range(-1, 2):
+ var tile = Vector2i(xo, yo) + Vector2i(position_)
+ if !game.get_tile_collision(tile): continue
+ tile = Vector2(tile)
+ var d = aabb_point_distance(tile, tile + Vector2.ONE, position_)
+ if d > PLAYER_SIZE: continue
+ var h = 0.01;
+ var d_sample_x = aabb_point_distance(tile, tile + Vector2.ONE, position_ + Vector2(h, 0))
+ var d_sample_y = aabb_point_distance(tile, tile + Vector2.ONE, position_ + Vector2(0, h))
+ var grad = (Vector2(d_sample_x - d, d_sample_y - d)) / h
+
+ 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()
+ if d < 0.01: continue
+ if d >= PLAYER_SIZE * 2: continue
+ var norm = diff.normalized();
+ var f = 100 / (1 + d)
+ velocity_.x += norm.x * f * dt
+ velocity_.y += norm.y * f * dt
+
+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_username_tag(state):
var tag: Label3D = character.username_tag
@@ -167,6 +227,7 @@ func pass_to(player: Player, hfrom: int, hto: int):
push_error("target is already holding an item")
player.set_item(i, hto)
+const MAX_DT = 1. / 50.
func _process(delta):
for h in hand.size():
if hand[h] != null:
@@ -178,15 +239,24 @@ func _process(delta):
movement_base.position.x = position_anim.x
movement_base.position.z = position_anim.y
movement_base.rotation.y = rotation_anim
- walking = walking or position_.distance_squared_to(position_anim) > 0.001
+ walking = direction.length_squared() > 0.1
character.walking = walking
character.boosting = boosting
- walking = false
if is_despawning:
movement_base.scale /= exp(delta * 8)
if movement_base.scale.length() < 0.01: self.queue_free()
else:
movement_base.scale = Vector3.ONE * G.interpolate(movement_base.scale.x, 1, delta * 8)
+
+ while delta > 0.001:
+ var dt = min(delta, MAX_DT)
+ _process_movement(delta)
+ position_anim = position_
+ rotation_anim = rotation_
+ delta -= dt
+
+func _process_movement(dt):
+ update_movement(dt, boosting)
func item_message(item_name: String, timeout_initial: float, timeout_remaining: float):
if item_bubble != null: