* Added console

* Set the game to automatically start a server and client.
* Made it so second player can pick up pot correctly and move it around.
* Still buggy when putting it down.
* Disabled "Countdown" as it became annoying when testing.
This commit is contained in:
2025-10-06 23:49:07 +02:00
parent 4ec52c34d4
commit ff083769d8
15 changed files with 1007 additions and 126 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"godotTools.editorPath.godot4": "c:\\Program Files\\godot\\4\\Godot_v4.4-stable_win64.exe"
}

View File

@@ -1,5 +1,7 @@
extends Node2D
@export var DEBUG_MULTIPLAYER: bool = true
var player_scene = preload("res://scripts/entities/player/player.tscn")
var pot_scene = preload("res://scripts/entities/world/pot.tscn")
@@ -21,6 +23,12 @@ func _ready() -> void:
#pot.position = Vector2(90,80)
#$SpawnRoot.add_child(pot)
if DEBUG_MULTIPLAYER:
# Add a random delay to ensure instances start at different times
var random_delay = randf_range(0.1, 0.5)
await get_tree().create_timer(random_delay).timeout
call_deferred("_setup_debug_multiplayer")
pass
func _finishedCountdown():
@@ -32,7 +40,7 @@ func _finishedCountdown():
_syncTimerToPlayer.rpc($TimerRound.time_left)
pass
func time_to_minutes_secs(time : float):
func time_to_minutes_secs(time: float):
var mins = int(floor(int(time) / 60.0))
time -= mins * 60
var secs = int(time)
@@ -44,12 +52,12 @@ func time_to_minutes_secs(time : float):
func _process(_delta: float) -> void:
if round_finished == false and round_started == false and start_round == false and multiplayer != null and multiplayer.is_server():
# make sure atleast 2 players are connected
var players = 0
for pl:CharacterBody2D in $SpawnRoot.get_children():
var _players = 0
for pl: CharacterBody2D in $SpawnRoot.get_children():
if "is_player" in pl:
players += 1
if players == 2:
start_round_func.rpc()
_players += 1
#if _players == 2:
#start_round_func.rpc()
if round_started:
$HUD/MarginContainerUpperRight/HBoxContainer/VBoxContainer/LabelTimeValue.text = time_to_minutes_secs($TimerRound.time_left)
pass
@@ -67,14 +75,14 @@ func _finishedHosting():
has_started = true
pass
func _addPlayer(id:int):
func _addPlayer(id: int):
print("add player:", id)
#if id == 1:
#var pot:CharacterBody2D = pot_scene.instantiate()
#pot.position = Vector2(90,80)
#$SpawnRoot.add_child(pot, true)
var player:CharacterBody2D = player_scene.instantiate()
var player: CharacterBody2D = player_scene.instantiate()
player.name = str(id)
#find empty 16x16 tile to spawn player
'
@@ -89,14 +97,14 @@ func _addPlayer(id:int):
terrainMultiplier = 0.5
pass
pass'
var best_spawn = Vector2(0,0)
var best_spawn = Vector2(0, 0)
var best_distance = -1
for spawnP:Node2D in $PlayerSpawnPoints.get_children():
for spawnP: Node2D in $PlayerSpawnPoints.get_children():
var pos = spawnP.position
var min_distance = INF
# find spawn position which is furthest from all players...
for pl:CharacterBody2D in $SpawnRoot.get_children():
for pl: CharacterBody2D in $SpawnRoot.get_children():
if "is_player" in pl:
var dist = pl.position.distance_to(pos)
min_distance = min(min_distance, dist) # Keep the smallest distance
@@ -116,12 +124,12 @@ func _addPlayer(id:int):
pass
@rpc("reliable")
func _syncTimerToPlayer(iTimeLeft:float):
func _syncTimerToPlayer(iTimeLeft: float):
round_started = true
$TimerRound.start(iTimeLeft)
pass
func _delPlayer(id:int):
func _delPlayer(id: int):
if !$SpawnRoot.has_node(str(id)):
return
$SpawnRoot.get_node(str(id)).queue_free()
@@ -129,18 +137,20 @@ func _delPlayer(id:int):
func _on_timer_timeout() -> void:
if has_started:
var countPots = 0
for child in $SpawnRoot.get_children():
if "object_name" in child:
countPots+=1
countPots += 1
pass
if countPots < 8:
var pot = pot_scene.instantiate()
pot.is_spawning = true
pot.positionZ = 90
pot.position = Vector2(64+16*randi_range(0,5),64+16*randi_range(0,5))
pot.position = Vector2(64 + 16 * randi_range(0, 5), 64 + 16 * randi_range(0, 5))
# Set server as authority for pot synchronization
pot.set_multiplayer_authority(1)
Console.print("Pot spawned with authority: ", pot.get_multiplayer_authority())
$SpawnRoot.add_child(pot, true)
$TimerSpawnPots.wait_time = randf_range(0.2, 1.4)
$TimerSpawnPots.start() # restart timer...
@@ -158,18 +168,18 @@ func _on_timer_round_timeout() -> void:
func _on_timer_until_next_round_timeout() -> void:
if multiplayer.is_server():
for pl2:CharacterBody2D in $SpawnRoot.get_children():
for pl2: CharacterBody2D in $SpawnRoot.get_children():
if "is_player" in pl2:
var best_spawn = Vector2(0,0)
var best_spawn = Vector2(0, 0)
var best_distance = -1
for spawnP:Node2D in $PlayerSpawnPoints.get_children():
for spawnP: Node2D in $PlayerSpawnPoints.get_children():
var pos = spawnP.position
var min_distance = INF
# find spawn position which is furthest from all players...
for pl:CharacterBody2D in $SpawnRoot.get_children():
for pl: CharacterBody2D in $SpawnRoot.get_children():
if "is_player" in pl and pl != pl2:
var dist = pl.position.distance_to(pos)
min_distance = min(min_distance, dist) # Keep the smallest distance
@@ -184,3 +194,52 @@ func _on_timer_until_next_round_timeout() -> void:
if multiplayer.is_server():
start_round_func.rpc()
pass # Replace with function body.
func _setup_debug_multiplayer():
# Get the character select scene to access the selected character
var character_select = get_tree().get_first_node_in_group("character_select")
if not character_select:
# Try to find it in the scene tree
character_select = get_tree().current_scene.get_node_or_null("CanvasLayer/CharacterSelect")
if character_select and character_select.has_method("get_current_character_stats"):
# Set the character data from the character select
MultiplayerManager.character_data = character_select.get_current_character_stats()
else:
# Fallback: create a default character
MultiplayerManager.character_data = CharacterStats.new()
MultiplayerManager.character_data.character_name = "DebugPlayer"
$MainMenu._showHostButton()
# Determine if this instance should host or join
# Check for command line arguments first
var should_host = false
var args = OS.get_cmdline_args()
# Check if --host or --join argument was passed
if "--host" in args:
should_host = true
print("DEBUG: Host mode specified via command line argument")
elif "--join" in args:
should_host = false
print("DEBUG: Join mode specified via command line argument")
else:
# Fallback: use process ID for deterministic behavior
var process_id = OS.get_process_id()
should_host = process_id % 2 == 1
print("DEBUG: No command line args, using process ID: ", process_id, " Should host: ", should_host)
if should_host:
print("DEBUG: Starting as HOST")
$MainMenu._on_button_host_pressed()
else:
print("DEBUG: Starting as CLIENT")
$MainMenu._on_button_join_pressed()
func _exit_tree():
# Clean up the debug lock file when exiting
if DEBUG_MULTIPLAYER:
var lock_file_path = "user://debug_host.lock"
if FileAccess.file_exists(lock_file_path):
DirAccess.remove_absolute(lock_file_path)
print("DEBUG: Cleaned up host lock file")

View File

@@ -12,7 +12,7 @@ config_version=5
config/name="ruinborn_new"
run/main_scene="uid://c6s2i06bbd6u6"
config/features=PackedStringArray("4.4", "Forward Plus")
config/features=PackedStringArray("4.5", "Forward Plus")
run/max_fps=60
boot_splash/show_image=false
config/icon="res://icon.svg"
@@ -20,6 +20,7 @@ config/icon="res://icon.svg"
[autoload]
MultiplayerManager="*res://scripts/Autoloads/multiplayer_manager.tscn"
Console="*res://scripts/Autoloads/console.tscn"
[display]
@@ -68,8 +69,7 @@ ui_down={
}
Attack={
"deadzone": 0.2,
"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":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
"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":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
]
}
Use={
@@ -78,6 +78,11 @@ Use={
, 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":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
Console={
"deadzone": 0.2,
"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":96,"key_label":0,"unicode":167,"location":0,"echo":false,"script":null)
]
}
[layer_names]

