extends CharacterBody2D @export var grid_size := 16 var player_id: int = -1 var is_local := false var is_moving := false var server_position := Vector2.ZERO signal interaction_request(target_id, target_name) @onready var ray = $RayCast2D func _ready(): server_position = position # Initial sprite setup $Sprite2D.scale = Vector2(0.5, 0.5) $Sprite2D.frame = 1 # Physics Collision (Movement) collision_layer = 2 # Force Player to be on Layer 2 collision_mask = 1 # Only collide with World (Layer 1), walk through other Players (Layer 2) # RayCast Setup ray.enabled = true ray.hit_from_inside = true # Detect if overlapping ray.collision_mask = 3 # Layers 1 (World) and 2 (Player) ray.add_exception(self) # Ignore local player if is_local: var cam = Camera2D.new() cam.zoom = Vector2(1.0, 1.0) cam.position_smoothing_enabled = true add_child(cam) NetworkManager.chat_message_received.connect(_on_chat_message) # Set Name Label var name_label = $NameLabel if is_local: name_label.text = NetworkManager.player_info["name"] name_label.modulate = Color(0.5, 1, 0.5) # Highlight local player elif player_id != -1 and NetworkManager.players.has(player_id): name_label.text = NetworkManager.players[player_id].name func _physics_process(_delta): if is_local: _process_input() else: _process_remote_movement() # Interaction Check (F Key) moved to _input to prevent spam var is_sitting := false var current_chair: Node2D = null func _process_input(): # Stop input if chatting var focus_owner = get_viewport().gui_get_focus_owner() if focus_owner is LineEdit: return if is_moving or is_sitting: return var input_dir := Vector2.ZERO if Input.is_action_pressed("ui_up") or Input.is_key_pressed(KEY_W): input_dir = Vector2.UP $Sprite2D.frame = 4 # Row 1, Center (Back view) $Sprite2D.flip_h = false ray.target_position = Vector2(0, -96) elif Input.is_action_pressed("ui_down") or Input.is_key_pressed(KEY_S): input_dir = Vector2.DOWN $Sprite2D.frame = 1 # Row 0, Center (Front view) $Sprite2D.flip_h = false ray.target_position = Vector2(0, 96) elif Input.is_action_pressed("ui_left") or Input.is_key_pressed(KEY_A): input_dir = Vector2.LEFT $Sprite2D.frame = 3 # Row 1, Left (Side view) $Sprite2D.flip_h = false ray.target_position = Vector2(-96, 0) elif Input.is_action_pressed("ui_right") or Input.is_key_pressed(KEY_D): input_dir = Vector2.RIGHT $Sprite2D.frame = 3 # Use same side view $Sprite2D.flip_h = true # Flip it ray.target_position = Vector2(96, 0) if input_dir != Vector2.ZERO: _attempt_move(input_dir) func _input(event): if not is_local: return if event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_F: # Check if ignoring input (chatting) var focus_owner = get_viewport().gui_get_focus_owner() if focus_owner is LineEdit: return if is_sitting: stand_up() return ray.force_raycast_update() if ray.is_colliding(): var collider = ray.get_collider() # Check if collider is a player (ID-based name) if collider: if collider.has_method("interact"): var text = collider.interact() if text: show_bubble(text) # Show response locally for now elif collider.name.is_valid_int(): var target_id = int(str(collider.name)) var p_info = NetworkManager.players.get(target_id) var t_name = p_info["name"] if p_info else "Unknown" interaction_request.emit(target_id, t_name) func start_sitting(target_pos: Vector2, chair: Node2D): if is_sitting: return is_sitting = true is_moving = false # Force stop moving current_chair = chair # Disable physics/collision set_physics_process(false) $CollisionShape2D.set_deferred("disabled", true) # Tween to seat var tween = create_tween() tween.tween_property(self, "position", target_pos, 0.3).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) # Visuals $Sprite2D.frame = 1 # Sit/Idle Front $Sprite2D.flip_h = false # You might want to offset Z-index if chair has backrest vs stool # z_index = 0 # Default func stand_up(): if not is_sitting: return is_sitting = false # Notify chair if current_chair and current_chair.has_method("on_player_stand_up"): current_chair.on_player_stand_up() current_chair = null # Restore physics set_physics_process(true) $CollisionShape2D.set_deferred("disabled", false) # Optional: Move slightly forward to avoid clipping immediately if collision enables var exit_pos = position + Vector2(0, 10) position = exit_pos NetworkManager.send_move(position.x, position.y, $Sprite2D.flip_h, $Sprite2D.frame) func _attempt_move(dir: Vector2): var target_pos = position + dir * grid_size # Save state var old_target = ray.target_position var old_mask = ray.collision_mask var old_hfi = ray.hit_from_inside var old_cwa = ray.collide_with_areas # Configure for movement check (World Only, Short Range, Bodies Only) ray.target_position = dir * grid_size ray.collision_mask = 1 ray.hit_from_inside = false ray.collide_with_areas = false # Ignore Areas (like Doors), only hit Walls (Bodies) ray.force_raycast_update() var blocked = ray.is_colliding() # Restore state for Chat Interaction ray.target_position = old_target ray.collision_mask = old_mask ray.hit_from_inside = old_hfi ray.collide_with_areas = old_cwa if not blocked: _tween_movement(target_pos) # Send to server WITH orientation NetworkManager.send_move(target_pos.x, target_pos.y, $Sprite2D.flip_h, $Sprite2D.frame) func _process_remote_movement(): if position != server_position and not is_moving: _tween_movement(server_position) func set_remote_state(pos: Vector2, flip_h: bool, frame: int): if is_local: return # Do not let remote updates override local input server_position = pos $Sprite2D.flip_h = flip_h $Sprite2D.frame = frame func _tween_movement(target_pos: Vector2): is_moving = true var tween = create_tween() tween.tween_property(self, "position", target_pos, 0.2) tween.tween_callback(func(): is_moving = false) func _on_chat_message(sender_id, text): if sender_id == player_id: show_bubble(text) func show_bubble(text): $NameLabel.hide() var bubble = preload("res://Scenes/UI/ChatBubble.tscn").instantiate() add_child(bubble) bubble.set_text(text) bubble.tree_exited.connect(func(): $NameLabel.show())