View File

@@ -0,0 +1,29 @@
extends Node2D
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
if (Input.is_action_just_pressed("Console")):
var tween = create_tween()
var target_y = 0
if ($CanvasLayer/ColorRectBG.position.y == 0):
#tween console to -y 240
target_y = -$CanvasLayer/ColorRectBG.size.y
tween.tween_property($CanvasLayer/ColorRectBG, "position:y", target_y, 0.13).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
pass
func isOpen() -> bool:
return $CanvasLayer/ColorRectBG.position.y == 0
func print(...args) -> void:
var line := ""
for arg in args:
line += str(arg)
print(line)
# append to your console label
$CanvasLayer/ColorRectBG/VBoxContainer/ScrollContainer/LabelText.text += "\n" + line

View File

@@ -0,0 +1 @@
uid://c8t6r0pvo3ko8

View File

@@ -0,0 +1,52 @@
[gd_scene load_steps=3 format=3 uid="uid://8bd1fipm7lw5"]
[ext_resource type="Script" uid="uid://c8t6r0pvo3ko8" path="res://scripts/Autoloads/console.gd" id="1_c563n"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_oawmp"]
[node name="Console" type="Node2D"]
script = ExtResource("1_c563n")
[node name="CanvasLayer" type="CanvasLayer" parent="."]
layer = 22
[node name="ColorRectBG" type="ColorRect" parent="CanvasLayer"]
material = SubResource("ShaderMaterial_oawmp")
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 320.0
grow_horizontal = 2
color = Color(0, 0, 0, 0.74509805)
[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/ColorRectBG"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ScrollContainer" type="ScrollContainer" parent="CanvasLayer/ColorRectBG/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="LabelText" type="RichTextLabel" parent="CanvasLayer/ColorRectBG/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
focus_mode = 2
vertical_alignment = 2
selection_enabled = true
[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/ColorRectBG/VBoxContainer"]
layout_mode = 2
[node name="LabelGT" type="Label" parent="CanvasLayer/ColorRectBG/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = ">"
[node name="LineEditInput" type="LineEdit" parent="CanvasLayer/ColorRectBG/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
caret_blink = true
caret_force_displayed = true

View File

@@ -22,7 +22,7 @@ const MAX_CHAT_HISTORY = 10
var connection_label_timer: Timer
var connection_label_tween: Tween
var character_data:CharacterStats = null
var character_data: CharacterStats = null
var SERVER_PORT = 21212
var SERVER_HOST = "ruinborn.thefirstboss.com"
@@ -39,8 +39,8 @@ var peers_ready_for_sync: Dictionary = {}
# Add at the top with other variables
var peers_character_data: Dictionary = {}
signal addPlayerSignal(id:int)
signal delPlayerSignal(id:int)
signal addPlayerSignal(id: int)
signal delPlayerSignal(id: int)
signal finished_hosting()
signal client_ready_for_players(peer_id: int)
signal connectionFailed
@@ -242,6 +242,80 @@ func broadcast_chat_message(message: String):
# Add local copy of the message
add_chat_message("You: " + message, "chat")
@rpc("any_peer", "reliable")
func request_lift_pot(pot_path: NodePath, peer_id: int):
Console.print("=== RPC RECEIVED ===")
Console.print("MultiplayerManager received request_lift_pot from peer ", peer_id, " for pot at ", pot_path)
Console.print("Current peer ID: ", multiplayer.get_unique_id())
Console.print("Is server: ", multiplayer.is_server())
Console.print("===================")
if multiplayer.is_server():
var pot = get_node_or_null(pot_path)
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(peer_id))
Console.print("MultiplayerManager found pot: ", pot, " player: ", player)
if pot and "lift" in pot and pot.liftable and player:
Console.print("MultiplayerManager authorizing lift for peer ", peer_id)
# Clear grabbed entity if it's not the pot we're lifting
if player.grabbed_entity != null and player.grabbed_entity != pot and "release" in player.grabbed_entity:
player.grabbed_entity.release()
if player.grabbed_entity != pot:
player.grabbed_entity = null
# Don't clear grabbed_entity_path if we're lifting the same pot
# This prevents the pot from being released when we're trying to lift it
player.is_grabbing = false
player.is_lifting = true
# Set held_entity_path directly on server, use RPC for clients
player.held_entity_path = str(pot.get_path())
# Send RPC to all other players (excluding server)
var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()
for p in all_players:
if p != player and p.has_method("set_held_entity_path_rpc"):
p.set_held_entity_path_rpc.rpc(str(pot.get_path()))
pot.lift(player)
# Set animation on the player who is lifting (the joiner)
player.current_animation = "LIFT"
# Sync animation to all clients
player.sync_animation.rpc("LIFT")
# Pot state is auto-synced via @export variables, no need for manual sync
# Sync animation and entity to all clients
#print("MultiplayerManager syncing LIFT animation and held_entity to all clients")
# Send RPC to all players except the server (since server already has correct state)
#var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()
#for p in all_players:
#if p.has_method("sync_animation") and p.get_multiplayer_authority() != 1:
#print("MultiplayerManager syncing to player: ", p.name)
#p.sync_animation.rpc("LIFT")
#p.sync_held_entity.rpc(str(pot.get_path()))
#p.sync_grabbed_entity.rpc("")
else:
Console.print("MultiplayerManager denied lift request - pot: ", pot, " liftable: ", pot.liftable if pot else "null", " player: ", player)
@rpc("any_peer", "reliable")
func request_throw_pot(pot_path: NodePath, peer_id: int, direction: Vector2):
Console.print("MultiplayerManager received request_throw_pot from peer ", peer_id, " for pot at ", pot_path)
if multiplayer.is_server():
var pot = get_node_or_null(pot_path)
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(peer_id))
Console.print("MultiplayerManager found pot: ", pot, " player: ", player)
if pot and player:
# Check if the pot is being held by this player
if pot.holder_peer_id == peer_id or (pot.holder != null and pot.holder.get_multiplayer_authority() == peer_id):
Console.print("MultiplayerManager authorizing throw for peer ", peer_id)
pot.throw(direction)
# Use RPC to clear held_entity_path on all players (including server)
player.set_held_entity_path_rpc.rpc("")
player.current_animation = "THROW"
# Pot state is auto-synced via @export variables, no need for manual sync
# Sync animation to all clients (entity sync is handled automatically by PlayerSynchronizer)
var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()
for p in all_players:
if p.has_method("sync_animation") and p.get_multiplayer_authority() != 1:
p.sync_animation.rpc("THROW")
else:
Console.print("MultiplayerManager denied throw - holder_peer_id mismatch or holder not found")
func sortScoreArr(a, b):
if a.kills > b.kills:
return true # More kills should come first
@@ -251,7 +325,7 @@ func sortScoreArr(a, b):
var previousScores = []
func updateScore(playerJoined:bool = false):
func updateScore(playerJoined: bool = false):
%LabelPlayerNr.text = "#"
%LabelPlayerNames.text = "Player"
%LabelPlayerKills.text = "Kills"
@@ -260,7 +334,7 @@ func updateScore(playerJoined:bool = false):
var scores = []
for pl in playersNode.get_children():
if "is_player" in pl:
scores.push_back({ "name": pl.stats.character_name, "kills": pl.stats.kills, "deaths": pl.stats.deaths})
scores.push_back({"name": pl.stats.character_name, "kills": pl.stats.kills, "deaths": pl.stats.deaths})
pass
pass
scores.sort_custom(sortScoreArr)
@@ -298,7 +372,7 @@ func updateScore(playerJoined:bool = false):
for sc in scores:
if cnt == 0:
%LabelRoundWinner.text = sc.name + " WINS THE ROUND!"
if cnt != 0 and (scores[cnt].kills < scores[cnt-1].kills or scores[cnt].deaths > scores[cnt-1].deaths):
if cnt != 0 and (scores[cnt].kills < scores[cnt - 1].kills or scores[cnt].deaths > scores[cnt - 1].deaths):
nr += 1
%LabelPlayerNr.text += "\r\n" + str(nr)
%LabelPlayerNames.text += "\r\n" + sc.name

View File

@@ -23,8 +23,40 @@ const JUMP_VELOCITY = -400.0
var held_entity = null
var grabbed_entity = null
@export var held_entity_path: String = "":
set(value):
Console.print("Running player set held_entity_path")
if value != "":
self.held_entity = get_node_or_null(value)
if self.held_entity != null:
Console.print("Client ", multiplayer.get_unique_id(), " synced held_entity to: ", held_entity.name)
else:
Console.print("Client ", multiplayer.get_unique_id(), " failed to find held_entity at path: ", value)
else:
if self.held_entity != null:
Console.print("Client ", multiplayer.get_unique_id(), " about to release held_entity: ", self.held_entity.name)
self.held_entity.release()
self.held_entity = null
Console.print("Client ", multiplayer.get_unique_id(), " cleared held_entity via sync")
held_entity_path = value
@export var grabbed_entity_path: String = "":
set(value):
Console.print("Running player set grabbed_entity_path")
if value != "":
self.grabbed_entity = get_node_or_null(value)
if self.grabbed_entity != null:
self.grabbed_entity.grab(self)
Console.print("Client ", multiplayer.get_unique_id(), " synced grabbed_entity to: ", self.grabbed_entity.name)
else:
Console.print("Client ", multiplayer.get_unique_id(), " failed to find grabbed_entity at path: ", value)
else:
if self.grabbed_entity != null:
self.grabbed_entity.release()
self.grabbed_entity = null
var is_player = true
@export var direction_vector = Vector2(0,0)
@export var direction_vector = Vector2(0, 0)
const ANIMATIONS = {
"IDLE": {
@@ -112,8 +144,8 @@ const ANIMATIONS = {
"nextAnimation": "IDLE"
},
"LIFT": {
"frames": [19,30],
"frameDurations": [70,70],
"frames": [19, 30],
"frameDurations": [70, 70],
"loop": false,
"nextAnimation": "IDLE_HOLD"
},
@@ -124,8 +156,8 @@ const ANIMATIONS = {
"nextAnimation": null
},
"RUN_PUSH": {
"frames": [30,29,30,31],
"frameDurations": [260,260,260,260],
"frames": [30, 29, 30, 31],
"frameDurations": [260, 260, 260, 260],
"loop": true,
"nextAnimation": null
}
@@ -142,14 +174,15 @@ enum Direction {
UP_LEFT = 5
}
@export var direction = Vector2(0,0) # default down
@export var last_direction = Vector2(0,1)
@export var direction = Vector2(0, 0) # default down
@export var last_direction = Vector2(0, 1)
@export var current_direction = Direction.DOWN
var current_frame = 0
var time_since_last_frame = 0.0
@export var current_animation:String = "IDLE":
@export var current_animation: String = "IDLE":
set(iAnimation):
if current_animation != iAnimation:
Console.print("Animation changed from ", current_animation, " to: ", iAnimation)
current_frame = 0
time_since_last_frame = 0
current_animation = iAnimation
@@ -158,8 +191,8 @@ var time_since_last_frame = 0.0
var liftable = true
var is_demo_character = false
var invul_timer:float = 0.0
var invul_duration:float = 2.0
var invul_timer: float = 0.0
var invul_duration: float = 2.0
var knockback_strength: float = 160.0
var knockback_duration: float = 0.4
var knockback_timer: float = 0.0
@@ -177,7 +210,7 @@ var knockback_direction: Vector2 = Vector2.ZERO
@export var is_moving = false
@export var isDemoCharacter = false
@export var stats:CharacterStats = CharacterStats.new()
@export var stats: CharacterStats = CharacterStats.new()
signal player_died
@@ -235,6 +268,7 @@ func _physics_process(delta: float) -> void:
if isDemoCharacter:
pass
else:
# Entity synchronization is now handled by setter functions when paths change
if get_multiplayer_authority() == multiplayer.get_unique_id():
_handleInput()
if stats.hp > 0: # only allow to move if we are alive...
@@ -301,11 +335,12 @@ func _apply_movement_from_input(_delta: float) -> void:
pass
pass
movespeed *= terrainMultiplier
if grabbed_entity != null:
# Only restrict movement when grabbing/pushing entities, not when lifting/holding
if grabbed_entity != null and held_entity == null:
grabMultiplier = 0.3
# set direction to only be last_direction or inverse last_direction
if direction != Vector2.ZERO:
var inverseLastDirection = last_direction*-1
var inverseLastDirection = last_direction * -1
if abs(direction.angle_to(last_direction)) > abs(direction.angle_to(inverseLastDirection)):
direction = inverseLastDirection
else:
@@ -332,15 +367,29 @@ func _apply_movement_from_input(_delta: float) -> void:
pass
if is_attacking:
print("Player ", multiplayer.get_unique_id(), " is attacking, held_entity: ", held_entity)
if grabbed_entity == null and held_entity == null and current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "DIE":
current_frame = 0
time_since_last_frame = 0
current_animation = "SWORD"
if held_entity != null:
print("Player ", multiplayer.get_unique_id(), " throwing held_entity, is_server: ", multiplayer.is_server())
Console.print("Player ", multiplayer.get_unique_id(), " throwing held_entity, is_server: ", multiplayer.is_server())
# throw it:
if multiplayer.is_server():
# Server can throw directly
held_entity.throw(last_direction)
current_animation = "THROW"
held_entity = null
held_entity_path = ""
else:
# Client requests throw from server
print("Player ", multiplayer.get_unique_id(), " requesting throw via RPC")
Console.print("Player ", multiplayer.get_unique_id(), " requesting throw via RPC")
MultiplayerManager.request_throw_pot.rpc_id(1, held_entity.get_path(), multiplayer.get_unique_id(), last_direction)
else:
print("Player ", multiplayer.get_unique_id(), " tried to throw but held_entity is null!")
Console.print("Player ", multiplayer.get_unique_id(), " tried to throw but held_entity is null!")
is_releasing = false
is_grabbing = false
is_lifting = false
@@ -352,6 +401,7 @@ func _apply_movement_from_input(_delta: float) -> void:
if grabbed_entity != null and "release" in grabbed_entity:
grabbed_entity.release()
grabbed_entity = null
grabbed_entity_path = ""
is_releasing = false
is_grabbing = false
use_button_up = false
@@ -359,12 +409,17 @@ func _apply_movement_from_input(_delta: float) -> void:
if is_releasing:
if held_entity != null:
if velocity.x != 0 or velocity.y != 0:
if multiplayer.is_server():
held_entity.throw(last_direction)
held_entity = null
held_entity_path = ""
current_animation = "THROW"
else:
MultiplayerManager.request_throw_pot.rpc_id(1, held_entity.get_path(), multiplayer.get_unique_id(), last_direction)
else:
if held_entity.put_down():
held_entity = null
held_entity_path = ""
if grabbed_entity != null and "release" in grabbed_entity:
grabbed_entity.release()
grabbed_entity = null
@@ -374,17 +429,26 @@ func _apply_movement_from_input(_delta: float) -> void:
is_lifting = false
if held_entity == null and grabbed_entity == null and is_grabbing:
var areas:Array[Area2D] = $Area2DPickup.get_overlapping_areas()
var areas: Array[Area2D] = $Area2DPickup.get_overlapping_areas()
if areas.size() != 0:
for a:Area2D in areas:
for a: Area2D in areas:
# make sure we are looking in direction of the entity
var player_to_pot = (a.get_parent().global_position - self.global_position).normalized()
if player_to_pot.dot(self.last_direction) > 0.78:
if "grab" in a.get_parent() and a.get_parent().grab(self) == true:
var pot = a.get_parent()
if "grab" in pot:
if multiplayer.is_server():
# Server can interact directly
if pot.grab(self) == true:
if held_entity == null:
is_lifting = false
grabbed_entity = a.get_parent()
grabbed_entity = pot
grabbed_entity_path = str(pot.get_path())
current_animation = "IDLE_PUSH"
else:
# Client uses RPC to request from server
if "request_grab_pot" in pot:
pot.request_grab_pot.rpc(pot.get_path(), multiplayer.get_unique_id())
break
pass
@@ -393,9 +457,9 @@ func _apply_movement_from_input(_delta: float) -> void:
pass
if held_entity == null and is_lifting:
var areas:Array[Area2D] = $Area2DPickup.get_overlapping_areas()
var areas: Array[Area2D] = $Area2DPickup.get_overlapping_areas()
if areas.size() != 0:
for a:Area2D in areas:
for a: Area2D in areas:
if "lift" in a.get_parent() and a.get_parent() != self and "liftable" in a.get_parent():
# make sure we are looking in direction of the entity
var player_to_pot = (a.get_parent().global_position - self.global_position).normalized()
@@ -404,9 +468,28 @@ func _apply_movement_from_input(_delta: float) -> void:
grabbed_entity.release()
grabbed_entity = null
is_grabbing = false
held_entity = a.get_parent()
held_entity.lift(self)
var pot = a.get_parent()
held_entity = pot
held_entity_path = str(pot.get_path())
pot.lift(self)
current_animation = "LIFT"
if multiplayer.is_server():
# Server can interact directly
held_entity = pot
#held_entity_path = str(pot.get_path())
#pot.lift(self)
#current_animation = "LIFT"
else:
# Client uses RPC to request from server
Console.print("Client ", multiplayer.get_unique_id(), " calling request_lift_pot RPC for pot at: ", pot.get_path())
Console.print("Client ", multiplayer.get_unique_id(), " pot object: ", pot)
Console.print("Client ", multiplayer.get_unique_id(), " is_server: ", multiplayer.is_server())
Console.print("Client ", multiplayer.get_unique_id(), " about to call RPC to server (peer 1)")
MultiplayerManager.request_lift_pot.rpc_id(1, pot.get_path(), multiplayer.get_unique_id())
Console.print("Client ", multiplayer.get_unique_id(), " RPC call completed")
break # only allow 1 at a time :)
pass
pass
@@ -419,12 +502,17 @@ func _apply_movement_from_input(_delta: float) -> void:
is_lifting = false
elif held_entity != null and is_lifting == false:
if velocity.x != 0 or velocity.y != 0:
if multiplayer.is_server():
held_entity.throw(last_direction)
held_entity = null
held_entity_path = ""
current_animation = "THROW"
else:
MultiplayerManager.request_throw_pot.rpc_id(1, held_entity.get_path(), multiplayer.get_unique_id(), last_direction)
else:
if held_entity.put_down():
held_entity = null
held_entity_path = ""
else:
is_lifting = true
if grabbed_entity != null and "release" in grabbed_entity:
@@ -635,6 +723,7 @@ func sync_player_stats(stats_dict: Dictionary):
# Update visuals
_stats_changed(stats)
@rpc("any_peer", "reliable")
func take_dmg_sound():
$SfxTakeDamage.play()
@@ -656,6 +745,7 @@ func take_damage(iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
if current_animation != "DAMAGE":
time_since_last_frame = 0
current_frame = 0
current_animation = "DAMAGE"
# Calculate knockback direction from the damaging body
@@ -663,7 +753,12 @@ func take_damage(iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
velocity = knockback_direction * knockback_strength
_updateHp()
if held_entity != null:
if multiplayer.is_server():
held_entity.throw(knockback_direction)
else:
MultiplayerManager.request_throw_pot.rpc_id(1, held_entity.get_path(), multiplayer.get_unique_id(), knockback_direction)
held_entity = null
held_entity_path = ""
is_lifting = false
if grabbed_entity != null and "release" in grabbed_entity:
@@ -721,6 +816,14 @@ func sync_player_deaths(iDeaths: int):
MultiplayerManager.updateScore()
pass
@rpc("call_local", "reliable")
func sync_animation(animation_name: String):
#print("Client ", multiplayer.get_unique_id(), " received sync_animation: ", animation_name)
current_animation = animation_name
pass
# RPC functions removed - entity synchronization now handled by setter functions
@rpc("reliable")
func _updateScore():
MultiplayerManager.updateScore()
@@ -735,13 +838,13 @@ func _on_died():
# find spawn point:
var spointPouints = get_parent().get_parent().get_node("PlayerSpawnPoints")
var targetPos = null
for spawnP:Node2D in spointPouints.get_children():
for spawnP: Node2D in spointPouints.get_children():
var pos = spawnP.position
if targetPos == null:
targetPos = pos
else:
# find spawn position which is furthest from all players...
for pl:CharacterBody2D in get_parent().get_children():
for pl: CharacterBody2D in get_parent().get_children():
if "is_player" in pl:
# compare
if pl.position.distance_to(pos) > pl.position.distance_to(targetPos):
@@ -753,7 +856,8 @@ func _on_died():
stats.hp = stats.maxhp
stats.is_invulnerable = false
stats.forceUpdate()
if current_animation != "IDLE":
Console.print("Animation changed from ", current_animation, " to: ", "IDLE")
current_animation = "IDLE"
self.set_collision_layer_value(10, true)
pass
@@ -775,6 +879,15 @@ func not_use():
func grab():
is_grabbing = true
@rpc("any_peer", "reliable")
func set_held_entity_path_rpc(entity_path: String):
held_entity_path = entity_path
@rpc("any_peer", "reliable")
func set_grabbed_entity_path_rpc(entity_path: String):
grabbed_entity_path = entity_path
@rpc("call_local")
func lift():
is_lifting = !is_lifting

View File

@@ -76,6 +76,12 @@ properties/10/replication_mode = 2
properties/11/path = NodePath(".:direction_vector")
properties/11/spawn = true
properties/11/replication_mode = 2
properties/12/path = NodePath(".:held_entity_path")
properties/12/spawn = true
properties/12/replication_mode = 2
properties/13/path = NodePath(".:grabbed_entity_path")
properties/13/spawn = true
properties/13/replication_mode = 2
[sub_resource type="Gradient" id="Gradient_hsjxb"]
offsets = PackedFloat32Array(0.847255, 0.861575)
@@ -300,6 +306,21 @@ theme_override_font_sizes/font_size = 6
text = "Playername"
horizontal_alignment = 1
[node name="LabelCurrentAnimation" type="Label" parent="."]
z_index = 18
z_as_relative = false
offset_left = -29.82
offset_top = -33.945
offset_right = 30.18
offset_bottom = -27.945
size_flags_horizontal = 3
size_flags_vertical = 6
theme_override_constants/outline_size = 6
theme_override_fonts/font = ExtResource("21_pyh4g")
theme_override_font_sizes/font_size = 6
text = "CurAnim"
horizontal_alignment = 1
[node name="CanvasLayer" type="CanvasLayer" parent="."]
follow_viewport_enabled = true

View File

@@ -0,0 +1,356 @@
[gd_scene load_steps=44 format=3 uid="uid://dgtfy455abe1t"]
[ext_resource type="Script" uid="uid://cvvy2s6620mcw" path="res://scripts/entities/player/player.gd" id="1_sgemx"]
[ext_resource type="Texture2D" uid="uid://bkninujaqqvb1" path="res://assets/gfx/Puny-Characters/Layer 0 - Skins/Human1_1.png" id="3_0818e"]
[ext_resource type="Shader" uid="uid://cfd38qf1ojmft" path="res://assets/shaders/cloth.gdshader" id="4_6nxnb"]
[ext_resource type="Script" uid="uid://yid4hjp68enj" path="res://scripts/entities/player/camera_2d.gd" id="4_n1hb6"]
[ext_resource type="Texture2D" uid="uid://dx1fovugabbwc" path="res://assets/gfx/Puny-Characters/Layer 1 - Shoes/IronBoots.png" id="5_2bw0v"]
[ext_resource type="Texture2D" uid="uid://bbqk2lcs772q3" path="res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Armour Body/BronzeArmour.png" id="5_7drg4"]
[ext_resource type="Texture2D" uid="uid://bkiexfnpcaxwa" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Facial Hairstyles/Mustache1White.png" id="7_2bw0v"]
[ext_resource type="Texture2D" uid="uid://0lmhxwt7k3e4" path="res://assets/gfx/Puny-Characters/Layer 5 - Eyes/Eye Color/EyecolorLightLime.png" id="8_68eso"]
[ext_resource type="Texture2D" uid="uid://ccu5cpyo7jpdr" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Hairstyles/MHairstyle8White.png" id="8_pyh4g"]
[ext_resource type="Texture2D" uid="uid://b4vh2v0x58v2f" path="res://assets/gfx/Puny-Characters/Layer 5 - Eyes/Eyelashes/MEyelash1.png" id="9_cvm1n"]
[ext_resource type="Texture2D" uid="uid://jxo0e2x145rs" path="res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Elf Add-ons/ElfEars3.png" id="10_o8aek"]
[ext_resource type="Texture2D" uid="uid://cu5fkio3ajr5i" path="res://assets/gfx/Puny-Characters/Layer 6 - Headgears/French/MusketeerHatPurple.png" id="11_idlgo"]
[ext_resource type="Texture2D" uid="uid://bloqx3mibftjn" path="res://assets/gfx/Puny-Characters/WeaponOverlayer.png" id="12_0818e"]
[ext_resource type="AudioStream" uid="uid://cbio6f0ssxvd6" path="res://assets/audio/sfx/walk/stone/walk_stone_1.wav.mp3" id="14_0818e"]
[ext_resource type="AudioStream" uid="uid://dq1va2882v23v" path="res://assets/audio/sfx/walk/stone/walk_stone_2.wav.mp3" id="15_2bw0v"]
[ext_resource type="AudioStream" uid="uid://dsuf4oa710gi8" path="res://assets/audio/sfx/walk/stone/walk_stone_3.wav.mp3" id="16_pyh4g"]
[ext_resource type="AudioStream" uid="uid://fvhvmxtcq018" path="res://assets/audio/sfx/walk/stone/walk_stone_4.wav.mp3" id="17_jfw4q"]
[ext_resource type="AudioStream" uid="uid://cw74evef8fm0t" path="res://assets/audio/sfx/walk/stone/walk_stone_5.wav.mp3" id="18_fj670"]
[ext_resource type="AudioStream" uid="uid://c43fyqtos11fd" path="res://assets/audio/sfx/walk/stone/walk_stone_6.wav.mp3" id="19_0j5vc"]
[ext_resource type="FontFile" uid="uid://bajcvmidrnc33" path="res://assets/fonts/standard_font.png" id="21_pyh4g"]
[ext_resource type="AudioStream" uid="uid://b4ng0o2en2hkm" path="res://assets/audio/sfx/player/fall_out/player_fall_infinitely-02.wav.mp3" id="22_jfw4q"]
[ext_resource type="AudioStream" uid="uid://bi546r2d771yg" path="res://assets/audio/sfx/player/take_damage/player_damaged_01.wav.mp3" id="23_7puce"]
[ext_resource type="AudioStream" uid="uid://b8trgc0pbomud" path="res://assets/audio/sfx/player/take_damage/player_damaged_02.wav.mp3" id="24_3n1we"]
[ext_resource type="AudioStream" uid="uid://dsnvagvhs152x" path="res://assets/audio/sfx/player/take_damage/player_damaged_03.wav.mp3" id="25_h8vet"]
[ext_resource type="AudioStream" uid="uid://ce51n4tvvflro" path="res://assets/audio/sfx/player/take_damage/player_damaged_04.wav.mp3" id="26_1rlbx"]
[ext_resource type="AudioStream" uid="uid://caclaiagfnr2o" path="res://assets/audio/sfx/player/take_damage/player_damaged_05.wav.mp3" id="27_1sdav"]
[ext_resource type="AudioStream" uid="uid://dighi525ty7sl" path="res://assets/audio/sfx/player/take_damage/player_damaged_06.wav.mp3" id="28_x7koh"]
[ext_resource type="AudioStream" uid="uid://bdhmel5vyixng" path="res://assets/audio/sfx/player/take_damage/player_damaged_07.wav.mp3" id="29_jl8uc"]
[sub_resource type="Gradient" id="Gradient_n1hb6"]
offsets = PackedFloat32Array(0.742243, 0.75179)
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_n1hb6"]
gradient = SubResource("Gradient_n1hb6")
fill = 1
fill_from = Vector2(0.508547, 0.487179)
fill_to = Vector2(0.961538, 0.034188)
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_fgrik"]
properties/0/path = NodePath(".:position")
properties/0/spawn = true
properties/0/replication_mode = 1
properties/1/path = NodePath(".:is_attacking")
properties/1/spawn = true
properties/1/replication_mode = 2
properties/2/path = NodePath(".:is_using")
properties/2/spawn = true
properties/2/replication_mode = 2
properties/3/path = NodePath(".:current_animation")
properties/3/spawn = true
properties/3/replication_mode = 2
properties/4/path = NodePath(".:last_direction")
properties/4/spawn = true
properties/4/replication_mode = 2
properties/5/path = NodePath(".:is_grabbing")
properties/5/spawn = true
properties/5/replication_mode = 2
properties/6/path = NodePath(".:is_lifting")
properties/6/spawn = true
properties/6/replication_mode = 2
properties/7/path = NodePath(".:use_button_up")
properties/7/spawn = true
properties/7/replication_mode = 2
properties/8/path = NodePath(".:use_button_down")
properties/8/spawn = true
properties/8/replication_mode = 2
properties/9/path = NodePath(".:is_moving")
properties/9/spawn = true
properties/9/replication_mode = 2
properties/10/path = NodePath(".:collision_layer")
properties/10/spawn = true
properties/10/replication_mode = 2
properties/11/path = NodePath(".:direction_vector")
properties/11/spawn = true
properties/11/replication_mode = 2
properties/12/path = NodePath(".:held_entity_path")
properties/12/spawn = true
properties/12/replication_mode = 2
properties/13/path = NodePath(".:grabbed_entity_path")
properties/13/spawn = true
properties/13/replication_mode = 2
[sub_resource type="Gradient" id="Gradient_hsjxb"]
offsets = PackedFloat32Array(0.847255, 0.861575)
colors = PackedColorArray(0, 0, 0, 0.611765, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_0818e"]
gradient = SubResource("Gradient_hsjxb")
width = 14
height = 6
fill = 1
fill_from = Vector2(0.504274, 0.478632)
fill_to = Vector2(0.897436, 0.0769231)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_2bw0v"]
shader = ExtResource("4_6nxnb")
shader_parameter/original_0 = Color(0, 0, 0, 1)
shader_parameter/original_1 = Color(0, 0, 0, 1)
shader_parameter/original_2 = Color(0, 0, 0, 1)
shader_parameter/original_3 = Color(0, 0, 0, 1)
shader_parameter/original_4 = Color(0, 0, 0, 1)
shader_parameter/original_5 = Color(0, 0, 0, 1)
shader_parameter/original_6 = Color(0, 0, 0, 1)
shader_parameter/replace_0 = Color(0, 0, 0, 1)
shader_parameter/replace_1 = Color(0, 0, 0, 1)
shader_parameter/replace_2 = Color(0, 0, 0, 1)
shader_parameter/replace_3 = Color(0, 0, 0, 1)
shader_parameter/replace_4 = Color(0, 0, 0, 1)
shader_parameter/replace_5 = Color(0, 0, 0, 1)
shader_parameter/replace_6 = Color(0, 0, 0, 1)
shader_parameter/tint = Color(1, 1, 1, 1)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_8ugno"]
shader = ExtResource("4_6nxnb")
shader_parameter/original_0 = Color(0, 0, 0, 1)
shader_parameter/original_1 = Color(0, 0, 0, 1)
shader_parameter/original_2 = Color(0, 0, 0, 1)
shader_parameter/original_3 = Color(0, 0, 0, 1)
shader_parameter/original_4 = Color(0, 0, 0, 1)
shader_parameter/original_5 = Color(0, 0, 0, 1)
shader_parameter/original_6 = Color(0, 0, 0, 1)
shader_parameter/replace_0 = Color(0, 0, 0, 1)
shader_parameter/replace_1 = Color(0, 0, 0, 1)
shader_parameter/replace_2 = Color(0, 0, 0, 1)
shader_parameter/replace_3 = Color(0, 0, 0, 1)
shader_parameter/replace_4 = Color(0, 0, 0, 1)
shader_parameter/replace_5 = Color(0, 0, 0, 1)
shader_parameter/replace_6 = Color(0, 0, 0, 1)
shader_parameter/tint = Color(1, 1, 1, 1)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_sgemx"]
size = Vector2(8, 6)
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_40ewq"]
streams_count = 6
stream_0/stream = ExtResource("14_0818e")
stream_1/stream = ExtResource("15_2bw0v")
stream_2/stream = ExtResource("16_pyh4g")
stream_3/stream = ExtResource("17_jfw4q")
stream_4/stream = ExtResource("18_fj670")
stream_5/stream = ExtResource("19_0j5vc")
[sub_resource type="RectangleShape2D" id="RectangleShape2D_0818e"]
size = Vector2(10, 8)
[sub_resource type="Gradient" id="Gradient_2bw0v"]
offsets = PackedFloat32Array(0)
colors = PackedColorArray(0, 0, 0, 1)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_pyh4g"]
gradient = SubResource("Gradient_2bw0v")
width = 16
[sub_resource type="Gradient" id="Gradient_jfw4q"]
offsets = PackedFloat32Array(1)
colors = PackedColorArray(1, 0.231947, 0.351847, 1)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_fj670"]
gradient = SubResource("Gradient_jfw4q")
width = 16
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_hnhes"]
streams_count = 7
stream_0/stream = ExtResource("23_7puce")
stream_1/stream = ExtResource("24_3n1we")
stream_2/stream = ExtResource("25_h8vet")
stream_3/stream = ExtResource("26_1rlbx")
stream_4/stream = ExtResource("27_1sdav")
stream_5/stream = ExtResource("28_x7koh")
stream_6/stream = ExtResource("29_jl8uc")
[node name="Player" type="CharacterBody2D"]
collision_layer = 512
collision_mask = 704
script = ExtResource("1_sgemx")
held_entity_path = null
grabbed_entity_path = null
direction_vector = null
direction = null
last_direction = null
current_direction = null
current_animation = null
is_attacking = null
is_using = null
is_grabbing = null
is_lifting = null
is_releasing = null
use_button_down = null
use_button_up = null
attack_button_down = null
attack_button_up = null
is_moving = null
isDemoCharacter = null
[node name="PlayerLight" type="PointLight2D" parent="."]
z_index = 10
position = Vector2(-1, -6)
blend_mode = 2
range_layer_max = 2
texture = SubResource("GradientTexture2D_n1hb6")
[node name="PlayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_fgrik")
[node name="Sprite2DShadow" type="Sprite2D" parent="."]
position = Vector2(0, 2)
texture = SubResource("GradientTexture2D_0818e")
[node name="Sprite2DBody" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_2bw0v")
position = Vector2(0, -5)
texture = ExtResource("3_0818e")
hframes = 35
vframes = 8
[node name="Sprite2DBoots" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("5_2bw0v")
hframes = 35
vframes = 8
[node name="Sprite2DArmour" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("5_7drg4")
hframes = 35
vframes = 8
[node name="Sprite2DFacialHair" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("7_2bw0v")
hframes = 35
vframes = 8
[node name="Sprite2DHair" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("8_pyh4g")
hframes = 35
vframes = 8
[node name="Sprite2DEyes" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("8_68eso")
hframes = 35
vframes = 8
[node name="Sprite2DEyeLashes" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("9_cvm1n")
hframes = 35
vframes = 8
[node name="Sprite2DAddons" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("10_o8aek")
hframes = 35
vframes = 8
[node name="Sprite2DHeadgear" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("11_idlgo")
hframes = 35
vframes = 8
[node name="Sprite2DWeapon" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("12_0818e")
hframes = 35
vframes = 8
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_sgemx")
[node name="Camera2D" type="Camera2D" parent="."]
zoom = Vector2(3, 3)
position_smoothing_enabled = true
script = ExtResource("4_n1hb6")
[node name="Timer" type="Timer" parent="Camera2D"]
[node name="SfxWalk" type="AudioStreamPlayer2D" parent="."]
stream = SubResource("AudioStreamRandomizer_40ewq")
volume_db = -12.0
attenuation = 8.28211
bus = &"Sfx"
[node name="TimerWalk" type="Timer" parent="SfxWalk"]
wait_time = 0.3
one_shot = true
[node name="Area2DPickup" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 1536
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickup"]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_0818e")
debug_color = Color(0.7, 0.495943, 0.135446, 0.42)
[node name="LabelPlayerName" type="Label" parent="."]
z_index = 18
z_as_relative = false
offset_left = -29.82
offset_top = -26.39
offset_right = 30.18
offset_bottom = -20.39
size_flags_horizontal = 3
size_flags_vertical = 6
theme_override_constants/outline_size = 6
theme_override_fonts/font = ExtResource("21_pyh4g")
theme_override_font_sizes/font_size = 6
text = "Playername"
horizontal_alignment = 1
[node name="CanvasLayer" type="CanvasLayer" parent="."]
follow_viewport_enabled = true
[node name="TextureProgressBarHealth" type="TextureProgressBar" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -8.0
offset_top = -16.0
offset_right = 8.0
offset_bottom = -15.0
grow_horizontal = 2
grow_vertical = 2
value = 100.0
texture_under = SubResource("GradientTexture1D_pyh4g")
texture_progress = SubResource("GradientTexture1D_fj670")
[node name="SfxDie" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("22_jfw4q")
bus = &"Sfx"
[node name="SfxTakeDamage" type="AudioStreamPlayer2D" parent="."]
stream = SubResource("AudioStreamRandomizer_hnhes")
bus = &"Sfx"
[node name="TimerGrab" type="Timer" parent="."]
wait_time = 0.2
one_shot = true
[connection signal="timeout" from="Camera2D/Timer" to="Camera2D" method="_on_timer_timeout"]

View File

@@ -8,8 +8,14 @@ var entity_id = ""
var object_name = "bomb"
@export var is_being_thrown = false
@export var is_being_lifted = false
@export var is_being_put_down = false
@export var is_being_lifted = false:
set(value):
is_being_lifted = value
Console.print("Pot is_being_lifted changed to: ", value, " on peer: ", multiplayer.get_unique_id())
@export var is_being_put_down = false:
set(value):
is_being_put_down = value
Console.print("Pot is_being_put_down changed to: ", value, " on peer: ", multiplayer.get_unique_id())
@export var is_being_grabbed = false
@export var is_moving = false
@export var is_spawning = false
@@ -44,6 +50,24 @@ var re_enable_time = 0.17
var previousFrameVel = Vector2.ZERO
var hasShownSmokePuffs = false
@export var holder_peer_id: int = 0:
set(value):
holder_peer_id = value
Console.print("Pot holder_peer_id changed to: ", value, " on peer: ", multiplayer.get_unique_id())
# Clear lifting state when holder is cleared, but only if we're not in the middle of lifting
if value == 0 and !is_being_lifted:
is_being_lifted = false
holder = null
elif value != 0:
# Find the holder by peer ID
var spawn_root = get_tree().get_current_scene().get_node("SpawnRoot")
if spawn_root:
holder = spawn_root.get_node_or_null(str(value))
if holder:
Console.print("Pot found holder: ", holder.name, " for peer ID: ", value)
else:
Console.print("Pot failed to find holder for peer ID: ", value)
func _ready() -> void:
if is_spawning:
liftable = false
@@ -58,6 +82,51 @@ func _ready() -> void:
pass
func _physics_process(delta: float) -> void:
# Update holder based on holder_peer_id for network sync
if holder_peer_id != 0:
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(holder_peer_id))
if player and holder != player:
Console.print("Pot updating holder from ", holder, " to ", player.name)
holder = player
# Ensure lifting state is synced when holder changes
if !is_being_lifted and !is_being_grabbed:
is_being_lifted = true
lift_progress = 0.0
elif !player:
Console.print("Pot could not find player with peer_id: ", holder_peer_id)
elif holder_peer_id == 0 and holder != null:
Console.print("Pot clearing holder (holder_peer_id is 0)")
holder = null
is_being_lifted = false
# Handle lifted pot position on ALL clients for smooth following
if is_being_lifted and holder:
$GPUParticles2D.emitting = false
if lift_progress < 1.0:
lift_progress += delta * lift_speed
lift_progress = min(lift_progress, 1.0)
# Smoothly interpolate from current position to above holder during lifting
var target_pos = holder.global_position + Vector2(0, -8)
if lift_progress < 1.0:
global_position = global_position.lerp(target_pos, lift_progress)
positionZ = lift_height * lift_progress
else:
# When fully lifted, maintain exact position above holder
global_position = target_pos
positionZ = lift_height
else:
# Debug: Check why pot is not following
if is_being_lifted and !holder:
Console.print("Pot is_being_lifted but holder is null! holder_peer_id: ", holder_peer_id)
# Fix inconsistent state
if holder_peer_id == 0:
is_being_lifted = false
elif !is_being_lifted and holder:
# Pot has holder but is_being_lifted is false - this is normal during transitions
pass
update_sprite_scale()
if multiplayer.is_server():
if is_being_thrown:
re_enable_collision_after_time -= delta
@@ -81,7 +150,7 @@ func _physics_process(delta: float) -> void:
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if abs(velocityZ) > minBounceVelocity:
velocityZ = -velocityZ * bounceRestitution
velocityZ = - velocityZ * bounceRestitution
else:
velocityZ = 0
is_being_thrown = false
@@ -117,23 +186,7 @@ func _physics_process(delta: float) -> void:
global_position = put_down_start_pos.lerp(put_down_target_pos, 1.0 - lift_progress)
positionZ = lift_height * lift_progress
update_sprite_scale()
elif is_being_lifted:
# Smooth lifting animation
if holder:
$GPUParticles2D.emitting = false
target_position = holder.global_position
#target_position.y -= 2
if lift_progress < 1.0:
lift_progress += delta * lift_speed
lift_progress = min(lift_progress, 1.0)
global_position = global_position.lerp(target_position, lift_progress)
positionZ = lift_height * lift_progress
else:
lift_progress = 1.0
global_position = target_position
positionZ = lift_height
update_sprite_scale()
pass
elif is_being_grabbed:
#if velocity != Vector2.ZERO and velocity != previousFrameVel and hasShownSmokePuffs == false:
if velocity != Vector2.ZERO:
@@ -150,7 +203,7 @@ func _physics_process(delta: float) -> void:
move_and_collide(velocity * delta)
previousFrameVel = velocity
pass
else: # it just spawned:
elif !is_being_lifted: # it just spawned or is free-falling:
# Apply gravity to vertical movement
velocityZ += accelerationZ * delta
positionZ += velocityZ * delta
@@ -171,7 +224,7 @@ func _physics_process(delta: float) -> void:
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if abs(velocityZ) > minBounceVelocity:
velocityZ = -velocityZ * bounceRestitution
velocityZ = - velocityZ * bounceRestitution
else:
velocityZ = 0
velocity = velocity.lerp(Vector2.ZERO, 0.5)
@@ -203,6 +256,19 @@ func _physics_process(delta: float) -> void:
if $SfxDrag2.playing:
$SfxDrag2.stop()
$GPUParticles2D.emitting = false
# Update position on client to follow holder
if holder:
target_position = holder.global_position + Vector2(0, -8)
if lift_progress < 1.0:
lift_progress += delta * lift_speed
lift_progress = min(lift_progress, 1.0)
global_position = global_position.lerp(target_position, lift_progress)
positionZ = lift_height * lift_progress
else:
# When fully lifted, maintain exact position above holder
global_position = target_position
positionZ = lift_height
update_sprite_scale()
elif is_being_grabbed:
if is_moving:
$GPUParticles2D.emitting = true
@@ -224,6 +290,10 @@ func _physics_process(delta: float) -> void:
if $SfxDrag2.playing:
$SfxDrag2.stop()
pass
# Fix stuck put_down state
if is_being_put_down and lift_progress <= 0:
Console.print("Pot is_being_put_down but lift_progress is 0, clearing state")
is_being_put_down = false
update_sprite_scale()
if is_destroyed and !destroy_initiated:
destroy_initiated = true
@@ -238,7 +308,7 @@ func update_sprite_scale() -> void:
var posY = positionZ # Direct mapping of Z to Y offset
var sc = 1.0 + (0.1 * height_factor) # Slightly less scale change than loot (0.3 instead of 0.8)
$Sprite2D.scale = Vector2(sc, sc)
$Sprite2D.offset.y = -posY
$Sprite2D.offset.y = - posY
# Also update shadow position and scale
if is_being_lifted:
$Sprite2D.z_as_relative = false
@@ -249,7 +319,7 @@ func update_sprite_scale() -> void:
$Sprite2D.z_index = 0
$Sprite2DShadow.offset.y = 0
#$Sprite2DShadow.scale = Vector2(1.125 * sc, 0.5 * sc) # Scale shadow with height
$Sprite2DShadow.scale = Vector2(1,1)
$Sprite2DShadow.scale = Vector2(1, 1)
#$Sprite2DShadow.modulate.
func throw(direction: Vector2, initial_velocity: float = 200):
@@ -262,23 +332,34 @@ func throw(direction: Vector2, initial_velocity: float = 200):
is_being_put_down = false
thrown_by = holder
holder = null
holder_peer_id = 0 # Clear the network holder reference
velocity = direction * initial_velocity
velocityZ = throw_height
positionZ = lift_height
current_height = 0
re_enable_collision_after_time = re_enable_time
@rpc("any_peer", "reliable")
func throw_rpc(direction: Vector2, initial_velocity: float = 200):
Console.print("Pot throw_rpc called on peer: ", multiplayer.get_unique_id())
# Only execute on server to avoid conflicts
if multiplayer.is_server():
throw(direction, initial_velocity)
func grab(new_holder: CharacterBody2D) -> bool:
if positionZ <= 0 and holder == null: # only allow grab if no previous owner and position is 0
$GPUParticles2D/TimerSmokeParticles.stop() #reset...
$GPUParticles2D/TimerSmokeParticles.stop() # reset...
holder = new_holder
holder_peer_id = new_holder.get_multiplayer_authority()
is_being_grabbed = true
indicate(false)
return true
return false
func release():
Console.print("Pot release() called on peer: ", multiplayer.get_unique_id())
holder = null
holder_peer_id = 0
is_being_grabbed = false
hasShownSmokePuffs = false
indicate(true)
@@ -287,6 +368,8 @@ func release():
pass
func lift(new_holder: CharacterBody2D):
Console.print("Pot lift() called with holder: ", new_holder.name if new_holder else "null")
Console.print("Pot current authority: ", get_multiplayer_authority())
if (new_holder != holder and holder != null) and "lose_held_entity" in holder:
# steal from holder
holder.lose_held_entity(self)
@@ -294,6 +377,7 @@ func lift(new_holder: CharacterBody2D):
$Area2DCollision.set_deferred("monitoring", false)
thrown_by = null
holder = new_holder
holder_peer_id = new_holder.get_multiplayer_authority()
# disable collisions
self.set_collision_layer_value(8, false)
self.set_collision_mask_value(7, false)
@@ -306,7 +390,23 @@ func lift(new_holder: CharacterBody2D):
is_being_put_down = false
lift_progress = 0.0
velocityZ = 0
# Store initial position for smooth lifting - don't change current position yet
# The pot will smoothly glide from its current position to above the holder
$SfxLand.play()
Console.print("Pot lift() completed, is_being_lifted: ", is_being_lifted)
@rpc("any_peer", "reliable")
func lift_rpc(holder_path: NodePath):
Console.print("Pot lift_rpc called with holder_path: ", holder_path, " on peer: ", multiplayer.get_unique_id())
# Only execute on server to avoid conflicts
if multiplayer.is_server():
# Find the holder by path
var holder_node = get_node_or_null(holder_path)
if holder_node and holder_node is CharacterBody2D:
Console.print("Pot found holder, calling lift()")
lift(holder_node)
else:
Console.print("Pot failed to find holder at path: ", holder_path)
func put_down() -> bool:
if not is_being_lifted or is_being_put_down:
@@ -365,13 +465,13 @@ func put_down() -> bool:
put_down_start_pos = global_position
thrown_by = null
holder = null
holder_peer_id = 0
indicate(true)
return true
func remove():
var fade_tween = create_tween()
fade_tween.set_trans(Tween.TRANS_CUBIC)
fade_tween.set_ease(Tween.EASE_OUT)
@@ -442,7 +542,6 @@ func _on_area_2d_collision_body_entered(body: Node2D) -> void:
if is_being_thrown == false or body == self or body == thrown_by:
return
if multiplayer.is_server():
var collision_shape = $Area2DCollision.get_overlapping_bodies()
if collision_shape.size() > 0:
@@ -477,6 +576,55 @@ func show_destroy_effect():
self.call_deferred("remove")
pass
@rpc("any_peer", "reliable")
func request_grab_pot(pot_path: NodePath, peer_id: int):
if multiplayer.is_server():
var pot = get_node_or_null(pot_path)
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(peer_id))
if pot and "grab" in pot and player:
if pot.grab(player):
player.grabbed_entity = pot
player.grabbed_entity_path = str(pot.get_path())
player.current_animation = "IDLE_PUSH"
# Sync animation and entity to all clients
#var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()
#for p in all_players:
#if p.has_method("sync_animation"):
#p.sync_animation.rpc("IDLE_PUSH")
#p.sync_grabbed_entity.rpc(str(pot.get_path()))
@rpc("any_peer", "reliable")
func request_lift_pot(pot_path: NodePath, peer_id: int):
# This function is now handled by MultiplayerManager
# Keeping it for backward compatibility but it should not be called
print("Pot received request_lift_pot RPC - this should be handled by MultiplayerManager")
pass
@rpc("any_peer", "reliable")
func request_throw_pot(pot_path: NodePath, peer_id: int, direction: Vector2):
if multiplayer.is_server():
var pot = get_node_or_null(pot_path)
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(peer_id))
print("Throw request: pot=", pot, " player=", player, " pot.holder_peer_id=", pot.holder_peer_id if pot else "null", " peer_id=", peer_id)
if pot and player:
# Check if the pot is being held by this player (either by holder_peer_id or by checking the holder directly)
if pot.holder_peer_id == peer_id or (pot.holder != null and pot.holder.get_multiplayer_authority() == peer_id):
print("Throw authorized for peer ", peer_id)
pot.throw(direction)
player.held_entity = null
player.held_entity_path = ""
player.current_animation = "THROW"
# Sync pot state to all clients first
pot.sync_pot_state.rpc(false, 0) # Not lifted, no holder
# Sync animation and clear held entity to all clients
var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()
for p in all_players:
if p.has_method("sync_animation"):
p.sync_animation.rpc("THROW")
p.sync_held_entity.rpc("") # Clear held entity
else:
print("Throw denied: holder_peer_id mismatch or holder not found")
@rpc("call_local")
func take_damage(_iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
is_destroyed = true # will trigger show_destroy_effect for clients...
@@ -490,6 +638,21 @@ func take_damage(_iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
self.set_collision_mask_value(10, false)
pass
@rpc("call_local", "reliable")
func sync_pot_state(lifted: bool, holder_id: int):
is_being_lifted = lifted
holder_peer_id = holder_id
if holder_peer_id != 0:
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(holder_peer_id))
if player:
holder = player
else:
holder = null
else:
holder = null
print("Pot state synced: lifted=", lifted, " holder_id=", holder_id)
pass
func _on_area_2d_collision_body_exited(_body: Node2D) -> void:
pass # Replace with function body.

View File

@@ -31,7 +31,7 @@ properties/0/spawn = true
properties/0/replication_mode = 2
properties/1/path = NodePath(".:positionZ")
properties/1/spawn = true
properties/1/replication_mode = 2
properties/1/replication_mode = 1
properties/2/path = NodePath(".:is_being_thrown")
properties/2/spawn = true
properties/2/replication_mode = 2
@@ -62,6 +62,9 @@ properties/10/replication_mode = 2
properties/11/path = NodePath(".:is_spawning")
properties/11/spawn = true
properties/11/replication_mode = 2
properties/12/path = NodePath(".:holder_peer_id")
properties/12/spawn = true
properties/12/replication_mode = 2
[sub_resource type="Gradient" id="Gradient_nb533"]
offsets = PackedFloat32Array(0.847255, 0.861575)

View File

@@ -17,23 +17,23 @@ var current_character_stats = CharacterStats.new()
func _ready() -> void:
RenderingServer.set_default_clear_color(Color(0, 0, 0, 1.0))
var pPanel:PopupPanel = $ControlCharacterInfo/MarginContainer/MarginContainer/VBoxContainer/TabContainer/Hair/HBoxContainerFacial/PickerButtonColorFacial.get_popup()
pPanel.max_size = Vector2i(80,120)
var cPicker:ColorPicker = $ControlCharacterInfo/MarginContainer/MarginContainer/VBoxContainer/TabContainer/Hair/HBoxContainerFacial/PickerButtonColorFacial.get_picker()
var pPanel: PopupPanel = $ControlCharacterInfo/MarginContainer/MarginContainer/VBoxContainer/TabContainer/Hair/HBoxContainerFacial/PickerButtonColorFacial.get_popup()
pPanel.max_size = Vector2i(80, 120)
var cPicker: ColorPicker = $ControlCharacterInfo/MarginContainer/MarginContainer/VBoxContainer/TabContainer/Hair/HBoxContainerFacial/PickerButtonColorFacial.get_picker()
cPicker.hex_visible = true
cPicker.sliders_visible = true
cPicker.can_add_swatches = false
cPicker.presets_visible = false
cPicker.scale = Vector2(0.24,0.24)
cPicker.scale = Vector2(0.24, 0.24)
var pPanel2:PopupPanel = $ControlCharacterInfo/MarginContainer/MarginContainer/VBoxContainer/TabContainer/Hair/HBoxContainerHair/PickerButtonColorHair.get_popup()
pPanel2.max_size = Vector2i(80,120)
var cPicker2:ColorPicker = $ControlCharacterInfo/MarginContainer/MarginContainer/VBoxContainer/TabContainer/Hair/HBoxContainerHair/PickerButtonColorHair.get_picker()
var pPanel2: PopupPanel = $ControlCharacterInfo/MarginContainer/MarginContainer/VBoxContainer/TabContainer/Hair/HBoxContainerHair/PickerButtonColorHair.get_popup()
pPanel2.max_size = Vector2i(80, 120)
var cPicker2: ColorPicker = $ControlCharacterInfo/MarginContainer/MarginContainer/VBoxContainer/TabContainer/Hair/HBoxContainerHair/PickerButtonColorHair.get_picker()
cPicker2.hex_visible = true
cPicker2.sliders_visible = true
cPicker2.can_add_swatches = false
cPicker2.presets_visible = false
cPicker2.scale = Vector2(0.24,0.24)
cPicker2.scale = Vector2(0.24, 0.24)
$ControlSelectCharacter/VBoxContainer/ButtonPlay.visible = false
@@ -75,7 +75,7 @@ func loadCharDatas():
characters.append(char_data)
pass
func getSaveData(iSaveFile:String) -> Dictionary:
func getSaveData(iSaveFile: String) -> Dictionary:
var savePath = "user://" + save_path + iSaveFile
if not FileAccess.file_exists(savePath):
return {}
@@ -97,15 +97,13 @@ func update_character_display() -> void:
$ControlYourCharacters/MarginContainer2/MarginContainer/VBoxContainerCharacters/ScrollContainer/VBoxContainer.add_child(charBut)
charBut.set_data(chara)
charBut.connect("pressed", _pressCharBut.bind(charBut, curIndex))
curIndex+=1
curIndex += 1
pass
$ControlYourCharacters/MarginContainer2/MarginContainer/VBoxContainerCharacters/ScrollContainer.queue_redraw()
$ControlYourCharacters/MarginContainer2/MarginContainer/VBoxContainerCharacters/ScrollContainer/VBoxContainer.queue_redraw()
pass
func checkSaves():
pass
func _input(event: InputEvent):
@@ -113,7 +111,7 @@ func _input(event: InputEvent):
var isPressed = event.is_pressed()
if isMouseButton and isPressed and event.button_index == 1:
var evLocal = make_input_local(event)
if !Rect2(Vector2(0,0), size).has_point(evLocal.position):
if !Rect2(Vector2(0, 0), size).has_point(evLocal.position):
release_focus()
@@ -122,10 +120,10 @@ func _on_line_edit_text_changed(new_text: String) -> void:
demoCharacter.initStats(demoCharacter.stats)
pass # Replace with function body.
func _pressCharBut(iCharBut:Button, iIndex:int) -> void:
func _pressCharBut(iCharBut: Button, iIndex: int) -> void:
$ControlSelectCharacter/VBoxContainer/ButtonPlay.visible = true
$ControlCharacterInfo.visible = false
var polayer = iCharBut.find_child("Player")
var _polayer = iCharBut.find_child("Player")
current_character_stats = iCharBut.find_child("Player").stats
current_slot = iIndex
demoCharacter.initStats(current_character_stats)
@@ -187,7 +185,7 @@ func _on_button_save_pressed() -> void:
if child.current_character_stats.character_name == current_character_stats.character_name:
current_slot = cnt
break
cnt+=1
cnt += 1
demoCharacter.initStats(current_character_stats)
demoCharacter._stats_changed(current_character_stats)
@@ -253,7 +251,6 @@ func _on_h_slider_ears_value_changed(value: float) -> void:
func _on_color_picker_button_picker_created() -> void:
pass # Replace with function body.
@@ -264,4 +261,7 @@ func _on_picker_button_color_facial_color_changed(color: Color) -> void:
func _on_picker_button_color_hair_color_changed(color: Color) -> void:
current_character_stats.setHairColor(color)
pass # Replace with function body.
pass
func get_current_character_stats() -> CharacterStats:
return current_character_stats # Replace with function body.

View File

@@ -4,7 +4,7 @@
[ext_resource type="PackedScene" uid="uid://dgtfy455abe1t" path="res://scripts/entities/player/player.tscn" id="4_5axoa"]
[ext_resource type="PackedScene" uid="uid://dgniqbtakal50" path="res://scripts/ui/button_select_char.tscn" id="7_bft24"]
[node name="CharacterSelect" type="Control"]
[node name="CharacterSelect" type="Control" groups=["character_select"]]
z_index = 26
layout_mode = 3
anchors_preset = 15

View File

@@ -25,15 +25,17 @@ func _connectSuccess():
pass
func _on_button_host_pressed() -> void:
self.visible = false
$CanvasLayer.visible = false
Console.print("I am host")
MultiplayerManager.host()
$CanvasLayer.visible = false
self.visible = false
#$CanvasLayer/CharacterSelect.queue_free()
pass # Replace with function body.
func _on_button_join_pressed() -> void:
self.visible = false
$CanvasLayer.visible = false
Console.print("I am joiner")
MultiplayerManager.join()
$CanvasLayer.visible = false
self.visible = false
pass # Replace with function body.