replace with multiplayer-coop files

This commit is contained in:
2026-01-08 16:47:52 +01:00
parent 1725c615ce
commit 22c7025ac4
1230 changed files with 20555 additions and 17232 deletions

View File

@@ -1,48 +0,0 @@
extends Node2D
var auto_scroll_enabled := true
@onready var lbl_text = $CanvasLayer/ColorRectBG/VBoxContainer/ScrollContainer/LabelText
@onready var sb = null
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
sb = lbl_text.get_v_scroll_bar()
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
lbl_text.text += "\n" + line
if auto_scroll_enabled:
lbl_text.scroll_to_line(lbl_text.get_line_count() - 1)
func _on_label_text_gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index in [MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN]:
auto_scroll_enabled = sb.value + sb.page >= sb.max_value - 10.0
elif event is InputEventMouseButton and event.is_pressed():
auto_scroll_enabled = sb.value + sb.page >= sb.max_value - 10.0
elif event is InputEventMouseMotion and (event.button_mask & MOUSE_BUTTON_LEFT):
# Only update when dragging with left mouse button
auto_scroll_enabled = sb.value + sb.page >= sb.max_value - 10.0
pass # Replace with function body.

View File

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

View File

@@ -1,54 +0,0 @@
[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
[connection signal="gui_input" from="CanvasLayer/ColorRectBG/VBoxContainer/ScrollContainer/LabelText" to="." method="_on_label_text_gui_input"]

View File

@@ -1,3 +0,0 @@
[gd_scene format=3 uid="uid://sc01ppjgkfew"]
[node name="GameManager" type="Node2D"]

View File

@@ -1,412 +0,0 @@
extends Node2D
# Add at the top with other variables
class ChatMessage:
var text: String
var timestamp: Dictionary
var type: String # "chat", "connection", "system"
func _init(msg: String, typ: String):
text = msg
timestamp = Time.get_datetime_dict_from_system()
type = typ
func format_timestamp() -> String:
return "%02d:%02d:%02d" % [timestamp.hour, timestamp.minute, timestamp.second]
var chat_history: Array[ChatMessage] = []
var show_timestamps: bool = false
const MAX_CHAT_HISTORY = 10
# Add at the top with other variables
var connection_label_timer: Timer
var connection_label_tween: Tween
var character_data: CharacterStats = null
var SERVER_PORT = 21212
var SERVER_HOST = "ruinborn.thefirstboss.com"
var hasMultiplayerInitiated = false
# Add these variables at the top
#var tcp_server: TCPServer = TCPServer.new()
# Add at top with other variables
#var websocket = WebSocketPeer.new()
# Track which peers are ready for entity sync
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 finished_hosting()
signal client_ready_for_players(peer_id: int)
signal connectionFailed
signal connectionSucceeded
signal countdownFinished
func _ready():
connection_label_timer = Timer.new()
connection_label_timer.one_shot = true
connection_label_timer.wait_time = 5.0
connection_label_timer.timeout.connect(_on_connection_label_timeout)
add_child(connection_label_timer)
$CanvasLayer/ControlCenter/MarginContainerForScore.visible = false # hide on start
$CanvasLayer/ControlCountdown.visible = false
%LabelRoundWinner.visible = false
pass
func _on_connection_label_timeout():
# Create tween for fading out
if connection_label_tween:
connection_label_tween.kill()
connection_label_tween = create_tween()
connection_label_tween.tween_property(
$CanvasLayer/BottomLeftCorner/VBoxContainer/LabelPlayerConnect,
"modulate:a",
0.0,
1.0
)
func _physics_process(_delta: float) -> void:
if %LabelRoundWinner.visible == false and Input.is_action_just_pressed("ui_focus_next"):
$CanvasLayer/ControlCenter/MarginContainerForScore.visible = !$CanvasLayer/ControlCenter/MarginContainerForScore.visible
pass
pass
func host():
#var server_peer = ENetMultiplayerPeer.new()
var server_peer = WebSocketMultiplayerPeer.new()
server_peer.create_server(SERVER_PORT)
#multiplayer.allow_object_decoding = true
multiplayer.multiplayer_peer = server_peer
multiplayer.peer_connected.connect(_add_player_to_game)
multiplayer.peer_disconnected.connect(_del_player)
_add_player_to_game(multiplayer.get_unique_id())
emit_signal("finished_hosting")
hasMultiplayerInitiated = true
pass
func join():
#var client_peer = ENetMultiplayerPeer.new()
#client_peer.create_client(SERVER_HOST, 443)
var client_peer = WebSocketMultiplayerPeer.new()
var _error = OK
if not OS.has_feature("pc"):
_error = client_peer.create_client("wss://" + SERVER_HOST + ":" + str(443))
else:
_error = client_peer.create_client("ws://" + SERVER_HOST + ":" + str(SERVER_PORT))
#multiplayer.allow_object_decoding = true
multiplayer.multiplayer_peer = client_peer
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
# Wait for connection
var max_attempts = 10
var attempts = 0
while client_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTING:
attempts += 1
if attempts >= max_attempts:
#_cleanup_connections()
connectionFailed.emit()
return
await get_tree().create_timer(1.0).timeout
if client_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
connectionSucceeded.emit()
hasMultiplayerInitiated = true
pass
func _add_player_to_game(id: int):
emit_signal("addPlayerSignal", id)
pass
func _del_player(id: int):
emit_signal("delPlayerSignal", id)
pass
@rpc("any_peer", "reliable")
func notify_client_ready(peer_id: int):
if multiplayer.is_server():
peers_ready_for_sync[peer_id] = true
client_ready_for_players.emit(peer_id)
# Send host's character data to the new client
sync_character_data.rpc_id(peer_id, multiplayer.get_unique_id(), character_data.save())
# Send all other peers' character data to the new client
for other_peer_id in peers_character_data:
sync_character_data.rpc_id(peer_id, other_peer_id, peers_character_data[other_peer_id])
else:
# Client sends its character data to server
sync_character_data.rpc_id(1, multiplayer.get_unique_id(), character_data.save())
@rpc("any_peer", "reliable")
func sync_character_data(peer_id: int, char_data: Dictionary):
if multiplayer.is_server():
# Store the character data
peers_character_data[peer_id] = char_data
# Broadcast this character data to all other connected peers
for other_peer in multiplayer.get_peers():
if other_peer != peer_id: # Don't send back to the sender
sync_character_data.rpc_id(other_peer, peer_id, char_data)
var playersNode = get_tree().current_scene.get_node("SpawnRoot")
# Update the player's stats locally
var player = playersNode.get_node_or_null(str(peer_id))
if player:
player.stats.load(char_data)
player.initStats(player.stats)
updateScore(true)
func _on_peer_connected(_peer_id: int):
#add_chat_message("Player " + str(peer_id) + " connected", "connection")
add_chat_message("Player " + str(_peer_id) + " connected", "connection")
$CanvasLayer/BottomLeftCorner/VBoxContainer/LabelPlayerConnect.text = "Player " + str(_peer_id) + " connected"
$CanvasLayer/BottomLeftCorner/VBoxContainer/LabelPlayerConnect.modulate.a = 1.0
# Reset and start the timer
if connection_label_tween:
connection_label_tween.kill()
connection_label_timer.start()
# Tell server we're ready for sync
notify_client_ready(multiplayer.get_unique_id()) # run locally also
notify_client_ready.rpc_id(1, multiplayer.get_unique_id())
pass
func _on_peer_disconnected(peer_id: int):
add_chat_message("Player " + str(peer_id) + " disconnected", "connection")
pass
func add_chat_message(message: String, type: String = "chat"):
var chat_msg = ChatMessage.new(message, type)
chat_history.append(chat_msg)
# Keep only last MAX_CHAT_HISTORY messages
if chat_history.size() > MAX_CHAT_HISTORY:
chat_history.pop_front()
update_chat_display()
func update_chat_display():
var display_text = ""
for msg in chat_history:
if show_timestamps:
display_text += "[%s] " % msg.format_timestamp()
display_text += msg.text + "\n"
$CanvasLayer/BottomLeftCorner/VBoxContainer/LabelPlayerConnect.text = display_text.strip_edges()
$CanvasLayer/BottomLeftCorner/VBoxContainer/LabelPlayerConnect.modulate.a = 1.0
# Reset and start the fade timer
if connection_label_tween:
connection_label_tween.kill()
connection_label_timer.start()
# Add RPC for chat messages between players
@rpc("any_peer", "reliable")
func send_chat_message(message: String):
var sender_id = multiplayer.get_remote_sender_id()
add_chat_message("Player %d: %s" % [sender_id, message], "chat")
# Function to send a chat message
func broadcast_chat_message(message: String):
if multiplayer.multiplayer_peer != null:
send_chat_message.rpc(message)
# 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):
if multiplayer.is_server():
var entity = get_node_or_null(pot_path)
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(peer_id))
if entity and player:
# Check if entity is a pot or a player
var is_pot = "lift" in entity and "liftable" in entity and entity.liftable
var is_player_entity = "is_player" in entity and entity.is_player and not entity.is_being_lifted
if (is_pot or is_player_entity):
# Clear grabbed entity if it's not the entity we're lifting
if player.grabbed_entity != null and player.grabbed_entity != entity and "release" in player.grabbed_entity:
player.grabbed_entity.release()
if player.grabbed_entity != entity:
player.grabbed_entity = null
player.is_grabbing = false
# DON'T clear is_lifting - it should stay true while holding the pot
# is_lifting will be cleared when player releases button or throws
# Set held_entity_path directly on server, use RPC for clients
player.held_entity_path = str(entity.get_path())
# Send RPC to the requesting player (joiner) to sync held_entity_path
player.set_held_entity_path_rpc.rpc(str(entity.get_path()))
# Send RPC to all other players (excluding server and requesting player)
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(entity.get_path()))
# Call lift on the entity (works for both pot and player)
if "lift" in entity:
entity.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")
@rpc("any_peer", "reliable")
func request_put_down_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 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):
if pot.put_down():
player.held_entity = null
# Use RPC to clear held_entity_path on all players
player.set_held_entity_path_rpc.rpc("")
player.is_lifting = false
@rpc("any_peer", "reliable")
func request_throw_pot(pot_path: NodePath, peer_id: int, direction: Vector2):
if multiplayer.is_server():
var entity = get_node_or_null(pot_path)
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(peer_id))
if entity and player:
# Check if the entity (pot or player) is being held by this player
var is_held = false
if "holder_peer_id" in entity:
is_held = entity.holder_peer_id == peer_id or (entity.holder != null and entity.holder.get_multiplayer_authority() == peer_id)
elif "is_being_lifted" in entity and entity.is_being_lifted:
is_held = entity.holder_peer_id == peer_id or (entity.holder != null and entity.holder.get_multiplayer_authority() == peer_id)
if is_held and "throw" in entity:
entity.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"
# Entity 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")
func sortScoreArr(a, b):
if a.kills > b.kills:
return true # More kills should come first
elif a.kills == b.kills:
return a.deaths < b.deaths # Fewer deaths should come first
return false # Otherwise, b comes first
var previousScores = []
func updateScore(playerJoined: bool = false):
%LabelPlayerNr.text = "#"
%LabelPlayerNames.text = "Player"
%LabelPlayerKills.text = "Kills"
%LabelPlayerDeaths.text = "Deaths"
var playersNode = get_tree().current_scene.get_node("SpawnRoot")
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})
pass
pass
scores.sort_custom(sortScoreArr)
# play sfx depending on score.
if !playerJoined and previousScores.size() != 0:
# check if you were leading
if previousScores[0].name == MultiplayerManager.character_data.character_name:
# you were previously leading, but no longer
if scores[0].name != previousScores[0].name:
$SfxLostTheLead.play()
pass
else:
# still in lead
if previousScores.size() > 1:
if previousScores[0].kills == previousScores[1].kills and previousScores[0].deaths == previousScores[1].deaths:
if scores.size() > 1:
if scores[0].kills > scores[1].kills or scores[0].deaths < scores[1].deaths:
$SfxTakenTheLead.play()
pass
pass
pass
else:
# you were NOT previously leading
if scores[0].name == MultiplayerManager.character_data.character_name:
# you have taken the lead!
$SfxTakenTheLead.play()
pass
pass
pass
var nr = 1
var cnt = 0
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):
nr += 1
%LabelPlayerNr.text += "\r\n" + str(nr)
%LabelPlayerNames.text += "\r\n" + sc.name
%LabelPlayerKills.text += "\r\n" + str(sc.kills)
%LabelPlayerDeaths.text += "\r\n" + str(sc.deaths)
cnt += 1
pass
previousScores = scores
pass
func start_round():
$CanvasLayer/ControlCountdown.visible = true
$CanvasLayer/ControlCountdown/LabelCountdown/AnimationPlayer.play("countdown")
await $CanvasLayer/ControlCountdown/LabelCountdown/AnimationPlayer.animation_finished
$CanvasLayer/ControlCountdown.visible = false
emit_signal("countdownFinished")
pass
@rpc("call_local", "reliable")
func round_finished():
if previousScores.size() != 0 and previousScores[0].name == MultiplayerManager.character_data.character_name:
$SfxWinner.play()
%LabelRoundWinner.visible = true
$CanvasLayer/ControlCenter/MarginContainerForScore.visible = true
# reset score
# Broadcast this character data to all other connected peers
pass
func new_round_started():
previousScores = [] # reset scores...
%LabelRoundWinner.visible = false
$CanvasLayer/ControlCenter/MarginContainerForScore.visible = false
if multiplayer.is_server():
var playersNode = get_tree().current_scene.get_node("SpawnRoot")
for pl in playersNode.get_children():
if "is_player" in pl:
pl.stats.kills = 0
pl.stats.deaths = 0
pl.stats.hp = pl.stats.maxhp
if int(pl.name) != multiplayer.get_unique_id(): # no need to sync server char, cuz it will be done in the initStats below.
for other_peer in multiplayer.get_peers():
sync_character_data.rpc_id(other_peer, int(pl.name), pl.stats.save())
pl.initStats(pl.stats)
updateScore(true)
pass

View File

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

View File

@@ -1,344 +0,0 @@
[gd_scene load_steps=11 format=3 uid="uid://m8hiw0yydn5"]
[ext_resource type="Script" uid="uid://ct73f1m77ayyp" path="res://scripts/Autoloads/multiplayer_manager.gd" id="1_vqyfe"]
[ext_resource type="AudioStream" uid="uid://caqdvm1q8lk3a" path="res://assets/audio/sfx/ut99/cd1.wav" id="2_84xnf"]
[ext_resource type="AudioStream" uid="uid://b5f6j5p5wcqht" path="res://assets/audio/sfx/announcer/taken_lead.mp3" id="2_eak84"]
[ext_resource type="AudioStream" uid="uid://cv10napkg4ft" path="res://assets/audio/sfx/announcer/lost_lead.mp3" id="3_62id2"]
[ext_resource type="AudioStream" uid="uid://cnc5mjushyugx" path="res://assets/audio/sfx/ut99/cd2.wav" id="3_h0fyv"]
[ext_resource type="AudioStream" uid="uid://dyjn7rgi6of0o" path="res://assets/audio/sfx/ut99/cd3.wav" id="4_4gr8q"]
[ext_resource type="AudioStream" uid="uid://hnwotbj3kwmu" path="res://assets/audio/sfx/ut99/winner.wav" id="5_h0fyv"]
[sub_resource type="Animation" id="Animation_62id2"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:scale")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(1, 1)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:text")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": ["3"]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath(".:pivot_offset")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(40, 58)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Sfx3:playing")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("Sfx2:playing")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("Sfx1:playing")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
[sub_resource type="Animation" id="Animation_eak84"]
resource_name = "countdown"
length = 3.4
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:text")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 1, 2, 3),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": ["3", "2", "1", "GO"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:scale")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.733333, 0.966667, 1.03333, 1.57554, 1.93691, 2.00097, 2.61712, 2.93333, 2.96667, 3.0533, 3.16667, 3.36138),
"transitions": PackedFloat32Array(1, 0.420448, 0.277392, 2, 0.5, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 0,
"values": [Vector2(0.001, 0.001), Vector2(4, 4), Vector2(4, 4), Vector2(0.001, 0.001), Vector2(4, 4), Vector2(4, 4), Vector2(0.001, 0.001), Vector2(4, 4), Vector2(4, 4), Vector2(0.001, 0.001), Vector2(4, 4), Vector2(4, 4), Vector2(0.001, 0.001)]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath(".:pivot_offset")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 3),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [Vector2(40, 58), Vector2(98, 58)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Sfx3:playing")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("Sfx2:playing")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0.866667),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("Sfx1:playing")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(1.93333),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_n0fp4"]
_data = {
&"RESET": SubResource("Animation_62id2"),
&"countdown": SubResource("Animation_eak84")
}
[node name="MultiplayerManager" type="Node2D"]
script = ExtResource("1_vqyfe")
[node name="CanvasLayer" type="CanvasLayer" parent="."]
layer = 21
[node name="ControlCenter" type="Control" parent="CanvasLayer"]
layout_mode = 3
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
grow_horizontal = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="MarginContainerForScore" type="MarginContainer" parent="CanvasLayer/ControlCenter"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -74.5
offset_top = 24.0
offset_right = 74.5
offset_bottom = 82.0
grow_horizontal = 2
[node name="ColorRect" type="ColorRect" parent="CanvasLayer/ControlCenter/MarginContainerForScore"]
layout_mode = 2
size_flags_vertical = 3
color = Color(0, 0, 0, 0.356863)
[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/ControlCenter/MarginContainerForScore"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/ControlCenter/MarginContainerForScore/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 4
[node name="LabelCurrentScore" type="Label" parent="CanvasLayer/ControlCenter/MarginContainerForScore/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/outline_size = 6
text = "- Current score -"
horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/ControlCenter/MarginContainerForScore/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="LabelPlayerNr" type="Label" parent="CanvasLayer/ControlCenter/MarginContainerForScore/MarginContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/outline_size = 6
text = "#
1."
[node name="LabelPlayerNames" type="Label" parent="CanvasLayer/ControlCenter/MarginContainerForScore/MarginContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/outline_size = 6
text = "Player
Elrinth"
[node name="LabelPlayerKills" type="Label" parent="CanvasLayer/ControlCenter/MarginContainerForScore/MarginContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 8
theme_override_constants/outline_size = 6
text = "Kills
0"
horizontal_alignment = 1
[node name="LabelPlayerDeaths" type="Label" parent="CanvasLayer/ControlCenter/MarginContainerForScore/MarginContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 8
size_flags_vertical = 8
theme_override_constants/outline_size = 6
text = "Deaths
0"
horizontal_alignment = 1
[node name="LabelRoundWinner" type="Label" parent="CanvasLayer/ControlCenter/MarginContainerForScore/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/outline_size = 10
theme_override_font_sizes/font_size = 37
text = "Elrinth WINS the round!"
horizontal_alignment = 1
[node name="BottomLeftCorner" type="Control" parent="CanvasLayer"]
layout_mode = 3
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_top = -40.0
offset_right = 40.0
grow_vertical = 0
mouse_filter = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/BottomLeftCorner"]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_top = -40.0
offset_right = 40.0
grow_vertical = 0
[node name="LabelPlayerConnect" type="Label" parent="CanvasLayer/BottomLeftCorner/VBoxContainer"]
layout_mode = 2
horizontal_alignment = 2
[node name="LabelChatHistory" type="Label" parent="CanvasLayer/BottomLeftCorner/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
vertical_alignment = 2
[node name="ControlCountdown" type="Control" parent="CanvasLayer"]
visible = false
layout_mode = 3
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -20.0
offset_top = -20.0
offset_right = 20.0
offset_bottom = 20.0
grow_horizontal = 2
grow_vertical = 2
[node name="LabelCountdown" type="Label" parent="CanvasLayer/ControlCountdown"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
pivot_offset = Vector2(40, 58)
theme_override_constants/outline_size = 16
theme_override_font_sizes/font_size = 128
text = "3"
horizontal_alignment = 1
vertical_alignment = 1
[node name="AnimationPlayer" type="AnimationPlayer" parent="CanvasLayer/ControlCountdown/LabelCountdown"]
libraries = {
&"": SubResource("AnimationLibrary_n0fp4")
}
[node name="Sfx1" type="AudioStreamPlayer2D" parent="CanvasLayer/ControlCountdown/LabelCountdown"]
stream = ExtResource("2_84xnf")
[node name="Sfx2" type="AudioStreamPlayer2D" parent="CanvasLayer/ControlCountdown/LabelCountdown"]
stream = ExtResource("3_h0fyv")
[node name="Sfx3" type="AudioStreamPlayer2D" parent="CanvasLayer/ControlCountdown/LabelCountdown"]
stream = ExtResource("4_4gr8q")
[node name="SfxWinner" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("5_h0fyv")
[node name="SfxTakenTheLead" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("2_eak84")
bus = &"Sfx"
[node name="SfxLostTheLead" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("3_62id2")
bus = &"Sfx"

View File

@@ -1,169 +0,0 @@
extends CharacterBody2D
var speed = 300
var direction = Vector2.ZERO
var stick_duration = 3.0 # How long the arrow stays stuck to walls
var is_stuck = false
var stick_timer = 0.0
var initiated_by: Node2D = null
@onready var arrow_area = $ArrowArea # Assuming you have an Area2D node named ArrowArea
@onready var shadow = $Shadow # Assuming you have a Shadow node under the CharacterBody2D
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
arrow_area.set_deferred("monitoring", true)
#arrow_area.body_entered.connect(_on_body_entered)
$SfxArrowFire.play()
call_deferred("_initialize_arrow")
func _initialize_arrow() -> void:
var angle = direction.angle()
self.rotation = angle - PI / 2 # Adjust for sprite orientation
# Set initial rotation based on direction
velocity = direction * speed # Set initial velocity to move the arrow
# Calculate the offset for the shadow position, which should be below the arrow
var shadow_offset = Vector2(0, 4) # Adjust the 16 to how far you want the shadow from the arrow (this is just an example)
# Apply the rotation of the arrow to the shadow offset
shadow.position += shadow_offset.rotated(-self.rotation)
if abs(direction.x) == 1:
shadow.scale.x = 0.26
shadow.scale.y = 0.062
elif abs(direction.x) > 0:
shadow.scale.x = 0.18
shadow.scale.y = 0.08
else:
shadow.scale.x = 0.1
shadow.scale.y = 0.1
# Calculate the shadow's scale based on the velocity or direction of the arrow
#var velocity_magnitude = velocity.length()
# Scale more in the horizontal direction if moving diagonally or horizontally
#var scale_factor = 0.28 + abs(velocity.x) / velocity_magnitude # Adjust the factor to your preference
# Apply the scaling to the shadow
shadow.rotation = -(angle - PI / 2)
func shoot(shoot_direction: Vector2, start_pos: Vector2) -> void:
direction = shoot_direction.normalized()
global_position = start_pos
#position = start_pos
# Called every frame. 'delta' is the e lapsed time since the previous frame.
func _process(delta: float) -> void:
if is_stuck:
# Handle fade out here if it's stuck
stick_timer += delta
if stick_timer >= stick_duration:
# Start fading out after it sticks
modulate.a = max(0, 1 - (stick_timer - stick_duration) / 1.0) # Fade out over 1 second
if stick_timer >= stick_duration + 1.0: # Extra second for fade out
queue_free() # Remove the arrow after fade out
move_and_slide()
func _physics_process(_delta: float) -> void:
# If the arrow is stuck, stop it from moving
if is_stuck:
velocity = Vector2.ZERO # Stop movement
# Optional: disable further physics interaction by setting linear_velocity
# move_and_slide(Vector2.ZERO) # You can also use this to stop the character
func play_impact():
$SfxImpactSound.play()
# Called when the arrow hits a wall or another object
func _on_body_entered(body: Node) -> void:
if not is_stuck:
if body == initiated_by:
return
if body is CharacterBody2D and body.stats.is_invulnerable == false and body.stats.hp > 0: # hit an enemy
#if body is CharacterBody2D and body.collision_layer & (1 << 8) and body.taking_damage_timer <= 0 and body.stats.hp > 0: # Check if body is enemy (layer 9)
# Stop the arrow
velocity = Vector2.ZERO
is_stuck = true
stick_timer = 0.0
arrow_area.set_deferred("monitoring", false)
# Calculate the collision point - move arrow slightly back from its direction
var collision_normal = -direction # Opposite of arrow's direction
var offset_distance = 8 # Adjust this value based on your collision shape sizes
var stick_position = global_position + (collision_normal * offset_distance)
# Make arrow a child of the enemy to stick to it
var global_rot = global_rotation
get_parent().call_deferred("remove_child", self)
body.call_deferred("add_child", self)
self.set_deferred("global_position", stick_position)
self.set_deferred("global_rotation", global_rot)
#global_rotation = global_rot
body.call_deferred("take_damage", self, initiated_by)
self.call_deferred("play_impact") # need to play the sound on the next frame, because else it cuts it.
else:
$SfxImpactWall.play()
# Stop the arrow
velocity = Vector2.ZERO
is_stuck = true
stick_timer = 0.0
arrow_area.set_deferred("monitoring", false)
# You can optionally stick the arrow at the collision point if you want:
# position = body.position # Uncomment this if you want to "stick" it at the collision point
# Additional logic for handling interaction with walls or other objects
func _on_arrow_area_area_entered(area: Area2D) -> void:
if not is_stuck:
if area.get_parent() == initiated_by:
return
if area.get_parent() is CharacterBody2D and area.get_parent().stats.is_invulnerable == false and area.get_parent().stats.hp > 0: # hit an enemy
#if body is CharacterBody2D and body.collision_layer & (1 << 8) and body.taking_damage_timer <= 0 and body.stats.hp > 0: # Check if body is enemy (layer 9)
# Stop the arrow
velocity = Vector2.ZERO
is_stuck = true
stick_timer = 0.0
arrow_area.set_deferred("monitoring", false)
# Calculate the collision point - move arrow slightly back from its direction
var collision_normal = -direction # Opposite of arrow's direction
var offset_distance = 8 # Adjust this value based on your collision shape sizes
var stick_position = global_position + (collision_normal * offset_distance)
# Make arrow a child of the enemy to stick to it
var global_rot = global_rotation
get_parent().call_deferred("remove_child", self)
area.get_parent().call_deferred("add_child", self)
self.set_deferred("global_position", stick_position)
self.set_deferred("global_rotation", global_rot)
#global_rotation = global_rot
area.get_parent().call_deferred("take_damage", self, initiated_by)
self.call_deferred("play_impact") # need to play the sound on the next frame, because else it cuts it.
else:
$SfxImpactWall.play()
# Stop the arrow
velocity = Vector2.ZERO
is_stuck = true
stick_timer = 0.0
arrow_area.set_deferred("monitoring", false)
# You can optionally stick the arrow at the collision point if you want:
# position = body.position # Uncomment this if you want to "stick" it at the collision point
# Additional logic for handling interaction with walls or other objects
pass # Replace with function body.
func _on_arrow_area_body_entered(body: Node2D) -> void:
if not is_stuck:
if body == initiated_by:
return
$SfxImpactWall.play()
# Stop the arrow
velocity = Vector2.ZERO
is_stuck = true
stick_timer = 0.0
arrow_area.set_deferred("monitoring", false)
pass # Replace with function body.

View File

@@ -1 +0,0 @@
uid://5o1ua60xh3jr

View File

@@ -1,76 +0,0 @@
[gd_scene load_steps=10 format=3 uid="uid://bh3q00c8grsdp"]
[ext_resource type="Texture2D" uid="uid://ba772auc1t65n" path="res://assets/gfx/arrow.png" id="1_bey2v"]
[ext_resource type="Script" path="res://scripts/attacks/arrow.gd" id="1_if6eb"]
[ext_resource type="AudioStream" uid="uid://hmci4kgvbqib" path="res://assets/audio/sfx/weapons/bow/arrow_fire_swosh.wav" id="3_o8cb2"]
[ext_resource type="AudioStream" uid="uid://b140nlsak4ub7" path="res://assets/audio/sfx/weapons/bow/arrow-hit-brick-wall-01.mp3" id="4_8l43l"]
[ext_resource type="AudioStream" uid="uid://dc7nt8gnjt5u5" path="res://assets/audio/sfx/weapons/melee_attack_12.wav.mp3" id="4_ol4b0"]
[sub_resource type="Gradient" id="Gradient_yp18a"]
colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_gpny7"]
gradient = SubResource("Gradient_yp18a")
fill = 1
fill_from = Vector2(0.504587, 0.504587)
fill_to = Vector2(0.848624, 0.784404)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_b6ybh"]
size = Vector2(2, 6)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_wuwd8"]
size = Vector2(2, 8)
[node name="Arrow" type="CharacterBody2D"]
z_index = 10
y_sort_enabled = true
collision_layer = 0
collision_mask = 0
motion_mode = 1
script = ExtResource("1_if6eb")
[node name="Shadow" type="Sprite2D" parent="."]
z_index = 1
z_as_relative = false
position = Vector2(-2.98023e-08, 0)
scale = Vector2(0.09375, 0.0820313)
texture = SubResource("GradientTexture2D_gpny7")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("1_bey2v")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_b6ybh")
[node name="ArrowArea" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 320
priority = 1
[node name="CollisionShape2D" type="CollisionShape2D" parent="ArrowArea"]
physics_interpolation_mode = 1
position = Vector2(0, 1)
shape = SubResource("RectangleShape2D_wuwd8")
debug_color = Color(0.7, 0, 0.195726, 0.42)
[node name="SfxArrowFire" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("3_o8cb2")
pitch_scale = 1.61
max_polyphony = 4
[node name="SfxImpactWall" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("4_8l43l")
volume_db = -4.0
pitch_scale = 1.29
attenuation = 3.4822
max_polyphony = 4
panning_strength = 1.3
[node name="SfxImpactSound" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("4_ol4b0")
volume_db = -4.685
pitch_scale = 1.47
max_polyphony = 4
[connection signal="area_entered" from="ArrowArea" to="." method="_on_arrow_area_area_entered"]
[connection signal="body_entered" from="ArrowArea" to="." method="_on_arrow_area_body_entered"]

View File

@@ -1,64 +0,0 @@
extends Node2D
var direction := Vector2.ZERO # Default direction
var fade_delay := 0.14 # When to start fading (mid-move)
var move_duration := 0.2 # Slash exists for 0.3 seconds
var fade_duration := 0.06 # Time to fade out
var stretch_amount := Vector2(1, 1.4) # How much to stretch the sprite
var slash_amount = 8
var initiated_by: Node2D = null
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
call_deferred("_initialize_swing")
pass # Replace with function body.
func _initialize_swing():
var tween = create_tween()
var move_target = global_position + (direction.normalized() * slash_amount) # Moves in given direction
tween.set_trans(Tween.TRANS_CUBIC) # Smooth acceleration & deceleration
tween.set_ease(Tween.EASE_OUT) # Fast start, then slows down
tween.tween_property(self, "global_position", move_target, move_duration)
'
# Create stretch tween (grow and shrink slightly)
var stretch_tween = create_tween()
stretch_tween.set_trans(Tween.TRANS_CUBIC)
stretch_tween.set_ease(Tween.EASE_OUT)
stretch_tween.tween_property($Sprite2D, "scale", Vector2.ONE, move_duration / 2) # start normal
stretch_tween.tween_property($Sprite2D, "scale", stretch_amount, move_duration / 2)
'
# Wait until mid-move to start fade
await get_tree().create_timer(fade_delay).timeout
# Start fade-out effect
var fade_tween = create_tween()
fade_tween.tween_property($Sprite2D, "modulate:a", 0.0, fade_duration) # Fade to transparent
await fade_tween.finished
queue_free()
pass
func _on_damage_area_body_entered(body: Node2D) -> void:
if body.get_parent() == initiated_by or body == initiated_by:
return
if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy
$MeleeImpact.play()
body.take_damage(self, initiated_by)
pass
else:
$MeleeImpactWall.play()
pass
pass # Replace with function body.
func _on_damage_area_area_entered(body: Area2D) -> void:
if body.get_parent() == initiated_by:
return
if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy
$MeleeImpact.play()
body.get_parent().take_damage(self, initiated_by)
pass
else:
$MeleeImpactWall.play()
pass
pass # Replace with function body.

View File

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

View File

@@ -1,88 +0,0 @@
[gd_scene load_steps=10 format=3 uid="uid://cjqyrhyeexbxb"]
[ext_resource type="Script" path="res://scripts/attacks/axe_swing.gd" id="1_xo3v0"]
[ext_resource type="Texture2D" uid="uid://bwxpic53sluul" path="res://assets/gfx/sword_slash.png" id="2_lwt2c"]
[ext_resource type="AudioStream" uid="uid://4vulahdsj4i2" path="res://assets/audio/sfx/swoosh/throw_01.wav.mp3" id="3_v2p0x"]
[ext_resource type="AudioStream" uid="uid://uerx5rib87a6" path="res://assets/audio/sfx/weapons/bone_hit_wall_01.wav.mp3" id="4_ul7bj"]
[ext_resource type="AudioStream" uid="uid://dc7nt8gnjt5u5" path="res://assets/audio/sfx/weapons/melee_attack_12.wav.mp3" id="5_whqew"]
[sub_resource type="Animation" id="Animation_6bxep"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Color(1, 1, 1, 1)]
}
[sub_resource type="Animation" id="Animation_p46b1"]
resource_name = "slash_anim"
length = 0.8
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.533333, 0.733333),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_hj6i2"]
_data = {
&"RESET": SubResource("Animation_6bxep"),
&"slash_anim": SubResource("Animation_p46b1")
}
[sub_resource type="RectangleShape2D" id="RectangleShape2D_3jdng"]
size = Vector2(12, 12)
[node name="AxeSwing" type="Node2D"]
z_index = 10
y_sort_enabled = true
script = ExtResource("1_xo3v0")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("2_lwt2c")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_hj6i2")
}
[node name="AttackSwosh" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("3_v2p0x")
pitch_scale = 0.74
autoplay = true
[node name="DamageArea" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 832
[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea"]
shape = SubResource("RectangleShape2D_3jdng")
debug_color = Color(0.7, 0, 0.18232, 0.42)
[node name="MeleeImpactWall" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("4_ul7bj")
volume_db = -4.0
pitch_scale = 1.3
max_polyphony = 4
[node name="MeleeImpact" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("5_whqew")
volume_db = -5.622
pitch_scale = 1.43
max_polyphony = 4
[connection signal="area_entered" from="DamageArea" to="." method="_on_damage_area_area_entered"]
[connection signal="body_entered" from="DamageArea" to="." method="_on_damage_area_body_entered"]

View File

@@ -1,65 +0,0 @@
extends Node2D
var direction := Vector2.ZERO # Default direction
var fade_delay := 0.14 # When to start fading (mid-move)
var move_duration := 0.2 # Slash exists for 0.3 seconds
var fade_duration := 0.06 # Time to fade out
var stretch_amount := Vector2(1, 1.4) # How much to stretch the sprite
var slash_amount = 8
var initiated_by: Node2D = null
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
call_deferred("_initialize_punch")
pass # Replace with function body.
func _initialize_punch():
$AnimationPlayer.play("punch")
var tween = create_tween()
var move_target = global_position + (direction.normalized() * slash_amount) # Moves in given direction
tween.set_trans(Tween.TRANS_CUBIC) # Smooth acceleration & deceleration
tween.set_ease(Tween.EASE_OUT) # Fast start, then slows down
tween.tween_property(self, "global_position", move_target, move_duration)
'
# Create stretch tween (grow and shrink slightly)
var stretch_tween = create_tween()
stretch_tween.set_trans(Tween.TRANS_CUBIC)
stretch_tween.set_ease(Tween.EASE_OUT)
stretch_tween.tween_property($Sprite2D, "scale", Vector2.ONE, move_duration / 2) # start normal
stretch_tween.tween_property($Sprite2D, "scale", stretch_amount, move_duration / 2)
'
# Wait until mid-move to start fade
await get_tree().create_timer(fade_delay).timeout
# Start fade-out effect
var fade_tween = create_tween()
fade_tween.tween_property($Sprite2D, "modulate:a", 0.0, fade_duration) # Fade to transparent
await fade_tween.finished
queue_free()
pass
func _on_damage_area_body_entered(body: Node2D) -> void:
if body.get_parent() == initiated_by or body == initiated_by:
return
if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy
$MeleeImpact.play()
body.take_damage(self, initiated_by)
pass
else:
$MeleeImpactWall.play()
pass
pass # Replace with function body.
func _on_damage_area_area_entered(body: Area2D) -> void:
if body.get_parent() == initiated_by:
return
if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy
$MeleeImpact.play()
body.get_parent().take_damage(self, initiated_by)
pass
else:
$MeleeImpactWall.play()
pass
pass # Replace with function body.

View File

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

View File

@@ -1,109 +0,0 @@
[gd_scene load_steps=11 format=3 uid="uid://cdef4c61y40ya"]
[ext_resource type="Script" path="res://scripts/attacks/punch.gd" id="1_8mni7"]
[ext_resource type="Texture2D" uid="uid://b7y7es36vcp8o" path="res://assets/gfx/fx/punch_fx.png" id="2_3hp7h"]
[ext_resource type="AudioStream" uid="uid://4vulahdsj4i2" path="res://assets/audio/sfx/swoosh/throw_01.wav.mp3" id="3_6lqdg"]
[ext_resource type="AudioStream" uid="uid://uerx5rib87a6" path="res://assets/audio/sfx/weapons/bone_hit_wall_01.wav.mp3" id="4_tjs11"]
[ext_resource type="AudioStream" uid="uid://dc7nt8gnjt5u5" path="res://assets/audio/sfx/weapons/melee_attack_12.wav.mp3" id="5_4f3c6"]
[sub_resource type="Animation" id="Animation_6bxep"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [0]
}
[sub_resource type="Animation" id="Animation_oq2oo"]
resource_name = "punch"
length = 0.2
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.1),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [0, 1, 2]
}
[sub_resource type="Animation" id="Animation_p46b1"]
resource_name = "slash_anim"
length = 0.8
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.533333, 0.733333),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_hj6i2"]
_data = {
&"RESET": SubResource("Animation_6bxep"),
&"punch": SubResource("Animation_oq2oo"),
&"slash_anim": SubResource("Animation_p46b1")
}
[sub_resource type="RectangleShape2D" id="RectangleShape2D_3jdng"]
size = Vector2(8, 7)
[node name="Punch" type="Node2D"]
z_index = 10
y_sort_enabled = true
script = ExtResource("1_8mni7")
[node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.5, 0.5)
texture = ExtResource("2_3hp7h")
hframes = 3
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_hj6i2")
}
[node name="AttackSwosh" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("3_6lqdg")
pitch_scale = 0.46
autoplay = true
[node name="DamageArea" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 832
[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea"]
position = Vector2(0, 0.5)
shape = SubResource("RectangleShape2D_3jdng")
debug_color = Color(0.7, 0, 0.18232, 0.42)
[node name="MeleeImpactWall" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("4_tjs11")
volume_db = -4.0
pitch_scale = 1.3
max_polyphony = 4
[node name="MeleeImpact" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("5_4f3c6")
volume_db = -5.622
pitch_scale = 1.43
max_polyphony = 4
[connection signal="area_entered" from="DamageArea" to="." method="_on_damage_area_area_entered"]
[connection signal="body_entered" from="DamageArea" to="." method="_on_damage_area_body_entered"]

View File

@@ -1,64 +0,0 @@
extends Node2D
var direction := Vector2.ZERO # Default direction
var fade_delay := 0.14 # When to start fading (mid-move)
var move_duration := 0.2 # Slash exists for 0.3 seconds
var fade_duration := 0.06 # Time to fade out
var stretch_amount := Vector2(1, 1.4) # How much to stretch the sprite
var slash_amount = 8
var initiated_by: Node2D = null
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
call_deferred("_initialize_thrust")
pass # Replace with function body.
func _initialize_thrust():
var tween = create_tween()
var move_target = global_position + (direction.normalized() * slash_amount) # Moves in given direction
tween.set_trans(Tween.TRANS_CUBIC) # Smooth acceleration & deceleration
tween.set_ease(Tween.EASE_OUT) # Fast start, then slows down
tween.tween_property(self, "global_position", move_target, move_duration)
'
# Create stretch tween (grow and shrink slightly)
var stretch_tween = create_tween()
stretch_tween.set_trans(Tween.TRANS_CUBIC)
stretch_tween.set_ease(Tween.EASE_OUT)
stretch_tween.tween_property($Sprite2D, "scale", Vector2.ONE, move_duration / 2) # start normal
stretch_tween.tween_property($Sprite2D, "scale", stretch_amount, move_duration / 2)
'
# Wait until mid-move to start fade
await get_tree().create_timer(fade_delay).timeout
# Start fade-out effect
var fade_tween = create_tween()
fade_tween.tween_property($Sprite2D, "modulate:a", 0.0, fade_duration) # Fade to transparent
await fade_tween.finished
queue_free()
pass
func _on_damage_area_body_entered(body: Node2D) -> void:
if body.get_parent() == initiated_by or body == initiated_by:
return
if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy
$MeleeImpact.play()
body.take_damage(self, initiated_by)
pass
else:
$MeleeImpactWall.play()
pass
pass # Replace with function body.
func _on_damage_area_area_entered(body: Area2D) -> void:
if body.get_parent() == initiated_by:
return
if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy
$MeleeImpact.play()
body.get_parent().take_damage(self, initiated_by)
pass
else:
$MeleeImpactWall.play()
pass
pass # Replace with function body.

View File

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

View File

@@ -1,88 +0,0 @@
[gd_scene load_steps=10 format=3 uid="uid://bgdys34sdkuwi"]
[ext_resource type="Script" path="res://scripts/attacks/spear_thrust.gd" id="1_psi1x"]
[ext_resource type="Texture2D" uid="uid://bwxpic53sluul" path="res://assets/gfx/sword_slash.png" id="2_rh1o6"]
[ext_resource type="AudioStream" uid="uid://4vulahdsj4i2" path="res://assets/audio/sfx/swoosh/throw_01.wav.mp3" id="3_j7ui3"]
[ext_resource type="AudioStream" uid="uid://uerx5rib87a6" path="res://assets/audio/sfx/weapons/bone_hit_wall_01.wav.mp3" id="4_cijfq"]
[ext_resource type="AudioStream" uid="uid://dc7nt8gnjt5u5" path="res://assets/audio/sfx/weapons/melee_attack_12.wav.mp3" id="5_h4gub"]
[sub_resource type="Animation" id="Animation_6bxep"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Color(1, 1, 1, 1)]
}
[sub_resource type="Animation" id="Animation_p46b1"]
resource_name = "slash_anim"
length = 0.8
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.533333, 0.733333),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_hj6i2"]
_data = {
&"RESET": SubResource("Animation_6bxep"),
&"slash_anim": SubResource("Animation_p46b1")
}
[sub_resource type="RectangleShape2D" id="RectangleShape2D_3jdng"]
size = Vector2(12, 12)
[node name="SpearThrust" type="Node2D"]
z_index = 10
y_sort_enabled = true
script = ExtResource("1_psi1x")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("2_rh1o6")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_hj6i2")
}
[node name="AttackSwosh" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("3_j7ui3")
pitch_scale = 1.4
autoplay = true
[node name="DamageArea" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 832
[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea"]
shape = SubResource("RectangleShape2D_3jdng")
debug_color = Color(0.7, 0, 0.18232, 0.42)
[node name="MeleeImpactWall" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("4_cijfq")
volume_db = -4.0
pitch_scale = 1.3
max_polyphony = 4
[node name="MeleeImpact" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("5_h4gub")
volume_db = -5.622
pitch_scale = 1.43
max_polyphony = 4
[connection signal="area_entered" from="DamageArea" to="." method="_on_damage_area_area_entered"]
[connection signal="body_entered" from="DamageArea" to="." method="_on_damage_area_body_entered"]

View File

@@ -1,64 +0,0 @@
extends Node2D
var direction := Vector2.ZERO # Default direction
var fade_delay := 0.14 # When to start fading (mid-move)
var move_duration := 0.2 # Slash exists for 0.3 seconds
var fade_duration := 0.06 # Time to fade out
var stretch_amount := Vector2(1, 1.4) # How much to stretch the sprite
var slash_amount = 8
var initiated_by: Node2D = null
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
call_deferred("_initialize_slash")
pass # Replace with function body.
func _initialize_slash():
var tween = create_tween()
var move_target = global_position + (direction.normalized() * slash_amount) # Moves in given direction
tween.set_trans(Tween.TRANS_CUBIC) # Smooth acceleration & deceleration
tween.set_ease(Tween.EASE_OUT) # Fast start, then slows down
tween.tween_property(self, "global_position", move_target, move_duration)
'
# Create stretch tween (grow and shrink slightly)
var stretch_tween = create_tween()
stretch_tween.set_trans(Tween.TRANS_CUBIC)
stretch_tween.set_ease(Tween.EASE_OUT)
stretch_tween.tween_property($Sprite2D, "scale", Vector2.ONE, move_duration / 2) # start normal
stretch_tween.tween_property($Sprite2D, "scale", stretch_amount, move_duration / 2)
'
# Wait until mid-move to start fade
await get_tree().create_timer(fade_delay).timeout
# Start fade-out effect
var fade_tween = create_tween()
fade_tween.tween_property($Sprite2D, "modulate:a", 0.0, fade_duration) # Fade to transparent
await fade_tween.finished
queue_free()
pass
func _on_damage_area_body_entered(body: Node2D) -> void:
if body.get_parent() == initiated_by or body == initiated_by:
return
if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy
$MeleeImpact.play()
body.take_damage(self, initiated_by)
pass
else:
$MeleeImpactWall.play()
pass
pass # Replace with function body.
func _on_damage_area_area_entered(area: Area2D) -> void:
if area.get_parent() == initiated_by:
return
if area.get_parent() is CharacterBody2D and area.get_parent().stats.is_invulnerable == false and area.get_parent().stats.hp > 0: # hit an enemy
$MeleeImpact.play()
area.get_parent().take_damage(self,self)
pass
else:
$MeleeImpactWall.play()
pass
pass # Replace with function body.

View File

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

View File

@@ -1,88 +0,0 @@
[gd_scene load_steps=10 format=3 uid="uid://cfm53nbsy2e33"]
[ext_resource type="Script" uid="uid://p1jghmgvhqic" path="res://scripts/attacks/sword_slash.gd" id="1_3ef01"]
[ext_resource type="Texture2D" uid="uid://bwxpic53sluul" path="res://assets/gfx/sword_slash.png" id="1_asvt4"]
[ext_resource type="AudioStream" uid="uid://4vulahdsj4i2" path="res://assets/audio/sfx/swoosh/throw_01.wav.mp3" id="3_c1h6a"]
[ext_resource type="AudioStream" uid="uid://dc7nt8gnjt5u5" path="res://assets/audio/sfx/weapons/melee_attack_12.wav.mp3" id="4_e5miu"]
[ext_resource type="AudioStream" uid="uid://uerx5rib87a6" path="res://assets/audio/sfx/weapons/bone_hit_wall_01.wav.mp3" id="4_hx26t"]
[sub_resource type="Animation" id="Animation_6bxep"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Color(1, 1, 1, 1)]
}
[sub_resource type="Animation" id="Animation_p46b1"]
resource_name = "slash_anim"
length = 0.8
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.533333, 0.733333),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_hj6i2"]
_data = {
&"RESET": SubResource("Animation_6bxep"),
&"slash_anim": SubResource("Animation_p46b1")
}
[sub_resource type="RectangleShape2D" id="RectangleShape2D_3jdng"]
size = Vector2(12, 12)
[node name="SwordSlash" type="Node2D"]
z_index = 10
y_sort_enabled = true
script = ExtResource("1_3ef01")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("1_asvt4")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_hj6i2")
}
[node name="AttackSwosh" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("3_c1h6a")
pitch_scale = 1.4
autoplay = true
[node name="DamageArea" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 832
[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea"]
shape = SubResource("RectangleShape2D_3jdng")
debug_color = Color(0.7, 0, 0.18232, 0.42)
[node name="MeleeImpactWall" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("4_hx26t")
volume_db = -4.0
pitch_scale = 1.3
max_polyphony = 4
[node name="MeleeImpact" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("4_e5miu")
volume_db = -5.622
pitch_scale = 1.43
max_polyphony = 4
[connection signal="area_entered" from="DamageArea" to="." method="_on_damage_area_area_entered"]
[connection signal="body_entered" from="DamageArea" to="." method="_on_damage_area_body_entered"]

View File

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

View File

@@ -1,24 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://dan4lcifonhd6"]
[ext_resource type="Script" uid="uid://5ik7rqtmhcxb" path="res://scripts/components/tile_particle.gd" id="1_2gfs2"]
[ext_resource type="Texture2D" uid="uid://bu4dq78f8lgj5" path="res://assets/gfx/sheet_18.png" id="2_ux51i"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_l644m"]
size = Vector2(15, 15)
[node name="TileParticle" type="CharacterBody2D"]
z_index = 17
z_as_relative = false
y_sort_enabled = true
collision_layer = 0
collision_mask = 0
script = ExtResource("1_2gfs2")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(-0.5, -0.5)
shape = SubResource("RectangleShape2D_l644m")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("2_ux51i")
region_enabled = true
region_rect = Rect2(0, 0, 16, 16)

View File

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

View File

@@ -1,28 +0,0 @@
[gd_scene load_steps=5 format=3 uid="uid://cjv34dxmi1fje"]
[ext_resource type="Script" uid="uid://bkj75f0p8yp8t" path="res://assets/scripts/components/blood_clot.gd" id="1_s6bhv"]
[sub_resource type="Gradient" id="Gradient_yyyio"]
offsets = PackedFloat32Array(0)
colors = PackedColorArray(0.983734, 0.952478, 1, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_s6bhv"]
gradient = SubResource("Gradient_yyyio")
width = 3
height = 3
[sub_resource type="RectangleShape2D" id="RectangleShape2D_s6bhv"]
size = Vector2(1, 1)
[node name="BloodClot" type="CharacterBody2D"]
collision_layer = 0
collision_mask = 64
script = ExtResource("1_s6bhv")
[node name="Sprite2D" type="Sprite2D" parent="."]
modulate = Color(0.585938, 0, 0.0793956, 1)
scale = Vector2(0.5, 0.5)
texture = SubResource("GradientTexture2D_s6bhv")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_s6bhv")

View File

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

View File

@@ -1,20 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://cbobah2ptwqh7"]
[ext_resource type="FontFile" uid="uid://cbmcfue0ek0tk" path="res://assets/fonts/dmg_numbers.png" id="1_q3q3r"]
[ext_resource type="Script" uid="uid://dkaeib51vnhsn" path="res://scripts/components/damage_number.gd" id="2_l584u"]
[sub_resource type="Theme" id="Theme_rmltq"]
default_font = ExtResource("1_q3q3r")
default_font_size = 8
[node name="DamageNumber" type="Label"]
light_mask = 262144
visibility_layer = 262144
z_index = 19
size_flags_horizontal = 6
size_flags_vertical = 6
theme = SubResource("Theme_rmltq")
text = "1"
horizontal_alignment = 1
vertical_alignment = 1
script = ExtResource("2_l584u")

View File

@@ -1 +0,0 @@
uid://5ik7rqtmhcxb

View File

@@ -0,0 +1,21 @@
@tool
extends Sprite2D
# Helper script to create a simple circle sprite at runtime
func _ready():
if texture == null:
var image = Image.create(64, 64, false, Image.FORMAT_RGBA8)
var center = Vector2(32, 32)
var radius = 30.0
for x in range(64):
for y in range(64):
var dist = Vector2(x, y).distance_to(center)
if dist <= radius:
image.set_pixel(x, y, Color.WHITE)
else:
image.set_pixel(x, y, Color.TRANSPARENT)
texture = ImageTexture.create_from_image(image)

View File

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

View File

@@ -0,0 +1,15 @@
@tool
extends Sprite2D
# Helper script to create a simple rectangle sprite at runtime
func _ready():
if texture == null:
var image = Image.create(64, 64, false, Image.FORMAT_RGBA8)
for x in range(64):
for y in range(64):
image.set_pixel(x, y, Color.WHITE)
texture = ImageTexture.create_from_image(image)

View File

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

View File

@@ -0,0 +1,24 @@
extends Sprite2D
# Creates a simple shadow circle sprite at runtime
func _ready():
var size = 48 # Shadow size
var image = Image.create(size, size, false, Image.FORMAT_RGBA8)
# Draw a semi-transparent black circle for shadow
for x in range(size):
for y in range(size):
var dx = x - size / 2.0
var dy = y - size / 2.0
var distance = sqrt(dx * dx + dy * dy)
if distance <= size / 2.0:
# Softer edges
var alpha = 0.4 * (1.0 - distance / (size / 2.0)) * 0.5
image.set_pixel(x, y, Color(0, 0, 0, alpha))
# Create texture from image
texture = ImageTexture.create_from_image(image)
centered = true

View File

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

View File

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

View File

@@ -0,0 +1,102 @@
extends CanvasLayer
# Debug Overlay - Shows network and player information
var debug_label: Label
var info_label: Label
var network_manager
var visible_debug = false
func _ready():
network_manager = get_node_or_null("/root/NetworkManager")
# Create debug label (toggleable)
debug_label = Label.new()
debug_label.name = "DebugLabel"
debug_label.position = Vector2(10, 10)
debug_label.add_theme_color_override("font_color", Color.YELLOW)
add_child(debug_label)
debug_label.visible = false
# Create info label (always visible in top right)
info_label = Label.new()
info_label.name = "InfoLabel"
info_label.add_theme_color_override("font_color", Color.WHITE)
info_label.add_theme_font_size_override("font_size", 20)
info_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
add_child(info_label)
# Position in top right
info_label.anchor_left = 1.0
info_label.anchor_right = 1.0
info_label.offset_left = -200
info_label.offset_right = -10
info_label.offset_top = 10
info_label.offset_bottom = 100
func _process(_delta):
# Toggle debug with F3
if Input.is_action_just_pressed("ui_cancel"):
visible_debug = !visible_debug
debug_label.visible = visible_debug
if visible_debug:
_update_debug_info()
# Always update info label
_update_info_label()
func _update_info_label():
var info = []
if multiplayer.has_multiplayer_peer():
# Show role
if multiplayer.is_server():
info.append("HOST")
else:
info.append("CLIENT")
# Show peer ID
info.append("ID: %d" % multiplayer.get_unique_id())
else:
info.append("OFFLINE")
# Show local player position
var game_world = get_node_or_null("../GameWorld")
if not game_world:
game_world = get_node_or_null("/root/Main/GameWorld")
if game_world:
var player_manager = game_world.get_node_or_null("PlayerManager")
if player_manager:
var local_players = player_manager.get_local_players()
if local_players.size() > 0:
var player = local_players[0]
info.append("Pos: (%.1f, %.1f)" % [player.global_position.x, player.global_position.y])
info_label.text = "\n".join(info)
func _update_debug_info():
var info = []
# Network info
if multiplayer.has_multiplayer_peer():
info.append("Network: Connected")
info.append("Peer ID: %d" % multiplayer.get_unique_id())
info.append("Is Server: %s" % multiplayer.is_server())
if network_manager:
info.append("Connected Peers: %d" % network_manager.players_info.size())
else:
info.append("Network: Offline")
# Player info
if network_manager:
info.append("\nPlayers:")
for peer_id in network_manager.players_info.keys():
var player_info = network_manager.players_info[peer_id]
info.append(" Peer %d: %d local players" % [peer_id, player_info.local_player_count])
# Performance
info.append("\nFPS: %d" % Engine.get_frames_per_second())
debug_label.text = "\n".join(info)

View File

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

88
src/scripts/door.gd Normal file
View File

@@ -0,0 +1,88 @@
extends StaticBody2D
@export_enum("GateDoor", "KeyDoor", "StoneDoor") var type: String = "StoneDoor"
@export_enum("Left", "Up", "Right", "Down") var direction: String = "Up"
#KeyDoors should always be closed at start
#StoneDoor and GateDoor CAN be opened in start, and become closed when entering it's room
#Then you must press a switch in the room or maybe you need to defeat all enemies in the room
@export var is_closed: bool = true
var is_closing:bool = false
var is_opening:bool = false
var time_to_move:float = 0.5
var move_timer:float = 0.0
var initial_position:Vector2 = Vector2.ZERO
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
if direction == "Left":
self.rotate(-PI/2)
elif direction == "Right":
self.rotate(PI/2)
elif direction == "Down":
self.rotate(PI)
initial_position = global_position
var amount = 16
set_collision_layer_value(7, false)
if is_closed:
set_collision_layer_value(7, true)
amount = 0
if direction == "Up":
position.y = initial_position.y - amount
elif direction == "Down":
position.y = initial_position.y + amount
elif direction == "Left":
position.x = initial_position.x - amount
elif direction == "Right":
position.x = initial_position.x + amount
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
# TODO write code to open/close door here
# when door is open, ofcourse
if is_opening or is_closing:
move_timer+=delta
#move 16 pixels in direction under 0.5 seconds
var amount = clamp(16*(move_timer/time_to_move),0,16)
if is_closing:
amount = 16-amount
if direction == "Up":
position.y = initial_position.y - amount
elif direction == "Down":
position.y = initial_position.y + amount
elif direction == "Left":
position.x = initial_position.x - amount
elif direction == "Right":
position.x = initial_position.x + amount
if move_timer >= time_to_move:
if is_opening:
is_closed = false
set_collision_layer_value(7, false)
else:
is_closed = true
set_collision_layer_value(7, true)
is_opening = false
is_closing = false
move_timer = 0
pass
func _open():
$SfxOpenKeyDoor.play()
is_opening = true
is_closing = false
move_timer = 0.0
pass
func _close():
$SfxOpenStoneDoor.play()
is_opening = false
is_closing = true
move_timer = 0.0
pass

1
src/scripts/door.gd.uid Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
uid://b0o8i0uuloh7d
uid://b080rtj3kr1cr

456
src/scripts/enemy_base.gd Normal file
View File

@@ -0,0 +1,456 @@
extends CharacterBody2D
# Base Enemy Class - All enemies inherit from this
@export var max_health: float = 50.0
@export var move_speed: float = 80.0
@export var damage: float = 10.0
@export var attack_cooldown: float = 1.0
var current_health: float = 50.0
var is_dead: bool = false
var target_player: Node = null
var attack_timer: float = 0.0
# Knockback
var is_knocked_back: bool = false
var knockback_time: float = 0.0
var knockback_duration: float = 0.3
var knockback_force: float = 125.0 # Scaled down for 1x scale
# Z-axis for flying enemies
var position_z: float = 0.0
var velocity_z: float = 0.0
# Animation
enum Direction {DOWN = 0, LEFT = 1, RIGHT = 2, UP = 3, DOWN_LEFT = 4, DOWN_RIGHT = 5, UP_LEFT = 6, UP_RIGHT = 7}
var current_direction: Direction = Direction.DOWN
var anim_frame: int = 0
var anim_time: float = 0.0
var anim_speed: float = 0.15 # Seconds per frame
@onready var sprite = get_node_or_null("Sprite2D")
@onready var shadow = get_node_or_null("Shadow")
@onready var collision_shape = get_node_or_null("CollisionShape2D")
func _ready():
current_health = max_health
add_to_group("enemy")
# Setup shadow (if it exists - some enemies like humanoids don't have a Shadow node)
if shadow:
shadow.modulate = Color(0, 0, 0, 0.5)
shadow.z_index = -1
# Top-down physics
motion_mode = MOTION_MODE_FLOATING
func _physics_process(delta):
if is_dead:
# Even when dead, allow knockback to continue briefly
if is_knocked_back:
knockback_time += delta
if knockback_time >= knockback_duration:
is_knocked_back = false
knockback_time = 0.0
velocity = Vector2.ZERO
else:
# Apply friction to slow down knockback
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
move_and_slide()
return
# Only server (authority) runs AI and physics
if multiplayer.has_multiplayer_peer() and not is_multiplayer_authority():
# Clients only interpolate position (handled by _sync_position)
return
# Update attack timer
if attack_timer > 0:
attack_timer -= delta
# Handle knockback
if is_knocked_back:
knockback_time += delta
if knockback_time >= knockback_duration:
is_knocked_back = false
knockback_time = 0.0
velocity = Vector2.ZERO
else:
# Apply friction to slow down knockback
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
# Enemy AI (override in subclasses) - only if not knocked back
if not is_knocked_back:
_ai_behavior(delta)
# Move
move_and_slide()
# Check collisions with players
_check_player_collision()
# Sync position and animation to clients (only server sends)
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority():
# Get state value if enemy has a state variable (for bats/slimes)
var state_val = -1
if "state" in self:
state_val = get("state") as int
# Only send RPC if we're in the scene tree
if is_inside_tree():
# Get enemy name/index for identification
var enemy_name = name
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
# Use game_world to send RPC instead of rpc() on node instance
# This avoids node path resolution issues when clients haven't spawned yet
var game_world = get_tree().get_first_node_in_group("game_world")
if game_world and game_world.has_method("_sync_enemy_position"):
# Send via game_world using enemy name/index and position for identification
game_world._sync_enemy_position.rpc(enemy_name, enemy_index, position, velocity, position_z, current_direction, anim_frame, "", 0, state_val)
else:
# Fallback: try direct rpc (may fail if node path doesn't match)
rpc("_sync_position", position, velocity, position_z, current_direction, anim_frame, "", 0, state_val)
func _ai_behavior(_delta):
# Override in subclasses
pass
func _check_player_collision():
# Check if touching a player to deal damage
for i in get_slide_collision_count():
var collision = get_slide_collision(i)
var collider = collision.get_collider()
if collider and collider.is_in_group("player"):
_attack_player(collider)
func _attack_player(player):
# Attack cooldown
if attack_timer > 0:
return
# Deal damage - send RPC to player's authority peer
if player.has_method("rpc_take_damage"):
var player_peer_id = player.get_multiplayer_authority()
if player_peer_id != 0:
# If target peer is the same as server (us), call directly
# rpc_id() might not execute locally when called to same peer
if multiplayer.is_server() and player_peer_id == multiplayer.get_unique_id():
# Call directly on the same peer
player.rpc_take_damage(damage, global_position)
else:
# Send RPC to remote peer
player.rpc_take_damage.rpc_id(player_peer_id, damage, global_position)
else:
# Fallback: broadcast if we can't get peer_id
player.rpc_take_damage.rpc(damage, global_position)
attack_timer = attack_cooldown
print(name, " attacked ", player.name, " (peer: ", player_peer_id, ", server: ", multiplayer.get_unique_id(), ")")
func _find_nearest_player() -> Node:
var players = get_tree().get_nodes_in_group("player")
var nearest = null
var nearest_dist = INF
for player in players:
if player and is_instance_valid(player) and not player.is_dead:
var dist = global_position.distance_to(player.global_position)
if dist < nearest_dist:
nearest_dist = dist
nearest = player
return nearest
func _find_nearest_player_in_range(max_range: float) -> Node:
# Find nearest player within specified range
var players = get_tree().get_nodes_in_group("player")
var nearest = null
var nearest_dist = INF
for player in players:
if player and is_instance_valid(player) and not player.is_dead:
var dist = global_position.distance_to(player.global_position)
if dist <= max_range and dist < nearest_dist:
nearest_dist = dist
nearest = player
return nearest
func take_damage(amount: float, from_position: Vector2):
# Only process damage on server/authority
if not is_multiplayer_authority():
return
if is_dead:
return
current_health -= amount
print(name, " took ", amount, " damage! Health: ", current_health)
# Calculate knockback direction (away from attacker)
var knockback_direction = (global_position - from_position).normalized()
velocity = knockback_direction * knockback_force
is_knocked_back = true
knockback_time = 0.0
_on_take_damage()
# Flash red (even if dying, show the hit)
_flash_damage()
# Sync damage visual to clients
# Use game_world to route damage visual sync instead of direct RPC to avoid node path issues
if multiplayer.has_multiplayer_peer() and is_inside_tree():
var enemy_name = name
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
var game_world = get_tree().get_first_node_in_group("game_world")
if game_world and game_world.has_method("_sync_enemy_damage_visual"):
game_world._sync_enemy_damage_visual.rpc(enemy_name, enemy_index)
else:
# Fallback: try direct RPC (may fail if node path doesn't match)
_sync_damage_visual.rpc()
if current_health <= 0:
# Delay death slightly so knockback is visible
call_deferred("_die")
@rpc("any_peer", "reliable")
func rpc_take_damage(amount: float, from_position: Vector2):
# RPC version - only process on server/authority
if is_multiplayer_authority():
take_damage(amount, from_position)
func _flash_damage():
# Flash red visual effect
if sprite:
var tween = create_tween()
tween.tween_property(sprite, "modulate", Color.RED, 0.1)
tween.tween_property(sprite, "modulate", Color.WHITE, 0.1)
func _update_client_visuals():
# Override in subclasses to update sprite frame and visuals based on synced state
# Base implementation just updates Z position
if sprite:
sprite.position.y = - position_z * 0.5
if shadow:
var shadow_scale = 1.0 - (position_z / 50.0) * 0.5
shadow.scale = Vector2.ONE * max(0.3, shadow_scale)
shadow.modulate.a = 0.5 - (position_z / 50.0) * 0.2
func _on_take_damage():
# Override in subclasses for custom damage reactions
pass
func _set_animation(_anim_name: String):
# Virtual function - override in subclasses that use animation state system
# (e.g., enemy_humanoid.gd uses player-like animation system)
pass
func _die():
if is_dead:
return
is_dead = true
print(name, " died!")
# Spawn loot immediately (before death animation)
_spawn_loot()
# Sync death to all clients (only server sends RPC)
# Use game_world to route death sync instead of direct RPC to avoid node path issues
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and is_inside_tree():
var enemy_name = name
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
var game_world = get_tree().get_first_node_in_group("game_world")
if game_world and game_world.has_method("_sync_enemy_death"):
game_world._sync_enemy_death.rpc(enemy_name, enemy_index)
else:
# Fallback: try direct RPC (may fail if node path doesn't match)
_sync_death.rpc()
# Play death animation (override in subclasses)
_play_death_animation()
func _play_death_animation():
# Override in subclasses
await get_tree().create_timer(0.5).timeout
queue_free()
func _spawn_loot():
# Only spawn loot on server/authority
if not is_multiplayer_authority():
print(name, " _spawn_loot() called but not authority, skipping")
return
print(name, " _spawn_loot() called on authority")
# Spawn random loot at enemy position
var loot_scene = preload("res://scenes/loot.tscn")
if not loot_scene:
print(name, " ERROR: loot_scene is null!")
return
# Random chance to drop loot (70% chance)
var loot_chance = randf()
print(name, " loot chance roll: ", loot_chance, " (need > 0.3)")
if loot_chance > 0.3:
# Random loot type
var loot_type = 0
# 50% chance for coin
if randf() < 0.5:
loot_type = 0 # COIN
# 50% chance for food item
else:
var food_types = [1, 2, 3] # APPLE, BANANA, CHERRY
loot_type = food_types[randi() % food_types.size()]
# Generate random velocity values (same on all clients)
var random_angle = randf() * PI * 2
var random_force = randf_range(50.0, 100.0)
var random_velocity_z = randf_range(80.0, 120.0)
# Generate initial velocity (same on all clients via RPC)
var initial_velocity = Vector2(cos(random_angle), sin(random_angle)) * random_force
# Find safe spawn position (on floor tile, not in walls)
var game_world = get_tree().get_first_node_in_group("game_world")
var safe_spawn_pos = global_position
if game_world and game_world.has_method("_find_nearby_safe_spawn_position"):
safe_spawn_pos = game_world._find_nearby_safe_spawn_position(global_position, 64.0)
# Spawn on server
var loot = loot_scene.instantiate()
var entities_node = get_parent()
if entities_node:
entities_node.add_child(loot)
loot.global_position = safe_spawn_pos
loot.loot_type = loot_type
# Set initial velocity before _ready() processes
loot.velocity = initial_velocity
loot.velocity_z = random_velocity_z
loot.velocity_set_by_spawner = true
loot.is_airborne = true
print(name, " ✓ dropped loot: ", loot_type, " at ", safe_spawn_pos, " (original enemy pos: ", global_position, ")")
# Sync loot spawn to all clients (use safe position)
if multiplayer.has_multiplayer_peer():
# Reuse game_world variable from above
if game_world:
# Generate unique loot ID
if not "loot_id_counter" in game_world:
game_world.loot_id_counter = 0
var loot_id = game_world.loot_id_counter
game_world.loot_id_counter += 1
# Store loot ID on server loot instance
loot.set_meta("loot_id", loot_id)
# Sync to clients with ID
game_world._sync_loot_spawn.rpc(safe_spawn_pos, loot_type, initial_velocity, random_velocity_z, loot_id)
print(name, " ✓ synced loot spawn to clients")
else:
print(name, " ERROR: game_world not found for loot sync!")
else:
print(name, " ERROR: entities_node is null! Cannot spawn loot!")
else:
print(name, " loot chance failed (", loot_chance, " <= 0.3), no loot dropped")
# This function can be called directly (not just via RPC) when game_world routes the update
func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, dir: int = 0, frame: int = 0, anim: String = "", frame_num: int = 0, state_value: int = -1):
# Clients receive position and animation updates from server
# Only process if we're not the authority (i.e., we're a client)
if is_multiplayer_authority():
return # Server ignores its own updates
# Debug: Log when client receives position update (first few times)
if not has_meta("position_sync_count"):
set_meta("position_sync_count", 0)
print("Enemy ", name, " (client) RECEIVED first position sync! pos=", pos, " authority: ", get_multiplayer_authority(), " is_authority: ", is_multiplayer_authority(), " in_tree: ", is_inside_tree())
var sync_count = get_meta("position_sync_count") + 1
set_meta("position_sync_count", sync_count)
if sync_count <= 3: # Log first 3 syncs
print("Enemy ", name, " (client) received position sync #", sync_count, ": pos=", pos)
# Update position and state
position = pos
velocity = vel
position_z = z_pos
current_direction = dir as Direction
# Update state if provided (for enemies with state machines like bats/slimes)
if state_value != -1 and "state" in self:
set("state", state_value)
# Update animation if provided (for humanoid enemies with player-like animation system)
if anim != "":
_set_animation(anim)
if "current_frame" in self:
set("current_frame", frame_num)
else:
# Default: use frame number for simple enemies
anim_frame = frame
# Update visual representation (override in subclasses for custom animation)
_update_client_visuals()
# This function can be called directly (not just via RPC) when game_world routes the update
func _sync_damage_visual():
# Clients receive damage visual sync
# Only process if we're not the authority (i.e., we're a client)
if is_multiplayer_authority():
return # Server ignores its own updates
_flash_damage()
# This function can be called directly (not just via RPC) when game_world routes the update
func _sync_death():
# Clients receive death sync and play death animation locally
# Only process if we're not the authority (i.e., we're a client)
if is_multiplayer_authority():
return # Server ignores its own updates
if not is_dead:
is_dead = true
print(name, " received death sync, dying on client")
# Remove collision layer so they don't collide with players, but still collide with walls
# This matches what happens on the server when rats/slimes die
set_collision_layer_value(2, false) # Remove from enemy collision layer (layer 2)
# Immediately mark as dead and stop AI/physics
# This prevents "inactive" enemies that are already dead
_play_death_animation()
else:
# Already dead, but make sure collision is removed and it's removed from scene
print(name, " received death sync but already dead, ensuring removal")
# Remove collision layer if not already removed
if get_collision_layer_value(2):
set_collision_layer_value(2, false)
if not is_queued_for_deletion():
queue_free()
func _get_direction_from_vector(vec: Vector2) -> Direction:
if vec.length() < 0.1:
return current_direction
var angle = vec.angle()
# Convert angle to 8 directions
if angle >= -PI / 8 and angle < PI / 8:
return Direction.RIGHT
elif angle >= PI / 8 and angle < 3 * PI / 8:
return Direction.DOWN_RIGHT
elif angle >= 3 * PI / 8 and angle < 5 * PI / 8:
return Direction.DOWN
elif angle >= 5 * PI / 8 and angle < 7 * PI / 8:
return Direction.DOWN_LEFT
elif angle >= 7 * PI / 8 or angle < -7 * PI / 8:
return Direction.LEFT
elif angle >= -7 * PI / 8 and angle < -5 * PI / 8:
return Direction.UP_LEFT
elif angle >= -5 * PI / 8 and angle < -3 * PI / 8:
return Direction.UP
return Direction.UP_RIGHT

View File

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

158
src/scripts/enemy_bat.gd Normal file
View File

@@ -0,0 +1,158 @@
extends "res://scripts/enemy_base.gd"
# Bat Enemy - Flies around, stationary when idle
enum BatState { IDLE, FLYING }
var state: BatState = BatState.IDLE
var state_timer: float = 0.0
var idle_duration: float = 2.0 # How long to stay idle
var fly_duration: float = 3.0 # How long to fly around
var detection_range: float = 80.0 # Range to detect players (much smaller)
var fly_height: float = 8.0 # Z position when flying
func _ready():
super._ready()
max_health = 30.0
current_health = max_health
move_speed = 40.0 # Reasonable speed for bats
damage = 5.0
state_timer = idle_duration
func _physics_process(delta):
# Always update animation (even when dead, and on clients)
_update_animation(delta)
# Always update Z position and shadow (even on clients)
_update_z_position(delta)
# Call parent physics process (handles dead state, authority checks, etc.)
super._physics_process(delta)
func _ai_behavior(delta):
# Update state timer
state_timer -= delta
# Find nearest player within detection range
target_player = _find_nearest_player_in_range(detection_range)
# State machine
match state:
BatState.IDLE:
_idle_behavior(delta)
BatState.FLYING:
_flying_behavior(delta)
# Animation and Z position are updated in _physics_process (always, even on clients)
func _idle_behavior(_delta):
velocity = Vector2.ZERO
position_z = 0.0
# Show idle frame (frame 2)
anim_frame = 2
# Check if player is nearby
if target_player:
var dist = global_position.distance_to(target_player.global_position)
if dist < detection_range:
# Start flying
state = BatState.FLYING
state_timer = fly_duration
return
# Switch to flying after idle duration
if state_timer <= 0:
state = BatState.FLYING
state_timer = fly_duration
func _flying_behavior(_delta):
position_z = fly_height
# Fly towards player if found
if target_player and is_instance_valid(target_player):
var direction = (target_player.global_position - global_position).normalized()
velocity = direction * move_speed
current_direction = _get_direction_from_vector(direction)
else:
# Wander randomly
if randf() < 0.02: # Small chance to change direction
var random_dir = Vector2(randf() - 0.5, randf() - 0.5).normalized()
velocity = random_dir * move_speed
current_direction = _get_direction_from_vector(random_dir)
# Switch back to idle after flying duration
if state_timer <= 0:
state = BatState.IDLE
state_timer = idle_duration
func _update_animation(delta):
if state == BatState.IDLE:
# Show idle frame
anim_frame = 2
else:
# Animate flying (frames 0, 1, 2)
anim_time += delta
if anim_time >= anim_speed:
anim_time = 0.0
anim_frame = (anim_frame + 1) % 3
# Map 8 directions to 4 sprite directions
var sprite_dir = _get_sprite_direction()
# Set sprite frame
if sprite:
sprite.frame = anim_frame + (sprite_dir * 3) # 3 frames per direction
func _get_sprite_direction() -> int:
# Map 8 directions to 4 sprite rows
match current_direction:
Direction.DOWN, Direction.DOWN_LEFT, Direction.DOWN_RIGHT:
return 0 # Down row
Direction.LEFT, Direction.UP_LEFT:
return 1 # Left row
Direction.RIGHT, Direction.UP_RIGHT:
return 2 # Right row
Direction.UP:
return 3 # Up row
return 0
func _update_z_position(_delta):
# Update sprite Y offset based on height
if sprite:
sprite.position.y = -position_z * 0.5
# Update shadow based on height
if shadow:
var shadow_scale = 1.0 - (position_z / 50.0) * 0.5
shadow.scale = Vector2.ONE * max(0.3, shadow_scale) * Vector2(0.8, 0.4)
shadow.modulate.a = 0.5 - (position_z / 50.0) * 0.2
func _update_client_visuals():
# Update visuals on clients based on synced state
super._update_client_visuals()
# Update animation based on synced state
_update_animation(0.0) # Update animation immediately when state changes
# Update sprite frame based on synced anim_frame and direction
if sprite:
var sprite_dir = _get_sprite_direction()
sprite.frame = anim_frame + (sprite_dir * 3) # 3 frames per direction
func _play_death_animation():
# Fall to ground
var fall_tween = create_tween()
fall_tween.tween_property(self, "position_z", 0.0, 0.3)
await fall_tween.finished
# Fade out
var fade_tween = create_tween()
fade_tween.tween_property(sprite, "modulate:a", 0.0, 0.3)
await fade_tween.finished
queue_free()

View File

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

View File

@@ -0,0 +1,959 @@
extends "res://scripts/enemy_base.gd"
# Humanoid Enemy - Uses same sprite structure as player with multiple layers
enum HumanoidType {
CYCLOPS,
DEMON,
HUMANOID,
NIGHTELF,
GOBLIN,
ORC,
SKELETON
}
@export var humanoid_type: HumanoidType = HumanoidType.HUMANOID
@export var randomize_appearance_on_respawn: bool = false
# Character sprite layers (same as player)
@onready var sprite_body = $Sprite2DBody
@onready var sprite_boots = $Sprite2DBoots
@onready var sprite_armour = $Sprite2DArmour
@onready var sprite_facial_hair = $Sprite2DFacialHair
@onready var sprite_hair = $Sprite2DHair
@onready var sprite_eyes = $Sprite2DEyes
@onready var sprite_eyelashes = $Sprite2DEyeLashes
@onready var sprite_addons = $Sprite2DAddons
@onready var sprite_headgear = $Sprite2DHeadgear
@onready var sprite_weapon = $Sprite2DWeapon
# Attack system
var sword_projectile_scene = preload("res://scenes/sword_projectile.tscn")
var can_attack: bool = true
var is_attacking: bool = false
var is_charging_attack: bool = false
var attack_charge_time: float = 0.0
var base_attack_charge_time: float = 0.4 # Base charge time before attack
var dex: int = 10 # Dexterity stat (affects attack speed)
# AI state
enum AIState {IDLE, WANDERING, NOTICED, CHASING, ATTACKING, GROUPING}
var ai_state: AIState = AIState.IDLE
var state_timer: float = 0.0
var group_target: Node = null # Other humanoid to group with
var group_distance: float = 40.0 # Preferred distance from group members
# Vision system
var vision_range: float = 120.0 # How far the enemy can see (reduced from 200)
var vision_angle: float = PI * 0.75 # Field of view (135 degrees)
var patrol_speed_multiplier: float = 0.4 # Slower when patrolling (reduced from 0.6)
var aggro_range: float = 150.0 # How far enemy will chase before losing aggro (reduced from 250)
var lost_target_timer: float = 0.0 # Timer for losing target
var lost_target_duration: float = 1.0 # Time before forgetting player
# Alert indicators
@onready var alert_indicator = $AlertIndicator # Exclamation mark sprite
@onready var question_indicator = $QuestionIndicator # Question mark sprite
@onready var aggro_area = $AggroArea # Area2D for aggro detection
# Sound effects
@onready var sfx_die = $SfxDie
@onready var sfx_alert_found_player = $SfxAlertFoundPlayer
# Animation system (same as player)
const ANIMATIONS = {
"IDLE": {
"frames": [0, 1],
"frameDurations": [500, 500],
"loop": true,
"nextAnimation": null
},
"RUN": {
"frames": [2, 3, 4, 5],
"frameDurations": [150, 150, 150, 150],
"loop": true,
"nextAnimation": null
},
"SWORD": {
"frames": [6, 7, 8],
"frameDurations": [100, 100, 200],
"loop": false,
"nextAnimation": "IDLE"
},
"DAMAGE": {
"frames": [20, 21],
"frameDurations": [150, 150],
"loop": false,
"nextAnimation": "IDLE"
},
"DIE": {
"frames": [21, 22, 23, 24],
"frameDurations": [200, 200, 200, 800],
"loop": false,
"nextAnimation": null
}
}
var current_animation = "IDLE"
var current_frame = 0
var time_since_last_frame = 0.0
# Random number generator for deterministic appearance (same on all clients)
var appearance_rng: RandomNumberGenerator
# Store spawn position for deterministic seed
var spawn_position: Vector2
func _ready():
super._ready()
# Override sprite reference (we use layered sprites, not single sprite)
sprite = null # Don't use base class sprite
# Store spawn position for deterministic seed (use current position if not set)
if spawn_position == Vector2.ZERO:
spawn_position = global_position
# Create deterministic RNG for appearance
# If randomize_appearance_on_respawn is true, add a random component to make each spawn unique
# Otherwise, use only spawn_position and type for consistent appearance
appearance_rng = RandomNumberGenerator.new()
var seed_value: int
if randomize_appearance_on_respawn:
# Add a random component based on current time/random to make each spawn unique
# But still deterministic across clients by using a synced random value
var random_component = randi() # This will be different each spawn
seed_value = hash(str(spawn_position) + str(humanoid_type) + str(random_component))
print(name, " appearance seed (randomized): ", seed_value, " at spawn position: ", spawn_position, " type: ", humanoid_type)
else:
# Deterministic based on position and type only
seed_value = hash(str(spawn_position) + str(humanoid_type))
print(name, " appearance seed (deterministic): ", seed_value, " at spawn position: ", spawn_position, " type: ", humanoid_type)
appearance_rng.seed = seed_value
# Set up appearance based on type
_setup_appearance()
# Set up stats based on type
_setup_stats()
# Start in idle state
ai_state = AIState.IDLE
state_timer = 2.0
func _setup_appearance():
# Load base body texture based on type
var body_texture_path = _get_body_texture_for_type(humanoid_type)
if body_texture_path:
var body_texture = load(body_texture_path)
if body_texture and sprite_body:
sprite_body.texture = body_texture
sprite_body.hframes = 35
sprite_body.vframes = 8
# Load random equipment/appearance
_load_random_equipment()
# Load type-specific addons
_load_type_addons()
# Load random beastkin addons (low chance)
if appearance_rng.randf() < 0.1: # 10% chance
_load_beastkin_addon()
func _get_body_texture_for_type(type: HumanoidType) -> String:
match type:
HumanoidType.CYCLOPS:
return "res://assets/gfx/Puny-Characters/Layer 0 - Skins/Cyclops1.png"
HumanoidType.DEMON:
return "res://assets/gfx/Puny-Characters/Layer 0 - Skins/Demon1.png"
HumanoidType.HUMANOID:
# Random human skin
var skins = ["Human1.png", "Human1_1.png", "Human2.png", "Human3.png",
"Human4.png", "Human5.png", "Human6.png", "Human7.png"]
return "res://assets/gfx/Puny-Characters/Layer 0 - Skins/" + skins[appearance_rng.randi() % skins.size()]
HumanoidType.NIGHTELF:
return "res://assets/gfx/Puny-Characters/Layer 0 - Skins/NightElf1.png"
HumanoidType.GOBLIN:
return "res://assets/gfx/Puny-Characters/Layer 0 - Skins/Orc1.png" # Use Orc1 for Goblin
HumanoidType.ORC:
var orcs = ["Orc1.png", "Orc2.png"]
return "res://assets/gfx/Puny-Characters/Layer 0 - Skins/" + orcs[appearance_rng.randi() % orcs.size()]
HumanoidType.SKELETON:
var skeletons = ["Skeleton1.png", "Skeleton2.png"]
return "res://assets/gfx/Puny-Characters/Layer 0 - Skins/" + skeletons[appearance_rng.randi() % skeletons.size()]
return ""
func _load_random_equipment():
# Load random shoes, armour, etc. (can be empty)
# For now, just load basic stuff - can be expanded later
pass
func _load_type_addons():
# Load type-specific addons based on requirements
match humanoid_type:
HumanoidType.GOBLIN, HumanoidType.ORC:
# Must have addon: GoblinEars or OrcJaw
var options = []
if humanoid_type == HumanoidType.GOBLIN:
options = ["GoblinEars1.png", "GoblinEars2.png"]
else: # ORC
options = ["OrcJaw1.png", "OrcJaw2.png"]
if options.size() > 0 and sprite_addons:
var addon_path = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/" + options[appearance_rng.randi() % options.size()]
var texture = load(addon_path)
if texture:
sprite_addons.texture = texture
sprite_addons.hframes = 35
sprite_addons.vframes = 8
print(name, " loaded type addon: ", addon_path)
HumanoidType.SKELETON:
# Can have (but not must) skeleton horns
if appearance_rng.randf() < 0.5 and sprite_addons:
var options = ["SkeletonBigHorns1.png", "SkeletonSmallHorns1.png"]
var addon_path = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Skeleton Add-ons/" + options[appearance_rng.randi() % options.size()]
var texture = load(addon_path)
if texture:
sprite_addons.texture = texture
sprite_addons.hframes = 35
sprite_addons.vframes = 8
print(name, " loaded skeleton horns: ", addon_path)
HumanoidType.HUMANOID:
# Can have (but not must) ElfEars
if appearance_rng.randf() < 0.3 and sprite_addons:
var addon_path = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Elf Add-ons/ElfEars1.png"
var texture = load(addon_path)
if texture:
sprite_addons.texture = texture
sprite_addons.hframes = 35
sprite_addons.vframes = 8
print(name, " loaded elf ears")
HumanoidType.NIGHTELF:
# Must have NightElfEars7
if sprite_addons:
var addon_path = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Elf Add-ons/NightElfEars7.png"
var texture = load(addon_path)
if texture:
sprite_addons.texture = texture
sprite_addons.hframes = 35
sprite_addons.vframes = 8
print(name, " loaded night elf ears")
HumanoidType.DEMON:
# Can have DemonEars or DemonJaw
if appearance_rng.randf() < 0.7 and sprite_addons:
var options = ["DemonEars.png", "DemonJaw1.png"]
var addon_path = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Demon Add-ons/" + options[appearance_rng.randi() % options.size()]
var texture = load(addon_path)
if texture:
sprite_addons.texture = texture
sprite_addons.hframes = 35
sprite_addons.vframes = 8
print(name, " loaded demon addon: ", addon_path)
func _load_beastkin_addon():
# Load random beastkin addon (low chance, can override type addon)
var options = [
"Antlers1.png", "BunnyEars1.png", "BunnyEars2.png",
"CatEars1.png", "CatEars2.png", "GoatHorns1.png",
"GoatHorns2.png", "UnicornHorn1.png"
]
if sprite_addons:
var addon_path = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Beastkin Add-ons/" + options[appearance_rng.randi() % options.size()]
var texture = load(addon_path)
if texture:
sprite_addons.texture = texture
sprite_addons.hframes = 35
sprite_addons.vframes = 8
print(name, " loaded beastkin addon: ", addon_path)
func _setup_stats():
# Set stats based on type
match humanoid_type:
HumanoidType.CYCLOPS:
max_health = 100.0
move_speed = 40.0
damage = 15.0
dex = 8 # Slow, strong
HumanoidType.DEMON:
max_health = 80.0
move_speed = 45.0
damage = 12.0
dex = 12 # Medium speed
HumanoidType.HUMANOID:
max_health = 60.0
move_speed = 50.0
damage = 10.0
dex = 15 # Fast, agile
HumanoidType.NIGHTELF:
max_health = 70.0
move_speed = 55.0
damage = 11.0
dex = 18 # Very fast
HumanoidType.GOBLIN:
max_health = 40.0
move_speed = 60.0
damage = 8.0
dex = 20 # Very fast, weak
HumanoidType.ORC:
max_health = 90.0
move_speed = 42.0
damage = 14.0
dex = 7 # Slow, very strong
HumanoidType.SKELETON:
max_health = 50.0
move_speed = 48.0
damage = 9.0
dex = 14 # Medium-fast
current_health = max_health
# Calculate attack cooldown based on DEX (higher DEX = faster attacks)
# Base cooldown of 1.5s, reduced by DEX (max reduction to 0.5s at DEX 20)
var dex_multiplier = 1.0 - (dex - 5) * 0.05 # Each point of DEX above 5 reduces cooldown by 5%
dex_multiplier = clamp(dex_multiplier, 0.33, 1.0) # Clamp between 0.33x (3x faster) and 1.0x
attack_cooldown = 1.5 * dex_multiplier
# Calculate attack charge time based on DEX (higher DEX = shorter charge)
# Base charge of 0.4s, reduced by DEX (min charge of 0.15s at DEX 20)
var charge_multiplier = 1.0 - (dex - 5) * 0.02 # Each point of DEX above 5 reduces charge by 2%
charge_multiplier = clamp(charge_multiplier, 0.375, 1.0) # Clamp between 0.375x (2.67x faster) and 1.0x
base_attack_charge_time = 0.4 * charge_multiplier
print(name, " stats: DEX=", dex, " attack_cooldown=", attack_cooldown, " charge_time=", base_attack_charge_time)
# Setup alert indicators
if alert_indicator:
alert_indicator.visible = false
alert_indicator.z_index = 100 # Always on top
if question_indicator:
question_indicator.visible = false
question_indicator.z_index = 100 # Always on top
# Setup aggro area
if aggro_area:
# Connect signals for aggro detection
aggro_area.body_entered.connect(_on_aggro_area_body_entered)
aggro_area.body_exited.connect(_on_aggro_area_body_exited)
# Set up collision shape radius to match aggro_range
collision_shape = aggro_area.get_node_or_null("CollisionShape2D")
if collision_shape and collision_shape.shape:
collision_shape.shape.radius = aggro_range
func _physics_process(delta):
# Always update animation (even when dead, for death animation)
_update_animation(delta)
# Handle dead state (from parent)
if is_dead:
if is_knocked_back:
knockback_time += delta
if knockback_time >= knockback_duration:
is_knocked_back = false
knockback_time = 0.0
velocity = Vector2.ZERO
else:
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
move_and_slide()
return
# Only server (authority) runs AI and physics
if multiplayer.has_multiplayer_peer() and not is_multiplayer_authority():
return
# Update attack timer
if attack_timer > 0:
attack_timer -= delta
# Handle knockback
if is_knocked_back:
knockback_time += delta
if knockback_time >= knockback_duration:
is_knocked_back = false
knockback_time = 0.0
velocity = Vector2.ZERO
else:
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
# Enemy AI - only if not knocked back
if not is_knocked_back:
_ai_behavior(delta)
# Move
move_and_slide()
# Don't use contact-based attack for humanoids (they use sword projectiles)
# _check_player_collision() # Disabled - humanoids attack with sword projectiles instead
# Sync position and animation to clients (only server sends)
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority():
# Use game_world to send RPC instead of rpc() on node instance
# This avoids node path resolution issues when clients haven't spawned yet
var game_world = get_tree().get_first_node_in_group("game_world")
if game_world and game_world.has_method("_sync_enemy_position"):
# Send via game_world using enemy name/index and position for identification
var enemy_name = name
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1
game_world._sync_enemy_position.rpc(enemy_name, enemy_index, position, velocity, position_z, current_direction, 0, current_animation, current_frame, -1)
else:
# Fallback: try direct call to _sync_position (not RPC)
_sync_position(position, velocity, position_z, current_direction, 0, current_animation, current_frame)
func _ai_behavior(delta):
# Update state timer
state_timer -= delta
# Find nearest player
target_player = _find_nearest_player()
# Find nearby humanoids for grouping
_find_group_target()
# State machine
match ai_state:
AIState.IDLE:
_idle_behavior(delta)
AIState.WANDERING:
_wandering_behavior(delta)
AIState.NOTICED:
_noticed_behavior(delta)
AIState.CHASING:
_chasing_behavior(delta)
AIState.ATTACKING:
_attacking_behavior(delta)
AIState.GROUPING:
_grouping_behavior(delta)
# Update lost target timer
if target_player:
var dist = global_position.distance_to(target_player.global_position)
if dist > aggro_range:
lost_target_timer += delta
if lost_target_timer >= lost_target_duration:
# Forget player and show question mark
_forget_player()
else:
lost_target_timer = 0.0
else:
lost_target_timer = 0.0
func _idle_behavior(_delta):
velocity = Vector2.ZERO
# Set idle animation if not already in another animation
if current_animation != "DAMAGE" and current_animation != "SWORD":
if current_animation != "IDLE":
_set_animation("IDLE")
# Check if player is in vision
if target_player and _is_player_in_vision(target_player):
ai_state = AIState.NOTICED
state_timer = 0.2 # Look at player for 0.2 seconds
_show_alert_indicator()
return
# Check if should group
if group_target:
ai_state = AIState.GROUPING
state_timer = 3.0
return
# Switch to wandering after idle
if state_timer <= 0:
ai_state = AIState.WANDERING
state_timer = 2.0
func _wandering_behavior(_delta):
# Patrolling at slower pace
# Pick a random direction at the start of wandering
if state_timer >= 1.9: # Pick direction at start
var random_angle = randf() * PI * 2
var random_dir = Vector2(cos(random_angle), sin(random_angle))
velocity = random_dir * move_speed * patrol_speed_multiplier # Slower when patrolling
current_direction = _get_direction_from_vector(random_dir)
_set_animation("RUN")
# Check if player enters vision while patrolling
if target_player and _is_player_in_vision(target_player):
ai_state = AIState.NOTICED
state_timer = 0.2 # Look at player for 0.2 seconds
_show_alert_indicator()
return
# Switch back to idle
if state_timer <= 0:
ai_state = AIState.IDLE
state_timer = 2.0
velocity = Vector2.ZERO
func _noticed_behavior(_delta):
# Just noticed player - look at them and show exclamation mark
velocity = Vector2.ZERO
if not target_player or not is_instance_valid(target_player) or target_player.is_dead:
_hide_alert_indicators()
ai_state = AIState.IDLE
state_timer = 1.0
return
# Face the player
var to_player = (target_player.global_position - global_position).normalized()
current_direction = _get_direction_from_vector(to_player)
# Set idle animation
if current_animation != "IDLE" and current_animation != "DAMAGE":
_set_animation("IDLE")
# After 0.2 seconds, start chasing
if state_timer <= 0:
_hide_alert_indicators()
ai_state = AIState.CHASING
state_timer = 5.0
func _chasing_behavior(_delta):
if not target_player or not is_instance_valid(target_player) or target_player.is_dead:
_hide_alert_indicators()
ai_state = AIState.IDLE
state_timer = 1.0
return
var dist = global_position.distance_to(target_player.global_position)
# Check if player left aggro range (handled by Area2D and timer in _ai_behavior)
# But still check if player is in vision for immediate reaction
if dist > aggro_range:
# Player left aggro range - timer will handle forgetting
# Don't immediately switch state, let timer handle it
pass
# Check if player is still in vision
if not _is_player_in_vision(target_player):
# Lost sight of player - go back to patrolling
ai_state = AIState.WANDERING
state_timer = 2.0
return
# Calculate direction to player
var to_player = (target_player.global_position - global_position).normalized()
# Attack if close enough (start charging attack)
if dist < 45.0 and can_attack and not is_charging_attack:
ai_state = AIState.ATTACKING
is_charging_attack = true
attack_charge_time = base_attack_charge_time
velocity = Vector2.ZERO # Stop moving
current_direction = _get_direction_from_vector(to_player) # Face player
return
# Chase player (get close enough to attack)
var desired_distance = 45.0 # Stop this far from player (attack range)
if dist > desired_distance:
velocity = to_player * move_speed * 0.8 # Reduce chase speed to 80% (was 100%)
else:
# Already close enough, stop and wait for attack cooldown
velocity = Vector2.ZERO
current_direction = _get_direction_from_vector(to_player)
# Set animation based on movement
if velocity.length() > 0.1:
if current_animation != "RUN" and current_animation != "SWORD" and current_animation != "DAMAGE":
_set_animation("RUN")
else:
if current_animation != "IDLE" and current_animation != "SWORD" and current_animation != "DAMAGE":
_set_animation("IDLE")
# Give up chasing after timer (or if player leaves vision)
if state_timer <= 0:
ai_state = AIState.IDLE
state_timer = 2.0
func _attacking_behavior(delta):
velocity = Vector2.ZERO
# Charge attack before performing it
if is_charging_attack:
attack_charge_time -= delta
if attack_charge_time <= 0:
# Charge complete, perform attack
is_charging_attack = false
if can_attack and not is_attacking:
_perform_attack()
else:
# Still charging - face player during charge
if target_player and is_instance_valid(target_player) and not target_player.is_dead:
var to_player = (target_player.global_position - global_position).normalized()
current_direction = _get_direction_from_vector(to_player)
# Set idle animation during charge
if current_animation != "IDLE" and current_animation != "SWORD" and current_animation != "DAMAGE":
_set_animation("IDLE")
return # Don't return to chasing yet
# Return to chasing after attack completes
if state_timer <= 0 and not is_attacking and not is_charging_attack:
ai_state = AIState.CHASING
state_timer = 3.0
# Make sure indicators are hidden during attack
_hide_alert_indicators()
func _grouping_behavior(_delta):
if not group_target or not is_instance_valid(group_target):
ai_state = AIState.IDLE
state_timer = 1.0
return
var dist = global_position.distance_to(group_target.global_position)
# Maintain group distance
if dist > group_distance * 1.5:
# Move towards group member
var direction = (group_target.global_position - global_position).normalized()
velocity = direction * move_speed * 0.7
current_direction = _get_direction_from_vector(direction)
else:
# Too close, move away slightly or stop
if dist < group_distance * 0.5:
var direction = (global_position - group_target.global_position).normalized()
velocity = direction * move_speed * 0.3
current_direction = _get_direction_from_vector(direction)
else:
velocity = Vector2.ZERO
# Check if player is nearby while grouping
if target_player:
var player_dist = global_position.distance_to(target_player.global_position)
if player_dist < 150.0:
ai_state = AIState.CHASING
state_timer = 5.0
return
# Return to idle after grouping timer
if state_timer <= 0:
ai_state = AIState.IDLE
state_timer = 2.0
func _find_group_target():
# Find nearby humanoid enemies to group with
var humanoids = get_tree().get_nodes_in_group("enemy")
group_target = null
var nearest_dist = group_distance * 3.0 # Look for group members within this range
for humanoid in humanoids:
if humanoid == self or not is_instance_valid(humanoid):
continue
# Check if it's a humanoid enemy (has humanoid_type property)
if not "humanoid_type" in humanoid:
continue # Not a humanoid
var dist = global_position.distance_to(humanoid.global_position)
if dist < nearest_dist:
nearest_dist = dist
group_target = humanoid
func _perform_attack():
if not can_attack or is_attacking:
return
# Only perform attack on server (authority)
if multiplayer.has_multiplayer_peer() and not is_multiplayer_authority():
return
can_attack = false
is_attacking = true
is_charging_attack = false # Reset charging flag
# Play attack animation
_set_animation("SWORD")
# Set state timer to allow attack animation to complete before returning to chasing
state_timer = attack_cooldown + 0.3 # Give extra time for attack animation
# Calculate attack direction
var attack_direction = Vector2.ZERO
match current_direction:
Direction.RIGHT:
attack_direction = Vector2.RIGHT
Direction.DOWN_RIGHT:
attack_direction = Vector2(1, 1).normalized()
Direction.DOWN:
attack_direction = Vector2.DOWN
Direction.DOWN_LEFT:
attack_direction = Vector2(-1, 1).normalized()
Direction.LEFT:
attack_direction = Vector2.LEFT
Direction.UP_LEFT:
attack_direction = Vector2(-1, -1).normalized()
Direction.UP:
attack_direction = Vector2.UP
Direction.UP_RIGHT:
attack_direction = Vector2(1, -1).normalized()
# Sync attack animation to clients first
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority():
_sync_attack.rpc(current_direction, attack_direction)
# Delay before spawning sword slash
await get_tree().create_timer(0.15).timeout
# Spawn sword projectile (only on server/authority)
if sword_projectile_scene and is_multiplayer_authority():
var projectile = sword_projectile_scene.instantiate()
get_parent().add_child(projectile)
projectile.setup(attack_direction, self)
var spawn_offset = attack_direction * 10.0
projectile.global_position = global_position + spawn_offset
print(name, " attacked with sword projectile at ", global_position)
# Reset attack cooldown
await get_tree().create_timer(attack_cooldown).timeout
can_attack = true
is_attacking = false
@rpc("authority", "reliable")
func _sync_attack(direction: int, attack_dir: Vector2):
# Sync attack to clients
if not is_multiplayer_authority():
current_direction = direction as Direction
_set_animation("SWORD")
await get_tree().create_timer(0.15).timeout
if sword_projectile_scene:
var projectile = sword_projectile_scene.instantiate()
get_parent().add_child(projectile)
projectile.setup(attack_dir, self)
var spawn_offset = attack_dir * 10.0
projectile.global_position = global_position + spawn_offset
print(name, " performed synced attack!")
func _set_animation(anim_name: String):
if anim_name in ANIMATIONS:
current_animation = anim_name
current_frame = 0
time_since_last_frame = 0.0
func _update_animation(delta):
# Update animation frame timing (even when dead, to play death animation)
time_since_last_frame += delta
if time_since_last_frame >= ANIMATIONS[current_animation]["frameDurations"][current_frame] / 1000.0:
current_frame += 1
if current_frame >= len(ANIMATIONS[current_animation]["frames"]):
current_frame -= 1 # Stay on last frame
if ANIMATIONS[current_animation]["loop"]:
current_frame = 0
if ANIMATIONS[current_animation]["nextAnimation"] != null and not is_dead:
# Don't transition to next animation if dead
current_frame = 0
current_animation = ANIMATIONS[current_animation]["nextAnimation"]
time_since_last_frame = 0.0
# Calculate frame index (8 directions, 35 frames per direction)
# Use sprite direction index which matches player sprite frame order
var sprite_dir = _get_sprite_direction_index()
var frame_index = sprite_dir * 35 + ANIMATIONS[current_animation]["frames"][current_frame]
# Update all sprite layers
if sprite_body:
sprite_body.frame = frame_index
if sprite_boots:
sprite_boots.frame = frame_index
if sprite_armour:
sprite_armour.frame = frame_index
if sprite_facial_hair:
sprite_facial_hair.frame = frame_index
if sprite_hair:
sprite_hair.frame = frame_index
if sprite_eyes:
sprite_eyes.frame = frame_index
if sprite_eyelashes:
sprite_eyelashes.frame = frame_index
if sprite_addons:
sprite_addons.frame = frame_index
if sprite_headgear:
sprite_headgear.frame = frame_index
if sprite_weapon:
sprite_weapon.frame = frame_index
# Vision system - check if player is within vision range and angle
func _is_player_in_vision(player: Node) -> bool:
if not player or not is_instance_valid(player) or player.is_dead:
return false
var to_player = player.global_position - global_position
var dist = to_player.length()
# Check range
if dist > vision_range:
return false
# Check angle (field of view) - based on current facing direction
var forward_dir = Vector2(0, 1) # Default facing down
match current_direction:
Direction.DOWN:
forward_dir = Vector2(0, 1)
Direction.DOWN_RIGHT:
forward_dir = Vector2(1, 1).normalized()
Direction.RIGHT:
forward_dir = Vector2(1, 0)
Direction.UP_RIGHT:
forward_dir = Vector2(1, -1).normalized()
Direction.UP:
forward_dir = Vector2(0, -1)
Direction.UP_LEFT:
forward_dir = Vector2(-1, -1).normalized()
Direction.LEFT:
forward_dir = Vector2(-1, 0)
Direction.DOWN_LEFT:
forward_dir = Vector2(-1, 1).normalized()
var to_player_normalized = to_player.normalized()
var angle_to_player = forward_dir.angle_to(to_player_normalized)
# Check if angle is within field of view (half angle on each side)
if abs(angle_to_player) <= vision_angle * 0.5:
return true
return false
# Convert base class Direction enum to player sprite frame direction index
# Player sprite frames: DOWN=0, DOWN_RIGHT=1, RIGHT=2, UP_RIGHT=3, UP=4, UP_LEFT=5, LEFT=6, DOWN_LEFT=7
# Base class enum: DOWN=0, LEFT=1, RIGHT=2, UP=3, DOWN_LEFT=4, DOWN_RIGHT=5, UP_LEFT=6, UP_RIGHT=7
func _get_sprite_direction_index() -> int:
# Map base class Direction enum to player sprite frame direction index
match current_direction:
Direction.DOWN:
return 0 # DOWN -> 0
Direction.DOWN_RIGHT:
return 1 # DOWN_RIGHT -> 1
Direction.RIGHT:
return 2 # RIGHT -> 2
Direction.UP_RIGHT:
return 3 # UP_RIGHT -> 3
Direction.UP:
return 4 # UP -> 4
Direction.UP_LEFT:
return 5 # UP_LEFT -> 5
Direction.LEFT:
return 6 # LEFT -> 6
Direction.DOWN_LEFT:
return 7 # DOWN_LEFT -> 7
return 0
func _update_client_visuals():
# Update visuals on clients based on synced state
# Update all sprite layer positions based on Z
var y_offset = - position_z * 0.5
for sprite_layer in [sprite_body, sprite_boots, sprite_armour, sprite_facial_hair,
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
sprite_headgear, sprite_weapon]:
if sprite_layer:
sprite_layer.position.y = y_offset
# Animation is updated in _update_animation which is called every frame
func _flash_damage():
# Flash all sprite layers red (override base class which uses single sprite)
# But don't flash if dead or about to die - just play die animation
if is_dead or current_health <= 0:
return # Skip flash - die animation will play
for sprite_layer in [sprite_body, sprite_boots, sprite_armour, sprite_facial_hair,
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
sprite_headgear, sprite_weapon]:
if sprite_layer:
var tween = create_tween()
tween.tween_property(sprite_layer, "modulate", Color.RED, 0.1)
tween.tween_property(sprite_layer, "modulate", Color.WHITE, 0.1)
func _on_take_damage():
# Override to play damage animation (same as player)
_set_animation("DAMAGE")
func _play_death_animation():
# Override to play death animation (same as player)
_set_animation("DIE")
# Play death sound effect
if sfx_die:
sfx_die.play()
# Wait for death animation to finish (same duration as player: 200+200+200+800 = 1400ms = 1.4s)
await get_tree().create_timer(1.4).timeout
# Fade out all sprite layers (same as player)
var fade_tween = create_tween()
fade_tween.set_parallel(true)
for sprite_layer in [sprite_body, sprite_boots, sprite_armour, sprite_facial_hair,
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
sprite_headgear, sprite_weapon, shadow]:
if sprite_layer:
fade_tween.tween_property(sprite_layer, "modulate:a", 0.0, 0.5)
# Wait for fade to finish
await fade_tween.finished
# Loot already spawned in _die(), just remove now
queue_free()
func _show_alert_indicator():
# Show exclamation mark
if alert_indicator:
alert_indicator.visible = true
alert_indicator.frame = 0
if question_indicator:
question_indicator.visible = false
# Play alert sound when enemy first notices player (only if not already chasing/attacking)
# This prevents the sound from playing repeatedly if they keep spotting the player
if sfx_alert_found_player and ai_state != AIState.CHASING and ai_state != AIState.ATTACKING:
sfx_alert_found_player.play()
func _show_question_indicator():
# Show question mark
if question_indicator:
question_indicator.visible = true
question_indicator.frame = 0
if alert_indicator:
alert_indicator.visible = false
func _hide_alert_indicators():
# Hide both indicators
if alert_indicator:
alert_indicator.visible = false
if question_indicator:
question_indicator.visible = false
func _hide_question_mark_after_delay():
# Hide question mark after delay
if question_indicator:
question_indicator.visible = false
func _on_aggro_area_body_entered(body):
# Player entered aggro area
if body and body.is_in_group("player") and not body.is_dead:
# If we don't have a target or this is a new player, set as target
if not target_player or target_player != body:
target_player = body
lost_target_timer = 0.0 # Reset timer when player enters aggro range
func _on_aggro_area_body_exited(body):
# Player left aggro area
if body and body.is_in_group("player") and target_player == body:
# Start timer to forget player
# Timer is handled in _ai_behavior, this just confirms they left
pass
func _forget_player():
# Forget the player and reset to patrol
target_player = null
lost_target_timer = 0.0
_show_question_indicator()
ai_state = AIState.WANDERING
state_timer = 2.0
velocity = Vector2.ZERO
# Hide question mark after a short time (use call_deferred to avoid async issues)
get_tree().create_timer(1.0).timeout.connect(_hide_question_mark_after_delay)

View File

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

131
src/scripts/enemy_rat.gd Normal file
View File

@@ -0,0 +1,131 @@
extends "res://scripts/enemy_base.gd"
# Rat Enemy - Moves in 4 directions, chases players
enum RatState { IDLE, WANDERING }
var state: RatState = RatState.IDLE
var state_timer: float = 0.0
var idle_duration: float = 3.0 # Longer idle time
var wander_duration: float = 2.0 # Short wander time
var detection_range: float = 150.0
func _ready():
super._ready()
max_health = 25.0
current_health = max_health
move_speed = 40.0 # Much slower
damage = 8.0
state_timer = idle_duration
func _ai_behavior(delta):
# Update state timer
state_timer -= delta
# Find nearest player
target_player = _find_nearest_player()
# State machine
match state:
RatState.IDLE:
_idle_behavior(delta)
RatState.WANDERING:
_wandering_behavior(delta)
# Update animation
_update_animation(delta)
func _idle_behavior(_delta):
velocity = Vector2.ZERO
# Show idle frame
anim_frame = 2
# Switch to wandering after idle duration (don't chase players)
if state_timer <= 0:
state = RatState.WANDERING
state_timer = wander_duration
func _wandering_behavior(_delta):
# Pick a random direction at the start of wandering
if state_timer >= wander_duration - 0.1: # Only change direction at start
var dirs = [Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT]
var random_dir = dirs[randi() % dirs.size()]
velocity = random_dir * move_speed
current_direction = _get_direction_from_vector(random_dir)
# Keep moving in the same direction during wander
# (velocity is already set, just maintain it)
# Switch back to idle after wander duration
if state_timer <= 0:
state = RatState.IDLE
state_timer = idle_duration
velocity = Vector2.ZERO # Stop moving
func _snap_to_4_directions(dir: Vector2) -> Vector2:
# Snap to closest cardinal direction
if abs(dir.x) > abs(dir.y):
return Vector2(sign(dir.x), 0)
else:
return Vector2(0, sign(dir.y))
func _update_animation(delta):
if state == RatState.IDLE or velocity.length() < 1.0:
# Show idle frame
anim_frame = 2
else:
# Animate moving (frames 0, 1, 2)
anim_time += delta
if anim_time >= anim_speed:
anim_time = 0.0
anim_frame = (anim_frame + 1) % 3
# Map directions to 4 sprite directions (only use cardinal directions)
var sprite_dir = _get_sprite_direction_4()
# Set sprite frame
if sprite:
sprite.frame = anim_frame + (sprite_dir * 3)
func _get_sprite_direction_4() -> int:
# Only use 4 cardinal directions
match current_direction:
Direction.DOWN, Direction.DOWN_LEFT, Direction.DOWN_RIGHT:
return 0 # Down
Direction.LEFT, Direction.UP_LEFT:
return 1 # Left
Direction.RIGHT, Direction.UP_RIGHT:
return 2 # Right
Direction.UP:
return 3 # Up
return 0
func _update_client_visuals():
# Update visuals on clients based on synced state
super._update_client_visuals()
# Update sprite frame based on synced anim_frame and direction
if sprite:
var sprite_dir = _get_sprite_direction_4()
sprite.frame = anim_frame + (sprite_dir * 3) # 3 frames per direction
func _die():
if is_dead:
return
# Remove collision layer so they don't collide with players, but still collide with walls
set_collision_layer_value(2, false) # Remove from enemy collision layer (layer 2)
# Call parent _die() which handles death sync and _play_death_animation()
super._die()
func _play_death_animation():
# Simple fade out
var fade_tween = create_tween()
fade_tween.tween_property(sprite, "modulate:a", 0.0, 0.4)
await fade_tween.finished
queue_free()

View File

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

232
src/scripts/enemy_slime.gd Normal file
View File

@@ -0,0 +1,232 @@
extends "res://scripts/enemy_base.gd"
# Slime Enemy - Bounces around, can do minor jumps
enum SlimeState { IDLE, MOVING, JUMPING, DAMAGED, DYING }
var state: SlimeState = SlimeState.IDLE
var state_timer: float = 0.0
var idle_duration: float = 1.0
var move_duration: float = 2.0
var jump_chance: float = 0.3 # 30% chance to jump instead of walk
var detection_range: float = 70.0 # Range to detect players (much smaller)
# Jump mechanics
var is_jumping: bool = false
var jump_anim_frames = [2, 3, 4, 5, 7, 2] # Jump animation sequence
var jump_anim_index: int = 0
# Animation frames
const FRAME_IDLE = 0
const FRAMES_MOVE = [0, 1, 2] # Slow move
const FRAMES_DAMAGE = [8, 9]
const FRAMES_DEATH = [8, 9, 10, 11, 12, 13, 14]
func _ready():
super._ready()
max_health = 20.0
current_health = max_health
move_speed = 35.0 # Slow normally (reduced from 60)
damage = 6.0
state_timer = idle_duration
# Slime is small - adjust collision
if collision_shape and collision_shape.shape:
collision_shape.shape.radius = 6.0 # 12x12 effective size
func _physics_process(delta):
# Always update animation (even when dead, and on clients)
_update_animation(delta)
# Call parent physics process (handles dead state, authority checks, etc.)
super._physics_process(delta)
func _ai_behavior(delta):
# Update state timer
state_timer -= delta
# Find nearest player within detection range
target_player = _find_nearest_player_in_range(detection_range)
# State machine
match state:
SlimeState.IDLE:
_idle_behavior(delta)
SlimeState.MOVING:
_moving_behavior(delta)
SlimeState.JUMPING:
_jumping_behavior(delta)
SlimeState.DAMAGED:
_damaged_behavior(delta)
SlimeState.DYING:
return # Do nothing while dying
# Animation is updated in _physics_process (always, even on clients)
func _idle_behavior(_delta):
velocity = Vector2.ZERO
anim_frame = FRAME_IDLE
# Check if player is nearby
if target_player:
var dist = global_position.distance_to(target_player.global_position)
if dist < detection_range:
# Start moving or jumping
if randf() < jump_chance:
_start_jump()
else:
state = SlimeState.MOVING
state_timer = move_duration
return
# Switch to moving/jumping after idle duration
if state_timer <= 0:
if randf() < jump_chance:
_start_jump()
else:
state = SlimeState.MOVING
state_timer = move_duration
func _moving_behavior(_delta):
# Move slowly towards player
if target_player and is_instance_valid(target_player):
var direction = (target_player.global_position - global_position).normalized()
velocity = direction * move_speed
else:
# Wander randomly
if randf() < 0.02:
var random_dir = Vector2(randf() - 0.5, randf() - 0.5).normalized()
velocity = random_dir * move_speed
# Randomly jump while moving
if state_timer > 0.5 and randf() < 0.01:
_start_jump()
return
# Switch back to idle after move duration
if state_timer <= 0:
state = SlimeState.IDLE
state_timer = idle_duration
func _start_jump():
state = SlimeState.JUMPING
is_jumping = true
jump_anim_index = 0
state_timer = 0.6 # Jump duration
anim_time = 0.0
# Jump towards player if nearby
if target_player and is_instance_valid(target_player):
var direction = (target_player.global_position - global_position).normalized()
velocity = direction * (move_speed * 1.8) # Faster during jump
else:
# Random jump direction
var random_dir = Vector2(randf() - 0.5, randf() - 0.5).normalized()
velocity = random_dir * (move_speed * 1.8)
func _jumping_behavior(_delta):
# Continue moving in jump direction
# Animation is handled in _update_animation
# End jump
if state_timer <= 0:
is_jumping = false
state = SlimeState.MOVING
state_timer = move_duration
func _damaged_behavior(_delta):
velocity = Vector2.ZERO
# Stay in damaged state briefly
if state_timer <= 0:
state = SlimeState.IDLE
state_timer = idle_duration
func _update_animation(delta):
if state == SlimeState.DYING or state == SlimeState.DAMAGED:
return # Animation handled elsewhere
if state == SlimeState.IDLE:
anim_frame = FRAME_IDLE
elif state == SlimeState.JUMPING:
# Animate jump sequence
anim_time += delta
if anim_time >= 0.1: # Fast jump animation
anim_time = 0.0
jump_anim_index += 1
if jump_anim_index < jump_anim_frames.size():
anim_frame = jump_anim_frames[jump_anim_index]
elif state == SlimeState.MOVING:
# Animate slow move (frames 0, 1, 2)
anim_time += delta
if anim_time >= anim_speed:
anim_time = 0.0
var move_index = FRAMES_MOVE.find(anim_frame)
if move_index == -1:
move_index = 0
else:
move_index = (move_index + 1) % FRAMES_MOVE.size()
anim_frame = FRAMES_MOVE[move_index]
# Set sprite frame (slime looks same in all directions)
if sprite:
sprite.frame = anim_frame
func _on_take_damage():
# Play damage animation
state = SlimeState.DAMAGED
state_timer = 0.3
anim_time = 0.0
# Animate damage frames
_play_damage_anim()
func _play_damage_anim():
for frame in FRAMES_DAMAGE:
anim_frame = frame
if sprite:
sprite.frame = frame
await get_tree().create_timer(0.1).timeout
func _die():
if is_dead:
return
# Remove collision layer so they don't collide with players, but still collide with walls
set_collision_layer_value(2, false) # Remove from enemy collision layer (layer 2)
# Set state before calling parent _die()
state = SlimeState.DYING
velocity = Vector2.ZERO
# Call parent _die() which handles death sync and _play_death_animation()
super._die()
func _update_client_visuals():
# Update visuals on clients based on synced state
super._update_client_visuals()
# Update animation based on synced state
_update_animation(0.0) # Update animation immediately when state changes
# Update sprite frame (slime looks same in all directions, no direction mapping)
if sprite:
sprite.frame = anim_frame
func _play_death_animation():
# Play death animation sequence
for frame in FRAMES_DEATH:
anim_frame = frame
if sprite:
sprite.frame = frame
await get_tree().create_timer(0.15).timeout
# Fade out
if sprite:
var fade_tween = create_tween()
fade_tween.tween_property(sprite, "modulate:a", 0.0, 0.3)
await fade_tween.finished
queue_free()

View File

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

View File

@@ -0,0 +1,201 @@
extends Node2D
# Enemy Spawner - Spawns enemies at this position
@export var enemy_scenes: Array[PackedScene] = [] # List of enemy scenes to randomly choose from
@export var spawn_on_ready: bool = true
@export var respawn_time: float = 10.0 # Time to respawn after enemy dies
@export var max_enemies: int = 1 # Maximum number of enemies this spawner can have alive
var spawned_enemies: Array = []
var respawn_timer: float = 0.0
var smoke_puff_scene = preload("res://scenes/smoke_puff.tscn")
func _ready():
print("========== EnemySpawner READY ==========")
print(" Position: ", global_position)
print(" Is server: ", multiplayer.is_server())
print(" Has multiplayer peer: ", multiplayer.has_multiplayer_peer())
print(" spawn_on_ready: ", spawn_on_ready)
print(" max_enemies: ", max_enemies)
print(" Parent: ", get_parent())
# Spawn on server, or in single player (no multiplayer peer)
var should_spawn = spawn_on_ready and (multiplayer.is_server() or not multiplayer.has_multiplayer_peer())
print(" Should spawn? ", should_spawn)
if should_spawn:
print(" Calling spawn_enemy()...")
call_deferred("spawn_enemy") # Use call_deferred to ensure scene is ready
else:
print(" NOT spawning - conditions not met")
print("========================================")
func _process(delta):
# Only server spawns, or single player
if multiplayer.has_multiplayer_peer() and not multiplayer.is_server():
return
# Clean up dead enemies from list
spawned_enemies = spawned_enemies.filter(func(e): return is_instance_valid(e) and not e.is_dead)
# Check if we need to respawn
if spawned_enemies.size() < max_enemies:
respawn_timer += delta
if respawn_timer >= respawn_time:
spawn_enemy()
respawn_timer = 0.0
func spawn_enemy():
print(">>> spawn_enemy() CALLED <<<")
# Choose enemy scene to spawn
var scene_to_spawn: PackedScene = null
if enemy_scenes.size() > 0:
# Use random scene from list
scene_to_spawn = enemy_scenes[randi() % enemy_scenes.size()]
print(" Selected enemy scene from list: ", scene_to_spawn)
if not scene_to_spawn:
push_error("ERROR: No enemy scene set for spawner! Add scenes to enemy_scenes array.")
return
print(" Spawning enemy at ", global_position)
# Spawn smoke puff effect
_spawn_smoke_puff()
print(" Instantiating enemy scene...")
var enemy = scene_to_spawn.instantiate()
if not enemy:
push_error("ERROR: Failed to instantiate enemy!")
return
print(" Enemy instantiated: ", enemy)
enemy.global_position = global_position
# Set spawn position for deterministic appearance seed (before adding to scene)
if "spawn_position" in enemy:
enemy.spawn_position = global_position
print(" Set enemy position to: ", global_position)
# Add to YSort node for automatic Y-sorting
var ysort = get_parent().get_node_or_null("Entities")
var parent = ysort if ysort else get_parent()
print(" Parent node: ", parent)
if not parent:
push_error("ERROR: No parent node!")
return
print(" Adding enemy as child...")
parent.add_child(enemy)
# Set multiplayer authority to server (peer 1)
if multiplayer.has_multiplayer_peer():
enemy.set_multiplayer_authority(1)
# Determine which scene index was used (for syncing to clients)
var scene_index = -1
if enemy_scenes.size() > 0:
# Find which scene was used
for i in range(enemy_scenes.size()):
if enemy_scenes[i] == scene_to_spawn:
scene_index = i
break
# Store scene_index as metadata on the enemy (for syncing existing enemies to new clients)
enemy.set_meta("spawn_scene_index", scene_index)
spawned_enemies.append(enemy)
print(" ✓ Successfully spawned enemy: ", enemy.name, " at ", global_position, " scene_index: ", scene_index)
print(" Total spawned enemies: ", spawned_enemies.size())
# Sync spawn to all clients via GameWorld
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
# Get GameWorld directly since spawner is a child of GameWorld
var game_world = get_parent()
print(" DEBUG: game_world=", game_world, " spawner name=", name)
if game_world and game_world.has_method("_sync_enemy_spawn"):
# Use spawner name (relative to GameWorld) since it's a direct child
print(" DEBUG: Calling _sync_enemy_spawn.rpc with name=", name, " pos=", global_position, " scene_index=", scene_index)
game_world._sync_enemy_spawn.rpc(name, global_position, scene_index)
print(" Sent RPC to sync enemy spawn to clients: spawner=", name, " pos=", global_position, " scene_index=", scene_index)
else:
push_error("ERROR: Could not find GameWorld or _sync_enemy_spawn method! game_world=", game_world, " has_method=", game_world.has_method("_sync_enemy_spawn") if game_world else "N/A")
func spawn_enemy_at_position(spawn_pos: Vector2, scene_index: int = -1):
# This method is called by GameWorld RPC to spawn enemies on clients
# scene_index tells us which scene from enemy_scenes array was used on the server
var scene_to_spawn: PackedScene = null
if scene_index >= 0 and scene_index < enemy_scenes.size():
# Use the scene index that was synced from server
scene_to_spawn = enemy_scenes[scene_index]
print("Client: Using enemy scene at index ", scene_index, ": ", scene_to_spawn)
elif enemy_scenes.size() > 0:
# Fallback: use first scene if index is invalid
scene_to_spawn = enemy_scenes[0]
print("Client: Invalid scene_index, using first enemy scene: ", scene_to_spawn)
if not scene_to_spawn:
push_error("ERROR: Spawner has no enemy scenes set! Add scenes to enemy_scenes array.")
return
print("Client: spawn_enemy_at_position called at ", spawn_pos)
# Spawn smoke puff effect
_spawn_smoke_puff()
# Instantiate and add enemy
var enemy = scene_to_spawn.instantiate()
if not enemy:
push_error("ERROR: Failed to instantiate enemy on client!")
return
enemy.global_position = spawn_pos
# Set spawn position for deterministic appearance seed (before adding to scene)
if "spawn_position" in enemy:
enemy.spawn_position = spawn_pos
# Add to YSort node for automatic Y-sorting
var ysort = get_parent().get_node_or_null("Entities")
var parent = ysort if ysort else get_parent()
if not parent:
push_error("ERROR: No parent node on client!")
return
parent.add_child(enemy)
# Set multiplayer authority to server (peer 1)
if multiplayer.has_multiplayer_peer():
enemy.set_multiplayer_authority(1)
print(" ✓ Client spawned enemy: ", enemy.name, " at ", spawn_pos)
func get_spawned_enemy_positions() -> Array:
# Return array of dictionaries with position and scene_index for all currently spawned enemies
var enemy_data = []
for enemy in spawned_enemies:
if is_instance_valid(enemy) and not enemy.is_dead:
var scene_index = -1
if enemy.has_meta("spawn_scene_index"):
scene_index = enemy.get_meta("spawn_scene_index")
enemy_data.append({"position": enemy.global_position, "scene_index": scene_index})
return enemy_data
func _spawn_smoke_puff():
print(" _spawn_smoke_puff() called")
print(" smoke_puff_scene: ", smoke_puff_scene)
if smoke_puff_scene:
print(" Instantiating smoke puff...")
var puff = smoke_puff_scene.instantiate()
if puff:
puff.global_position = global_position
get_parent().add_child(puff)
print(" ✓ Smoke puff spawned at ", global_position)
else:
print(" ERROR: Failed to instantiate smoke puff")
else:
print(" WARNING: No smoke puff scene loaded")

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,383 +0,0 @@
[gd_scene load_steps=41 format=3 uid="uid://b15qv06j2v5mq"]
[ext_resource type="Script" uid="uid://dhrlybbkwfyqr" path="res://assets/scripts/entities/enemies/goblin/goblin.gd" id="1_muqri"]
[ext_resource type="AudioStream" uid="uid://dtydo3gymnrcv" path="res://assets/audio/sfx/enemies/goblin/die1.mp3" id="2_jcncw"]
[ext_resource type="Texture2D" uid="uid://caq1ig4sdicos" path="res://assets/gfx/Puny-Characters/Layer 0 - Skins/Orc2.png" id="2_xx2fg"]
[ext_resource type="AudioStream" uid="uid://c3fqe3i3qbl8x" path="res://assets/audio/sfx/enemies/goblin/die2.mp3" id="3_6e00w"]
[ext_resource type="Texture2D" uid="uid://b3rba26046knv" path="res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Armour Body/GoldArmour.png" id="3_876ij"]
[ext_resource type="Shader" uid="uid://b1k834hb0xm2w" path="res://assets/shaders/draw_sprite_above.gdshader" id="4_8og43"]
[ext_resource type="AudioStream" uid="uid://dlgavklpnx0rs" path="res://assets/audio/sfx/enemies/goblin/raargh.mp3" id="4_xx2fg"]
[ext_resource type="AudioStream" uid="uid://cvh7dfc4hshsb" path="res://assets/audio/sfx/enemies/goblin/raargh2.mp3" id="5_876ij"]
[ext_resource type="Texture2D" uid="uid://cxp2kxvigtcwi" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Facial Hairstyles/Beardstyle1Blond.png" id="5_u6mrb"]
[ext_resource type="AudioStream" uid="uid://cbvxokqp1bxar" path="res://assets/audio/sfx/enemies/goblin/raargh3.mp3" id="6_2t7lq"]
[ext_resource type="Texture2D" uid="uid://circuqey2gqgj" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Hairstyles/M Hairstyle 9/MHairstyle9Cyan.png" id="6_nift8"]
[ext_resource type="AudioStream" uid="uid://dscx61fdkejlt" path="res://assets/audio/sfx/enemies/goblin/ive_been_waiting_for_this.mp3" id="7_8og43"]
[ext_resource type="Texture2D" uid="uid://cmhi3ns2fgx7i" path="res://assets/gfx/Puny-Characters/Layer 5 - Eyes/Eye Color/EyecolorBlack.png" id="7_pococ"]
[ext_resource type="Texture2D" uid="uid://b4vh2v0x58v2f" path="res://assets/gfx/Puny-Characters/Layer 5 - Eyes/Eyelashes/MEyelash1.png" id="8_rsnib"]
[ext_resource type="AudioStream" uid="uid://ban8uv8hifsgc" path="res://assets/audio/sfx/enemies/goblin/stay_back_if_you_wanna_keep_your_head.mp3" id="8_v3qiy"]
[ext_resource type="AudioStream" uid="uid://dil4kftgybqcs" path="res://assets/audio/sfx/enemies/goblin/wait_til_i_blow_my_horn.mp3" id="9_jp84j"]
[ext_resource type="Texture2D" uid="uid://dx1fovugabbwc" path="res://assets/gfx/Puny-Characters/Layer 1 - Shoes/IronBoots.png" id="9_v458m"]
[ext_resource type="Texture2D" uid="uid://bkaam8riwwft4" path="res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Range/ArcherHatCyan.png" id="10_a3dd1"]
[ext_resource type="AudioStream" uid="uid://b0jm6abkgm1dv" path="res://assets/audio/sfx/enemies/goblin/your_mine.mp3" id="10_jqf8r"]
[ext_resource type="AudioStream" uid="uid://bhiqb5ppt3ktg" path="res://assets/audio/sfx/enemies/goblin/hey.mp3" id="11_6e00w"]
[ext_resource type="Texture2D" uid="uid://7ubmf453qlu" path="res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/OrcJaw2.png" id="11_vflxt"]
[ext_resource type="Texture2D" uid="uid://bloqx3mibftjn" path="res://assets/gfx/Puny-Characters/WeaponOverlayer.png" id="12_ry8ej"]
[ext_resource type="Texture2D" uid="uid://dxk8dcdqrft0v" path="res://assets/gfx/gibb_sprite.png" id="14_2t7lq"]
[sub_resource type="Shader" id="Shader_oa0y8"]
code = "shader_type canvas_item;
uniform float opacity;
uniform float r;
uniform float g;
uniform float b;
uniform float whitening;
void vertex() {
// Called for every vertex the material is visible on.
}
void fragment() {
// Called for every pixel the material is visible on.
vec4 texture_color = texture(TEXTURE, UV);
if (texture_color.a != 0.0)
COLOR = vec4(mix(texture_color.rgb, vec3(r,g,b), whitening), opacity);
}
void light() {
// Called for every pixel for every light affecting the CanvasItem.
float cNdotL = max(1.0, dot(NORMAL, LIGHT_DIRECTION));
LIGHT = vec4(LIGHT_COLOR.rgb * COLOR.rgb * LIGHT_ENERGY * cNdotL, LIGHT_COLOR.a);
}
"
[sub_resource type="ShaderMaterial" id="ShaderMaterial_nuww6"]
shader = SubResource("Shader_oa0y8")
shader_parameter/opacity = 1.0
shader_parameter/r = 0.0
shader_parameter/g = 0.0
shader_parameter/b = 0.0
shader_parameter/whitening = 0.0
[sub_resource type="ShaderMaterial" id="ShaderMaterial_v3qiy"]
shader = ExtResource("4_8og43")
[sub_resource type="CircleShape2D" id="CircleShape2D_xx2fg"]
radius = 1.0
[sub_resource type="Animation" id="Animation_obk05"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/BodySprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [0]
}
[sub_resource type="Animation" id="Animation_u4t7j"]
resource_name = "idle_down"
length = 0.3
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/BodySprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [0]
}
[sub_resource type="Animation" id="Animation_rnli0"]
resource_name = "idle_left"
length = 0.3
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/BodySprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [9]
}
[sub_resource type="Animation" id="Animation_7qtya"]
resource_name = "idle_right"
length = 0.3
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/BodySprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [6]
}
[sub_resource type="Animation" id="Animation_b5q0x"]
resource_name = "idle_up"
length = 0.3
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/BodySprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [3]
}
[sub_resource type="Animation" id="Animation_twgw7"]
resource_name = "walk_down"
length = 0.8
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/BodySprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [1, 0, 2, 0]
}
[sub_resource type="Animation" id="Animation_iaftf"]
resource_name = "walk_left"
length = 0.8
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/BodySprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [10, 9, 11, 9]
}
[sub_resource type="Animation" id="Animation_vst1w"]
resource_name = "walk_right"
length = 0.8
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/BodySprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [7, 6, 8, 6]
}
[sub_resource type="Animation" id="Animation_it8wc"]
resource_name = "walk_up"
length = 0.8
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/BodySprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [4, 3, 5, 3]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_2y3xo"]
_data = {
&"RESET": SubResource("Animation_obk05"),
&"idle_down": SubResource("Animation_u4t7j"),
&"idle_left": SubResource("Animation_rnli0"),
&"idle_right": SubResource("Animation_7qtya"),
&"idle_up": SubResource("Animation_b5q0x"),
&"walk_down": SubResource("Animation_twgw7"),
&"walk_left": SubResource("Animation_iaftf"),
&"walk_right": SubResource("Animation_vst1w"),
&"walk_up": SubResource("Animation_it8wc")
}
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_sasld"]
streams_count = 2
stream_0/stream = ExtResource("2_jcncw")
stream_1/stream = ExtResource("3_6e00w")
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_1pe7g"]
streams_count = 8
stream_0/stream = ExtResource("4_xx2fg")
stream_1/stream = ExtResource("5_876ij")
stream_2/stream = ExtResource("6_2t7lq")
stream_3/stream = ExtResource("7_8og43")
stream_4/stream = ExtResource("8_v3qiy")
stream_5/stream = ExtResource("9_jp84j")
stream_6/stream = ExtResource("10_jqf8r")
stream_7/stream = ExtResource("11_6e00w")
[sub_resource type="RectangleShape2D" id="RectangleShape2D_876ij"]
size = Vector2(8, 11.5)
[node name="Goblin" type="CharacterBody2D"]
z_index = 10
z_as_relative = false
y_sort_enabled = true
collision_layer = 256
collision_mask = 576
script = ExtResource("1_muqri")
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]
path_desired_distance = 27.73
target_desired_distance = 11.29
path_max_distance = 110.0
navigation_layers = 3
avoidance_enabled = true
radius = 7.0
neighbor_distance = 100.0
time_horizon_obstacles = 0.13
avoidance_mask = 128
[node name="CombinedSprite" type="Sprite2D" parent="."]
position = Vector2(0, -5)
texture = ExtResource("14_2t7lq")
[node name="RenderViewport" type="SubViewport" parent="."]
transparent_bg = true
size = Vector2i(32, 32)
[node name="BodySprite" type="Sprite2D" parent="RenderViewport"]
material = SubResource("ShaderMaterial_nuww6")
texture = ExtResource("2_xx2fg")
centered = false
hframes = 29
vframes = 8
[node name="ArmourSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("3_876ij")
centered = false
hframes = 29
vframes = 8
[node name="FacialSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("5_u6mrb")
centered = false
hframes = 29
vframes = 8
[node name="HairSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("6_nift8")
centered = false
hframes = 29
vframes = 8
[node name="EyeSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("7_pococ")
centered = false
hframes = 29
vframes = 8
[node name="EyeLashSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("8_rsnib")
centered = false
hframes = 29
vframes = 8
[node name="BootsSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("9_v458m")
centered = false
hframes = 29
vframes = 8
[node name="HeadgearSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("10_a3dd1")
centered = false
hframes = 29
vframes = 8
[node name="AddonSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("11_vflxt")
centered = false
hframes = 29
vframes = 8
[node name="AddonHornsSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("11_vflxt")
centered = false
hframes = 29
vframes = 8
[node name="AddonJawSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("11_vflxt")
centered = false
hframes = 29
vframes = 8
[node name="AttackSprite" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("12_ry8ej")
centered = false
hframes = 29
vframes = 8
[node name="BodyAboveSprite" type="Sprite2D" parent="."]
visible = false
z_index = 18
z_as_relative = false
material = SubResource("ShaderMaterial_v3qiy")
position = Vector2(0, -5)
texture = ExtResource("14_2t7lq")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_xx2fg")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_2y3xo")
}
[node name="SfxDie" type="AudioStreamPlayer2D" parent="."]
stream = SubResource("AudioStreamRandomizer_sasld")
max_polyphony = 4
[node name="SfxAlertFoundPlayer" type="AudioStreamPlayer2D" parent="."]
stream = SubResource("AudioStreamRandomizer_1pe7g")
max_polyphony = 8
[node name="Area2DTakeDamage" type="Area2D" parent="."]
collision_layer = 256
collision_mask = 0
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DTakeDamage"]
position = Vector2(0, -4)
shape = SubResource("RectangleShape2D_876ij")
debug_color = Color(0.7, 0, 0.0273481, 0.42)
[connection signal="velocity_computed" from="NavigationAgent2D" to="." method="_on_navigation_agent_2d_velocity_computed"]

View File

@@ -1,121 +0,0 @@
[gd_scene load_steps=19 format=3 uid="uid://wg3txryj23fv"]
[ext_resource type="Script" uid="uid://f7yt42uhe246" path="res://assets/scripts/entities/enemies/humanoid/humanoid.gd" id="1_ox25x"]
[ext_resource type="Texture2D" uid="uid://caq1ig4sdicos" path="res://assets/gfx/Puny-Characters/Layer 0 - Skins/Orc2.png" id="2_ox25x"]
[ext_resource type="Texture2D" uid="uid://dx1fovugabbwc" path="res://assets/gfx/Puny-Characters/Layer 1 - Shoes/IronBoots.png" id="3_1un2j"]
[ext_resource type="Texture2D" uid="uid://c23gdx52uxdsu" path="res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Viking Body/JarlBody.png" id="4_qlq8y"]
[ext_resource type="Texture2D" uid="uid://0wyhap0ywxso" path="res://assets/gfx/Puny-Characters/Layer 3 - Gloves/GlovesMaple.png" id="5_ox25x"]
[ext_resource type="Texture2D" uid="uid://dnvh3mjkmxdfv" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Facial Hairstyles/Beardstyle2Brown.png" id="6_bt2gh"]
[ext_resource type="Texture2D" uid="uid://3yp6125ggjxp" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Hairstyles/M Hairstyle 2/MHairstyle2Red.png" id="7_x5lcf"]
[ext_resource type="Texture2D" uid="uid://dh3uvf05breb8" path="res://assets/gfx/Puny-Characters/Layer 5 - Eyes/Eye Color/EyecolorCyan.png" id="8_ap4nq"]
[ext_resource type="Texture2D" uid="uid://1mr6h7tkw80c" path="res://assets/gfx/Puny-Characters/Layer 5 - Eyes/Eyelashes/MEyelash2.png" id="9_77mwg"]
[ext_resource type="Texture2D" uid="uid://dxfp3lf1vck5p" path="res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Assasin/ThiefBandanaLime.png" id="10_4vah4"]
[ext_resource type="Texture2D" uid="uid://ds4dmxn8boxdq" path="res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/GoblinEars2.png" id="11_1phnd"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_ycatx"]
size = Vector2(8, 7)
[sub_resource type="Gradient" id="Gradient_iinuc"]
offsets = PackedFloat32Array(0.272727, 0.518519, 0.750842)
colors = PackedColorArray(0, 0, 0, 0.784314, 0, 0, 0, 0.615686, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_ox25x"]
gradient = SubResource("Gradient_iinuc")
width = 32
height = 32
fill = 1
fill_from = Vector2(0.517391, 0.456522)
fill_to = Vector2(0.944444, 0.106838)
[sub_resource type="Gradient" id="Gradient_8cy7b"]
offsets = PackedFloat32Array(0)
colors = PackedColorArray(0, 0, 0, 0.466667)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_ox25x"]
gradient = SubResource("Gradient_8cy7b")
width = 16
[sub_resource type="Gradient" id="Gradient_lehs0"]
offsets = PackedFloat32Array(1)
colors = PackedColorArray(0.960784, 0.0352941, 0.215686, 0.529412)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_5grkw"]
gradient = SubResource("Gradient_lehs0")
width = 16
[node name="Humanoid" type="CharacterBody2D"]
script = ExtResource("1_ox25x")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, 3.5)
shape = SubResource("RectangleShape2D_ycatx")
[node name="Sprite2DBody" type="Sprite2D" parent="."]
texture = ExtResource("2_ox25x")
hframes = 29
vframes = 8
[node name="Sprite2DShoes" type="Sprite2D" parent="."]
texture = ExtResource("3_1un2j")
hframes = 29
vframes = 8
[node name="Sprite2DClothes" type="Sprite2D" parent="."]
texture = ExtResource("4_qlq8y")
hframes = 29
vframes = 8
[node name="Sprite2DGloves" type="Sprite2D" parent="."]
texture = ExtResource("5_ox25x")
hframes = 29
vframes = 8
[node name="Sprite2DFacialHair" type="Sprite2D" parent="."]
texture = ExtResource("6_bt2gh")
hframes = 29
vframes = 8
[node name="Sprite2DHair" type="Sprite2D" parent="."]
texture = ExtResource("7_x5lcf")
hframes = 29
vframes = 8
[node name="Sprite2DEyes" type="Sprite2D" parent="."]
texture = ExtResource("8_ap4nq")
hframes = 29
vframes = 8
[node name="Sprite2DEyeLashes" type="Sprite2D" parent="."]
texture = ExtResource("9_77mwg")
hframes = 29
vframes = 8
[node name="Sprite2DHeadgear" type="Sprite2D" parent="."]
texture = ExtResource("10_4vah4")
hframes = 29
vframes = 8
[node name="Sprite2DAddOns" type="Sprite2D" parent="."]
texture = ExtResource("11_1phnd")
hframes = 29
vframes = 8
[node name="Shadow" type="Sprite2D" parent="."]
z_index = -1
position = Vector2(0, 7)
scale = Vector2(0.460937, 0.125)
texture = SubResource("GradientTexture2D_ox25x")
[node name="TextureProgressBarHealth" type="TextureProgressBar" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -8.0
offset_top = -12.0
offset_right = 8.0
offset_bottom = -11.0
grow_horizontal = 2
grow_vertical = 2
value = 80.0
texture_under = SubResource("GradientTexture1D_ox25x")
texture_progress = SubResource("GradientTexture1D_5grkw")

View File

@@ -1,9 +0,0 @@
extends CharacterBody2D
func _ready() -> void:
pass
func _process(delta: float) -> void:
pass

View File

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

View File

@@ -1,675 +0,0 @@
extends CharacterBody2D
@onready var coin_scene = preload("res://assets/scripts/entities/pickups/coin.tscn")
@onready var loot_scene = preload("res://assets/scripts/entities/pickups/loot.tscn")
@onready var blood_scene = preload("res://assets/scripts/components/blood_clot.tscn")
@onready var damage_number_scene = preload("res://assets/scripts/components/damage_number.tscn")
@onready var animation_player = $AnimationPlayer
@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D
@onready var sprite = $RenderViewport/Sprite2D
var above_texture: ImageTexture
var player_sprite_size = Vector2(32, 32)
@onready var render_viewport = $RenderViewport
@onready var combined_sprite = $CombinedSprite
var entity_id: int = 1
var chase_speed: float = 35.0 # Slightly slower than goblin
var patrol_speed: float = 18.0
var current_speed: float = 0.0
var acceleration: float = 100.0
var alert_radius: float = 70.0 # Distance to first notice player
var tracking_radius: float = 110.0 # Distance to keep chasing player
var vision_angle: float = 360.0 # Slimes see all around them
var stats = CharacterStats.new()
var taking_damage_timer: float = 0.0
var damage_flash_duration: float = 0.2
var has_spotted_player: bool = false
var knockback_timer: float = 0.0
var knockback_duration: float = 0.2
var minimum_chase_timer: float = 0.0
const MINIMUM_CHASE_DURATION: float = 4.0
# Add this near your other class variables
var _position_update_timer: float = 0.0
const POSITION_UPDATE_INTERVAL: float = 0.2
var network_update_timer := 0.0
const NETWORK_UPDATE_INTERVAL := 0.05 # 20 times per second
enum State {
IDLE,
CHASE,
INVESTIGATE,
PATROL,
DAMAGE
}
signal signal_died(enemy: Node2D)
var current_state = State.IDLE
var last_known_player_pos: Vector2
var idle_timer: float = 0.0
var patrol_timer: float = 0.0
var idle_duration: float = 2.0 # How long to idle between actions
var patrol_duration: float = 3.0 # How long to walk before idling
var patrol_point: Vector2
# Add these variables near the top with other variables
var positionZ = 0.0
var velocityZ = 0.0
var accelerationZ = -400.0 # Gravity
var bounceRestitution = 0.6
var minBounceVelocity = 50.0
var jumpVelocity = 120.0 # Initial upward velocity when jumping
var jumpCooldown = 0.8 # Time between jumps
var jumpTimer = 0.0 # Track time since last jump
var attack_radius: float = 30.0 # Distance at which slime will jump at player
# Add near the top with other variables
@onready var sync_next_path_position: Vector2
# Add near the top with other variables
@export var sync_position: Vector2
@export var sync_velocity: Vector2
@export var sync_state: int
@export var sync_hp: int
@export var sync_positionZ: float
@export var sync_velocityZ: float
# Add monitoring state to sync variables at the top
@export var sync_area2d_monitoring: bool = false
# Add near the top with other sync variables
@export var sync_animation: String = ""
var positionInt = Vector2i(0, 0)
var previousFramePosition = Vector2i(-1, 0)
var previousHadValue = -1
func update_above_texture():
positionInt = Vector2i(position)
if previousFramePosition == positionInt:
return
previousFramePosition = positionInt
var image: Image = null
# Loop over the 32x32 area and get tiles from the TileMapAbove
var tma: TileMapLayer = get_tree().current_scene.get_node("TileMapAbove")
if tma != null:
# Adjust the start position based on the player's global position and movement
var startPos = global_position - Vector2(16, 16)
var pieceX = int(global_position.x) % 16
var pieceY = int(global_position.y) % 16
var startTilePos = startPos
var extraX = 16 - pieceX
var extraY = 16 - pieceY
# Loop through 3x3 tile grid
for y in range(0, 32 + extraY, 16): # Step by 16 pixels (tile size)
for x in range(0, 32 + extraX, 16):
var pixel_pos = startTilePos + Vector2(x, y)
var tile_pos = tma.local_to_map(pixel_pos)
if get_tile_texture(tma, tile_pos):
if image == null:
image = above_texture.get_image()
image.fill(Color.from_rgba8(0, 0, 0, 0)) # fill with transparent pixels before we grab from the tilemaplayer
previousHadValue = 1
var tile_image = atlas_texture.get_image()
var dest_pos = Vector2(x - pieceX, y - pieceY)
image.blit_rect(tile_image, Rect2(Vector2.ZERO, tma.tile_set.tile_size), dest_pos)
if image == null and previousHadValue == 1:
previousHadValue = -1
image = above_texture.get_image()
image.fill(Color.from_rgba8(0, 0, 0, 0)) # fill with transparent pixels before we grab from the tilemaplayer
if image != null:
# Recreate the texture from the modified image
above_texture = ImageTexture.create_from_image(image)
$BodyAboveSprite.material.set_shader_parameter("above_texture", above_texture)
var atlas_texture = AtlasTexture.new()
# New function to get the tile texture
func get_tile_texture(tilemap: TileMapLayer, tile_pos: Vector2i) -> AtlasTexture:
var tile_id = tilemap.get_cell_source_id(tile_pos)
if tile_id == -1:
return null # Return null if no tile is present
var tile_atlas_coords = tilemap.get_cell_atlas_coords(tile_pos)
var tile_size = tilemap.tile_set.tile_size
var texture = tilemap.tile_set.get_source(tile_id).texture
var region = Rect2(tile_atlas_coords * tile_size, tile_size)
atlas_texture.atlas = texture
atlas_texture.region = region
return atlas_texture
func _ready() -> void:
add_to_group("enemies")
$BodyAboveSprite.visible = false
combined_sprite.texture = render_viewport.get_texture()
#$BodyAboveSprite.texture = render_viewport.get_texture()
#$BodyAboveSprite.visible = true
$Area2DDealDamage.set_deferred("monitoring", false)
var img: Image = Image.create(32, 32, false, Image.FORMAT_RGBA8)
img.fill(Color.from_rgba8(0, 0, 0, 0))
above_texture = ImageTexture.create_from_image(img)
var mat = $BodyAboveSprite.material.duplicate()
$BodyAboveSprite.material = mat
$BodyAboveSprite.material.set_shader_parameter("above_texture", above_texture)
stats.hp = 2
stats.maxhp = 2
stats.damage = 1
stats.defense = 0
# Configure NavigationAgent
navigation_agent.path_desired_distance = 4.0
navigation_agent.target_desired_distance = 4.0
navigation_agent.path_max_distance = 90.0
navigation_agent.avoidance_enabled = true
navigation_agent.neighbor_distance = 90.0
navigation_agent.max_neighbors = 10
navigation_agent.time_horizon = 1.0
navigation_agent.max_speed = chase_speed
# Start in idle state
current_state = State.IDLE
idle_timer = idle_duration
# Enable navigation debug in debug builds
if OS.is_debug_build():
navigation_agent.debug_enabled = true
func _draw() -> void:
if OS.is_debug_build():
# Draw alert radius
draw_arc(Vector2.ZERO, alert_radius, 0, TAU, 32, Color(1, 0, 0, 0.2), 2.0)
# Draw tracking radius
draw_arc(Vector2.ZERO, tracking_radius, 0, TAU, 32, Color(0, 1, 0, 0.2), 2.0)
func is_entity_in_camera_view() -> bool:
var camera = get_viewport().get_camera_2d()
if camera == null:
return false # No active camera
# Get the camera's visible rectangle in global coordinates
var camera_rect = Rect2(
camera.global_position - (camera.get_zoom() * camera.get_viewport_rect().size) / 2,
camera.get_zoom() * camera.get_viewport_rect().size
)
# Check if the player's position is within the camera's visible rectangle
return camera_rect.has_point(global_position)
func _physics_process(delta: float) -> void:
if multiplayer.is_server():
# Server-side logic
# ... existing movement/AI code ...
# Only send updates at fixed intervals and when there's meaningful change
network_update_timer += delta
if network_update_timer >= NETWORK_UPDATE_INTERVAL:
network_update_timer = 0.0
# Only sync if there's significant movement or state change
if (position.distance_squared_to(sync_position) > 1.0 or
velocity.length_squared() > 1.0 or
stats.hp != sync_hp):
# Send to all clients
sync_slime_movement.rpc(global_position, velocity, stats.hp, current_state)
# Update last synced values
sync_position = global_position
sync_hp = stats.hp
if !multiplayer.is_server():
# Clients update their position based on sync data
if sync_position != Vector2.ZERO:
position = position.lerp(sync_position, 0.5)
if sync_velocity != Vector2.ZERO:
velocity = sync_velocity
positionZ = sync_positionZ
velocityZ = sync_velocityZ
if stats.hp != sync_hp:
stats.hp = sync_hp
if current_state != sync_state:
current_state = sync_state as State
update_sprite_scale()
return
if knockback_timer > 0:
knockback_timer -= delta
if knockback_timer <= 0:
velocity = Vector2.ZERO
stats.is_invulnerable = false
knockback_timer = 0
if taking_damage_timer > 0:
taking_damage_timer -= delta
if taking_damage_timer <= 0:
taking_damage_timer = 0
if stats.hp > 0:
# Update jump timer
if jumpTimer > 0:
jumpTimer -= delta
# Update Z position
velocityZ += accelerationZ * delta
positionZ += velocityZ * delta
# Ground collision and bounce
if positionZ <= 0:
positionZ = 0
if animation_player.current_animation != "take_damage" and animation_player.current_animation != "die":
if abs(velocityZ) > minBounceVelocity:
velocityZ = -velocityZ * bounceRestitution
velocityZ = 0
animation_player.play("land")
animation_player.queue("idle")
$Area2DDealDamage.set_deferred("monitoring", false)
else:
velocityZ = 0
if current_state == State.CHASE and jumpTimer <= 0:
var closest_player = find_closest_player()
if closest_player and global_position.distance_to(closest_player.global_position) < attack_radius:
velocityZ = jumpVelocity
jumpTimer = jumpCooldown
$SfxJump.pitch_scale = randf_range(1.0, 1.2)
$SfxJump.play()
animation_player.play("jump")
$Area2DDealDamage.set_deferred("monitoring", true)
# Make sure idle animation is playing when not moving
if velocity.length() < 0.1 and current_state != State.DAMAGE and stats.hp > 0:
if not animation_player.is_playing() or animation_player.current_animation != "idle":
animation_player.play("idle")
match current_state:
State.IDLE:
handle_idle_state(delta)
State.PATROL:
handle_patrol_state(delta)
State.CHASE:
handle_chase_state(delta)
State.INVESTIGATE:
handle_investigate_state(delta)
State.DAMAGE:
handle_damage_state(delta)
move_and_slide()
update_sprite_scale()
func update_sprite_scale() -> void:
# Calculate scale based on height
# Maximum height will have scale 1.3, ground will have scale 1.0
var height_factor = positionZ / 50.0 # Assuming 50 is max height
var sc = 1.0 + (0.3 * height_factor)
$CombinedSprite.scale = Vector2(sc, sc)
$BodyAboveSprite.scale = Vector2(sc, sc)
# Update Y position based on height
$CombinedSprite.position.y = -positionZ # Negative because higher Y is down in Godot
$BodyAboveSprite.position.y = -positionZ
var shadowFactor = positionZ / 30.0
$Sprite2DShadow.modulate.a = 1 - shadowFactor
func handle_idle_state(delta: float) -> void:
var new_velocity = velocity.move_toward(Vector2.ZERO, acceleration * delta)
#navigation_agent.set_velocity_forced(new_velocity)
navigation_agent.set_velocity(new_velocity)
# Check for player first
var closest_player = find_closest_player()
if closest_player and global_position.distance_to(closest_player.global_position) < alert_radius:
current_state = State.CHASE
return
idle_timer -= delta
if idle_timer <= 0:
current_state = State.PATROL
patrol_timer = patrol_duration
# Set random patrol point within radius
var random_angle = randf() * TAU
var random_distance = randf_range(30, 100)
patrol_point = global_position + Vector2.from_angle(random_angle) * random_distance
navigation_agent.target_position = patrol_point
func handle_patrol_state(delta: float) -> void:
patrol_timer -= delta
# Check for player first
var closest_player = find_closest_player()
if closest_player and global_position.distance_to(closest_player.global_position) < alert_radius:
current_state = State.CHASE
return
if not navigation_agent.is_navigation_finished():
var next_path_position: Vector2 = navigation_agent.get_next_path_position()
var direction = global_position.direction_to(next_path_position)
var new_velocity = navigation_agent.velocity.move_toward(direction * patrol_speed, acceleration * delta)
navigation_agent.set_velocity(new_velocity)
else:
# Only go to idle when we've reached our destination
current_state = State.IDLE
idle_timer = idle_duration
func handle_chase_state(delta: float) -> void:
_position_update_timer += delta
#var res = navigation_agent.get_current_navigation_result()
#print("current nav result:", res.path)
if not navigation_agent.is_navigation_finished():
var next_path_position: Vector2 = navigation_agent.get_next_path_position()
var direction = global_position.direction_to(next_path_position)
var new_velocity = navigation_agent.velocity.move_toward(direction * chase_speed, acceleration * delta)
navigation_agent.set_velocity(new_velocity)
if _position_update_timer >= POSITION_UPDATE_INTERVAL:
_position_update_timer = 0.0
var closest_player = find_closest_player()
if closest_player:
navigation_agent.target_position = closest_player.global_position # update navigation agent pos
else:
current_state = State.IDLE
idle_timer = idle_duration
func handle_investigate_state(delta: float) -> void:
if not navigation_agent.is_navigation_finished():
var next_path_position: Vector2 = navigation_agent.get_next_path_position()
var direction = global_position.direction_to(next_path_position)
var new_velocity = navigation_agent.velocity.move_toward(direction * patrol_speed, acceleration * delta)
navigation_agent.set_velocity(new_velocity)
else:
current_state = State.IDLE
idle_timer = idle_duration
func handle_damage_state(_delta: float) -> void:
if knockback_timer <= 0:
var closest_player = find_closest_player()
if closest_player:
current_state = State.CHASE
else:
current_state = State.IDLE
idle_timer = idle_duration
func find_closest_player() -> Node2D:
var players = get_tree().get_nodes_in_group("players")
var closest_player = null
var closest_distance = tracking_radius
for player in players:
var distance = global_position.distance_to(player.global_position)
if distance < closest_distance:
closest_player = player
closest_distance = distance
if not has_spotted_player:
has_spotted_player = true
if closest_player and minimum_chase_timer > 0:
minimum_chase_timer -= get_physics_process_delta_time()
return closest_player
elif closest_distance <= tracking_radius:
return closest_player
return null
@rpc("any_peer")
func take_damage(iBody: Node2D, iByWhoOrWhat: Node2D) -> void:
if !stats.is_invulnerable and stats.hp > 0:
var damage_amount = 1.0
stats.take_damage(damage_amount)
stats.is_invulnerable = true
knockback_timer = knockback_duration
current_state = State.DAMAGE
taking_damage_timer = damage_flash_duration
# Calculate knockback direction from the damaging body
var knockback_direction = (global_position - iBody.global_position).normalized()
velocity = knockback_direction * 80.0
# Create damage number
if multiplayer.is_server():
spawn_damage_number(damage_amount)
spawn_damage_number.rpc(damage_amount)
if stats.hp <= 0:
if iByWhoOrWhat != null and iByWhoOrWhat is CharacterBody2D:
if "stats" in iByWhoOrWhat:
iByWhoOrWhat.stats.add_xp(1)
var exp_number = damage_number_scene.instantiate() as Label
if exp_number:
if "direction" in exp_number:
exp_number.direction = Vector2(0, -1)
exp_number.label = "+ 1 EXP"
exp_number.move_duration = 1.0
if "color" in exp_number:
exp_number.color = Color.WEB_GREEN
get_tree().current_scene.call_deferred("add_child", exp_number)
exp_number.global_position = global_position + Vector2(0, -16)
pass
if multiplayer.is_server():
$Area2DDealDamage.set_deferred("monitoring", false) # we should no longer be able to take damage from the slime.
signal_died.emit(self)
remove_enemy.rpc()
remove_enemy() # Call locally for server
else:
if multiplayer.is_server():
play_damage_animation()
play_damage_animation.rpc()
@rpc("reliable")
func remove_enemy():
if $SfxJump.playing:
$SfxJump.stop()
if multiplayer.is_server():
# Register this enemy as defeated before removing it
GameManager.register_defeated_enemy.rpc(entity_id)
# Generate loot items array
var loot_items = []
for i in range(randi_range(1, 2 + stats.coin)): # Random number of coins
var angle = randf_range(0, TAU)
var speed = randf_range(50, 100)
var velZ = randf_range(100, 200)
loot_items.append({
"type": 0, # Coin type
"angle": angle,
"speed": speed,
"velocityZ": velZ
})
# Randomly add item loot
if randf() < 0.4: # 40% chance for item
var angle = randf_range(0, TAU)
var speed = randf_range(50, 100)
var velZ = randf_range(100, 200)
loot_items.append({
"type": randi_range(1, 7), # Random item type
"angle": angle,
"speed": speed,
"velocityZ": velZ
})
# Spawn loot locally on server first
spawn_loot(loot_items)
# Then tell clients to spawn the same loot
spawn_loot.rpc(loot_items)
$AnimationPlayer.play("die")
$Sprite2DShadow.visible = false
$SfxDie.play()
for i in 6:
var angle = randf_range(0, TAU)
var speed = randf_range(50, 100)
var initial_velocityZ = randf_range(50, 90)
var b = blood_scene.instantiate() as CharacterBody2D
b.get_node("Sprite2D").modulate = Color(0, 0.8, 0, 1.0)
b.scale = Vector2(randf_range(0.3, 2), randf_range(0.3, 2))
b.global_position = global_position
# Set initial velocities from the synchronized data
var direction = Vector2.from_angle(angle)
b.velocity = direction * speed
b.velocityZ = initial_velocityZ
get_parent().call_deferred("add_child", b)
await $AnimationPlayer.animation_finished
if $SfxDie.playing:
$SfxDie.stop()
call_deferred("queue_free")
# Modify the sync_slime_state RPC to include monitoring state and animation
@rpc("authority", "unreliable")
func sync_slime_movement(pos: Vector2, vel: Vector2, hp: int, state: int):
if not multiplayer.is_server():
sync_position = pos
sync_velocity = vel
sync_hp = hp
sync_state = state
@rpc("unreliable")
func sync_path_position(pos: Vector2):
if not multiplayer.is_server():
sync_next_path_position = pos
func _on_area_2d_deal_damage_body_entered(body: Node2D) -> void:
if body is Area2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy
body.take_damage(self, self)
pass
pass # Replace with function body.
@rpc("reliable")
func spawn_loot(loot_items: Array):
for lootItem in loot_items:
if lootItem.type == 0: # Coin
var coin = coin_scene.instantiate()
GameManager.get_node("Coins").call_deferred("add_child", coin)
#get_parent().call_deferred("add_child", coin)
coin.global_position = global_position
var direction = Vector2.from_angle(lootItem.angle)
coin.velocity = direction * lootItem.speed
coin.velocityZ = lootItem.velocityZ
else: # Item loot
var item = Item.new()
if lootItem.type == 1:
item.item_type = Item.ItemType.Equippable
item.equipment_type = Item.EquipmentType.ARMOUR
item.item_name = "Leather Armour"
item.description = "A nice leather armour"
item.spriteFrame = 12
item.modifiers["def"] = 2
item.equipmentPath = "res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Tunic Body/BrownTunic.png"
elif lootItem.type == 2:
item.item_type = Item.ItemType.Equippable
item.equipment_type = Item.EquipmentType.HEADGEAR
item.item_name = "Leather helm"
item.description = "A nice leather helm"
item.spriteFrame = 31
item.modifiers["def"] = 1
item.equipmentPath = "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Mage/MageHatRed.png"
elif lootItem.type == 3:
item.item_type = Item.ItemType.Equippable
item.equipment_type = Item.EquipmentType.MAINHAND
item.weapon_type = Item.WeaponType.SWORD
item.item_name = "Dagger"
item.description = "A sharp dagger"
item.spriteFrame = 5 * 20 + 10
item.modifiers["dmg"] = 2
elif lootItem.type == 4:
item.item_type = Item.ItemType.Equippable
item.equipment_type = Item.EquipmentType.MAINHAND
item.weapon_type = Item.WeaponType.AXE
item.item_name = "Hand Axe"
item.description = "A sharp hand axe"
item.spriteFrame = 5 * 20 + 11
item.modifiers["dmg"] = 4
elif lootItem.type == 5:
item.item_type = Item.ItemType.Equippable
item.equipment_type = Item.EquipmentType.OFFHAND
item.weapon_type = Item.WeaponType.AMMUNITION
item.quantity = 13
item.can_have_multiple_of = true
item.item_name = "Iron Arrow"
item.description = "A sharp arrow made of iron and feathers from pelican birds"
item.spriteFrame = 7 * 20 + 11
item.modifiers["dmg"] = 3
elif lootItem.type == 6:
item.item_type = Item.ItemType.Equippable
item.equipment_type = Item.EquipmentType.MAINHAND
item.weapon_type = Item.WeaponType.BOW
item.item_name = "Wooden Bow"
item.description = "A wooden bow made of elfish lembas trees"
item.spriteFrame = 6 * 20 + 16
item.modifiers["dmg"] = 3
elif lootItem.type == 7:
item.item_type = Item.ItemType.Equippable
item.equipment_type = Item.EquipmentType.BOOTS
item.weapon_type = Item.WeaponType.NONE
item.item_name = "Sandals"
item.description = "A pair of shitty sandals"
item.equipmentPath = "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/ShoesBrown.png"
item.spriteFrame = 2 * 20 + 10
item.modifiers["def"] = 1
var loot = loot_scene.instantiate()
#get_parent().call_deferred("add_child", loot)
GameManager.get_node("Loot").call_deferred("add_child", loot)
loot.setItem(item)
loot.global_position = global_position
var direction = Vector2.from_angle(lootItem.angle)
loot.velocity = direction * lootItem.speed
loot.velocityZ = lootItem.velocityZ
@rpc("reliable")
func spawn_coins(coin_data: Array):
for data in coin_data:
var coin = coin_scene.instantiate()
GameManager.get_node("Coins").call_deferred("add_child", coin)
#get_parent().add_child(coin)
coin.global_position = global_position
# Set initial velocities from the synchronized data
var direction = Vector2.from_angle(data.angle)
coin.velocity = direction * data.speed
coin.velocityZ = data.velocityZ
# Add new RPCs for visual effects
@rpc("authority", "reliable")
func spawn_damage_number(damage_amount: float):
if damage_number_scene:
var damage_number = damage_number_scene.instantiate() as Label
if damage_number:
get_tree().current_scene.add_child(damage_number)
damage_number.global_position = global_position + Vector2(0, -16)
if "direction" in damage_number:
damage_number.direction = Vector2(0, -1)
if "label" in damage_number:
damage_number.label = str(damage_amount)
if "color" in damage_number:
damage_number.color = Color(1, 0.3, 0.3, 1)
@rpc("authority", "reliable")
func play_damage_animation():
$SfxDie.play()
$AnimationPlayer.play("take_damage")
$AnimationPlayer.queue("idle")
func _on_area_2d_deal_damage_area_entered(area: Area2D) -> void:
if area is Area2D and area.get_parent().stats.is_invulnerable == false and area.get_parent().stats.hp > 0: # hit an enemy
area.get_parent().take_damage(self, self)
pass
pass # Replace with function body.
func _on_navigation_agent_2d_velocity_computed(safe_velocity: Vector2) -> void:
velocity = safe_velocity
pass # Replace with function body.

View File

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

View File

@@ -1,235 +0,0 @@
[gd_scene load_steps=22 format=3 uid="uid://b8etn6xqbg57o"]
[ext_resource type="Script" uid="uid://djftsndq45u0a" path="res://assets/scripts/entities/enemies/slime/slime.gd" id="1_w201s"]
[ext_resource type="Texture2D" uid="uid://csr5k0etreqbf" path="res://assets/gfx/enemies/Slime.png" id="1_ytere"]
[ext_resource type="Shader" uid="uid://b1k834hb0xm2w" path="res://assets/shaders/draw_sprite_above.gdshader" id="3_taldv"]
[ext_resource type="Texture2D" uid="uid://dxk8dcdqrft0v" path="res://assets/gfx/gibb_sprite.png" id="4_orljr"]
[ext_resource type="AudioStream" uid="uid://oly1occx067k" path="res://assets/audio/sfx/enemies/slime/slime_die.mp3" id="5_trpa5"]
[ext_resource type="AudioStream" uid="uid://dr5va4d7psjk6" path="res://assets/audio/sfx/enemies/slime/slime_die2.mp3" id="6_thd0o"]
[ext_resource type="AudioStream" uid="uid://c50k7hswlkf8r" path="res://assets/audio/sfx/enemies/slime/jump.mp3" id="7_thd0o"]
[sub_resource type="Gradient" id="Gradient_cjpjf"]
offsets = PackedFloat32Array(0.487589, 0.572695)
colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_w201s"]
gradient = SubResource("Gradient_cjpjf")
width = 32
height = 32
fill = 1
fill_from = Vector2(0.529915, 0.482906)
[sub_resource type="Animation" id="Animation_w201s"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [0]
}
[sub_resource type="Animation" id="Animation_trpa5"]
resource_name = "die"
length = 0.4
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.3),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 1,
"values": [9, 10, 11, 12, 13]
}
[sub_resource type="Animation" id="Animation_ytere"]
resource_name = "idle"
length = 0.8
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 1]
}
[sub_resource type="Animation" id="Animation_roai2"]
resource_name = "jump"
length = 0.3
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0.0666667, 0.166667),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [4, 5]
}
[sub_resource type="Animation" id="Animation_taldv"]
resource_name = "land"
length = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [7, 8]
}
[sub_resource type="Animation" id="Animation_orljr"]
resource_name = "take_damage"
length = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("RenderViewport/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.133333),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [6, 9]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_roai2"]
_data = {
&"RESET": SubResource("Animation_w201s"),
&"die": SubResource("Animation_trpa5"),
&"idle": SubResource("Animation_ytere"),
&"jump": SubResource("Animation_roai2"),
&"land": SubResource("Animation_taldv"),
&"take_damage": SubResource("Animation_orljr")
}
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_w201s"]
radius = 3.0
height = 8.0
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_roai2"]
radius = 2.0
height = 8.0
[sub_resource type="RectangleShape2D" id="RectangleShape2D_w201s"]
size = Vector2(8, 8)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_trpa5"]
shader = ExtResource("3_taldv")
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_1dxl4"]
streams_count = 2
stream_0/stream = ExtResource("5_trpa5")
stream_1/stream = ExtResource("6_thd0o")
[node name="Slime" type="CharacterBody2D"]
z_index = 10
z_as_relative = false
y_sort_enabled = true
collision_layer = 0
collision_mask = 192
script = ExtResource("1_w201s")
[node name="Sprite2DShadow" type="Sprite2D" parent="."]
z_index = 1
z_as_relative = false
position = Vector2(4.76837e-07, 5)
scale = Vector2(0.47, 0.157)
texture = SubResource("GradientTexture2D_w201s")
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]
path_desired_distance = 4.0
target_desired_distance = 4.0
navigation_layers = 3
avoidance_enabled = true
radius = 4.0
neighbor_distance = 100.0
time_horizon_obstacles = 0.36
max_speed = 4000.0
avoidance_layers = 3
avoidance_mask = 128
[node name="CombinedSprite" type="Sprite2D" parent="."]
position = Vector2(0, -1)
texture = ExtResource("4_orljr")
[node name="RenderViewport" type="SubViewport" parent="."]
transparent_bg = true
size = Vector2i(32, 32)
[node name="Sprite2D" type="Sprite2D" parent="RenderViewport"]
texture = ExtResource("1_ytere")
centered = false
hframes = 15
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_roai2")
}
next/land = &"idle"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
visible = false
rotation = -1.5708
shape = SubResource("CapsuleShape2D_w201s")
[node name="Area2DDealDamage" type="Area2D" parent="."]
visible = false
collision_layer = 0
collision_mask = 512
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DDealDamage"]
rotation = -1.5708
shape = SubResource("CapsuleShape2D_roai2")
debug_color = Color(0.7, 0, 0.145721, 0.42)
[node name="Area2DTakeDamage" type="Area2D" parent="."]
visible = false
collision_layer = 256
collision_mask = 0
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DTakeDamage"]
shape = SubResource("RectangleShape2D_w201s")
debug_color = Color(0.7, 0, 0.0697005, 0.42)
[node name="BodyAboveSprite" type="Sprite2D" parent="."]
visible = false
z_index = 14
z_as_relative = false
material = SubResource("ShaderMaterial_trpa5")
position = Vector2(0, -1)
texture = ExtResource("4_orljr")
[node name="SfxDie" type="AudioStreamPlayer2D" parent="."]
stream = SubResource("AudioStreamRandomizer_1dxl4")
[node name="SfxJump" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("7_thd0o")
volume_db = -6.911
pitch_scale = 1.12
[connection signal="velocity_computed" from="NavigationAgent2D" to="." method="_on_navigation_agent_2d_velocity_computed"]
[connection signal="area_entered" from="Area2DDealDamage" to="." method="_on_area_2d_deal_damage_area_entered"]
[connection signal="body_entered" from="Area2DDealDamage" to="." method="_on_area_2d_deal_damage_body_entered"]

View File

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

View File

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

View File

@@ -1,126 +0,0 @@
[gd_scene load_steps=13 format=3 uid="uid://v1s8j8vtla1o"]
[ext_resource type="Script" uid="uid://j4ww03kd5vmc" path="res://assets/scripts/entities/pickups/coin.gd" id="1_1mphl"]
[ext_resource type="Texture2D" uid="uid://cimek2qjgoqa1" path="res://assets/gfx/pickups/gold_coin.png" id="1_lavav"]
[ext_resource type="AudioStream" uid="uid://b60bke4f5uw4v" path="res://assets/audio/sfx/pickups/coin_pickup.mp3" id="3_cjpjf"]
[ext_resource type="AudioStream" uid="uid://brl8ivwb1l5i7" path="res://assets/audio/sfx/pickups/coin_drop_01.wav.mp3" id="4_06bcn"]
[sub_resource type="Gradient" id="Gradient_06bcn"]
colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_06bcn"]
gradient = SubResource("Gradient_06bcn")
width = 8
height = 8
fill = 1
fill_from = Vector2(0.529915, 0.482906)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1mphl"]
size = Vector2(3, 5)
[sub_resource type="Animation" id="Animation_06bcn"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [0]
}
[sub_resource type="Animation" id="Animation_cjpjf"]
resource_name = "idle"
length = 0.6
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_538um"]
_data = {
&"RESET": SubResource("Animation_06bcn"),
&"idle": SubResource("Animation_cjpjf")
}
[sub_resource type="RectangleShape2D" id="RectangleShape2D_cjpjf"]
size = Vector2(5, 7)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_lavav"]
size = Vector2(3, 5)
[node name="Coin" type="CharacterBody2D"]
z_index = 10
z_as_relative = false
y_sort_enabled = true
collision_layer = 0
collision_mask = 64
script = ExtResource("1_1mphl")
[node name="Sprite2DShadow" type="Sprite2D" parent="."]
z_index = 1
z_as_relative = false
position = Vector2(0, 3.5)
scale = Vector2(1, 0.125)
texture = SubResource("GradientTexture2D_06bcn")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
visible = false
position = Vector2(0.5, 0.5)
shape = SubResource("RectangleShape2D_1mphl")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("1_lavav")
hframes = 6
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
root_node = NodePath("../Sprite2D")
libraries = {
&"": SubResource("AnimationLibrary_538um")
}
autoplay = "idle"
[node name="Area2DCollision" type="Area2D" parent="."]
visible = false
collision_layer = 0
collision_mask = 64
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DCollision"]
position = Vector2(0.5, 0.5)
shape = SubResource("RectangleShape2D_cjpjf")
[node name="Area2DPickup" type="Area2D" parent="."]
visible = false
collision_layer = 0
collision_mask = 768
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickup"]
position = Vector2(0.5, 0.5)
shape = SubResource("RectangleShape2D_lavav")
debug_color = Color(0.7, 0.682756, 0.184079, 0.42)
[node name="SfxCoinBounce" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("3_cjpjf")
max_polyphony = 6
[node name="SfxCoinCollect" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("4_06bcn")
[connection signal="body_entered" from="Area2DCollision" to="." method="_on_area_2d_collision_body_entered"]
[connection signal="area_entered" from="Area2DPickup" to="." method="_on_area_2d_pickup_area_entered"]
[connection signal="area_exited" from="Area2DPickup" to="." method="_on_area_2d_pickup_area_exited"]
[connection signal="body_entered" from="Area2DPickup" to="." method="_on_area_2d_pickup_body_entered"]
[connection signal="body_exited" from="Area2DPickup" to="." method="_on_area_2d_pickup_body_exited"]

View File

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

View File

@@ -1,79 +0,0 @@
[gd_scene load_steps=9 format=3 uid="uid://buoaism4nuooj"]
[ext_resource type="Script" uid="uid://d2ngv2yvtmnaf" path="res://assets/scripts/entities/pickups/loot.gd" id="1_ws6g6"]
[ext_resource type="Texture2D" uid="uid://hib38y541eog" path="res://assets/gfx/items_n_shit.png" id="2_1uy5x"]
[ext_resource type="AudioStream" uid="uid://umoxmryvbm01" path="res://assets/audio/sfx/cloth/leather_cloth_01.wav.mp3" id="3_4s0gc"]
[sub_resource type="Gradient" id="Gradient_4s0gc"]
colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_1uy5x"]
gradient = SubResource("Gradient_4s0gc")
width = 8
height = 8
fill = 1
fill_from = Vector2(0.529915, 0.482906)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_4s0gc"]
size = Vector2(14, 14)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1uy5x"]
size = Vector2(14, 14)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_nu74k"]
size = Vector2(16, 16)
[node name="Loot" type="CharacterBody2D"]
z_index = 10
z_as_relative = false
y_sort_enabled = true
collision_layer = 1024
collision_mask = 64
script = ExtResource("1_ws6g6")
[node name="Sprite2DShadow" type="Sprite2D" parent="."]
z_index = 1
z_as_relative = false
position = Vector2(-2.38419e-07, 8)
scale = Vector2(2, 0.5)
texture = SubResource("GradientTexture2D_1uy5x")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("2_1uy5x")
hframes = 20
vframes = 14
[node name="SfxPickup" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("3_4s0gc")
[node name="Area2DPickup" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 512
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickup"]
shape = SubResource("RectangleShape2D_4s0gc")
[node name="Label" type="Label" parent="."]
offset_left = -20.0
offset_top = -20.0
offset_right = 20.0
offset_bottom = 3.0
theme_override_font_sizes/font_size = 6
text = "Pick up"
horizontal_alignment = 1
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_1uy5x")
[node name="Area2DCollision" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 64
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DCollision"]
shape = SubResource("RectangleShape2D_nu74k")
[connection signal="area_entered" from="Area2DPickup" to="." method="_on_area_2d_pickup_area_entered"]
[connection signal="area_exited" from="Area2DPickup" to="." method="_on_area_2d_pickup_area_exited"]
[connection signal="body_entered" from="Area2DPickup" to="." method="_on_area_2d_body_entered"]
[connection signal="body_exited" from="Area2DPickup" to="." method="_on_area_2d_body_exited"]
[connection signal="body_entered" from="Area2DCollision" to="." method="_on_area_2d_collision_body_entered"]

View File

@@ -1,66 +0,0 @@
extends Camera2D
var targetResolution = Vector2i(320,200)
var targetZoom:float = 3.0
@export var minTimeToNextShake:float = 0.0
@export var maxTimeToNextShake:float = 4.0
var timeToNextShake:float = 0.0
var currentTimeToNextShake:float = 0.0
var lerpStrength:float = 0.07
@export var randomStrength:float = 4
@export var shakeFade:float = 2.0
@onready var previousShakeFade = shakeFade
@export var is_shaking = false
@export var diminish_shake = true
var shake_strength: float = 0
var rnd = RandomNumberGenerator.new()
func _process(delta: float) -> void:
#($"../CanvasLayer/LabelPlayerName" as Label).offset_top = $"..".position.y - 30
#($"../CanvasLayer/LabelPlayerName" as Label).offset_left = $"..".position.x - 30
var vp_rect:Rect2 = get_viewport_rect()
targetZoom = ceil(vp_rect.size.y / targetResolution.y)
zoom.x = targetZoom
zoom.y = targetZoom
if is_shaking:
currentTimeToNextShake+=delta
if currentTimeToNextShake>=timeToNextShake:
currentTimeToNextShake = 0
timeToNextShake = rnd.randf_range(minTimeToNextShake, maxTimeToNextShake)
apply_shake()
if shake_strength > 0:
shake_strength = lerpf(shake_strength, 0, shakeFade * delta)
offset = randomOffset()
if shake_strength == 0:
is_shaking = false
else:
offset = Vector2(0,0)
pass
func randomOffset() -> Vector2:
return Vector2(rnd.randf_range(-shake_strength, shake_strength), rnd.randf_range(-shake_strength, shake_strength))
func bombShake(iStrength:float = 4.0, iShakeFade:float = 2.0, iWaitTime:float = 0.4):
if !is_shaking:
previousShakeFade = shakeFade
shakeFade = iShakeFade
randomStrength = iStrength
shake_strength = iStrength
$Timer.wait_time = iWaitTime
$Timer.start(0.0)
is_shaking = true
pass
func apply_shake():
shake_strength = randomStrength
func _on_timer_timeout() -> void:
shakeFade = previousShakeFade
is_shaking = false
pass # Replace with function body.

View File

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

View File

@@ -1,32 +0,0 @@
extends MultiplayerSynchronizer
var direction_vector = Vector2(0,0)
@onready var player = $".."
func _ready() -> void:
if get_multiplayer_authority() != multiplayer.get_unique_id():
set_process(false)
set_physics_process(false)
pass
func _physics_process(_delta: float) -> void:
pass
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("Attack"):
attack.rpc()
if Input.is_action_just_pressed("Use"):
use.rpc()
grab.rpc()
$TimerGrab.start()
if Input.is_action_just_released("Use"):
not_use()
if not $TimerGrab.is_stopped():
lift.rpc()
else:
release.rpc()
pass

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,354 +0,0 @@
[gd_scene 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(".:held_entity_path")
properties/11/spawn = true
properties/11/replication_mode = 2
properties/12/path = NodePath(".:grabbed_entity_path")
properties/12/spawn = true
properties/12/replication_mode = 2
properties/13/path = NodePath(".:current_direction")
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" unique_id=642482055]
collision_layer = 512
collision_mask = 704
script = ExtResource("1_sgemx")
[node name="PlayerLight" type="PointLight2D" parent="." unique_id=98233177]
z_index = 10
position = Vector2(-1, -6)
blend_mode = 2
range_layer_max = 2
texture = SubResource("GradientTexture2D_n1hb6")
[node name="PlayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=1561958126]
replication_config = SubResource("SceneReplicationConfig_fgrik")
[node name="Sprite2DShadow" type="Sprite2D" parent="." unique_id=1430953243]
position = Vector2(0, 2)
texture = SubResource("GradientTexture2D_0818e")
[node name="Sprite2DBody" type="Sprite2D" parent="." unique_id=36949699]
material = SubResource("ShaderMaterial_2bw0v")
position = Vector2(0, -5)
texture = ExtResource("3_0818e")
hframes = 35
vframes = 8
[node name="Sprite2DBoots" type="Sprite2D" parent="." unique_id=1502518208]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("5_2bw0v")
hframes = 35
vframes = 8
[node name="Sprite2DArmour" type="Sprite2D" parent="." unique_id=1239356181]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("5_7drg4")
hframes = 35
vframes = 8
[node name="Sprite2DFacialHair" type="Sprite2D" parent="." unique_id=973907314]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("7_2bw0v")
hframes = 35
vframes = 8
[node name="Sprite2DHair" type="Sprite2D" parent="." unique_id=1924405266]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("8_pyh4g")
hframes = 35
vframes = 8
[node name="Sprite2DEyes" type="Sprite2D" parent="." unique_id=1443066557]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("8_68eso")
hframes = 35
vframes = 8
[node name="Sprite2DEyeLashes" type="Sprite2D" parent="." unique_id=691771626]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("9_cvm1n")
hframes = 35
vframes = 8
[node name="Sprite2DAddons" type="Sprite2D" parent="." unique_id=647154359]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("10_o8aek")
hframes = 35
vframes = 8
[node name="Sprite2DHeadgear" type="Sprite2D" parent="." unique_id=831310279]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("11_idlgo")
hframes = 35
vframes = 8
[node name="Sprite2DWeapon" type="Sprite2D" parent="." unique_id=2021209530]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("12_0818e")
hframes = 35
vframes = 8
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=131165090]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_sgemx")
[node name="Camera2D" type="Camera2D" parent="." unique_id=643763897]
zoom = Vector2(3, 3)
position_smoothing_enabled = true
script = ExtResource("4_n1hb6")
[node name="Timer" type="Timer" parent="Camera2D" unique_id=880082893]
[node name="SfxWalk" type="AudioStreamPlayer2D" parent="." unique_id=568890407]
stream = SubResource("AudioStreamRandomizer_40ewq")
volume_db = -12.0
attenuation = 8.28211
bus = &"Sfx"
[node name="TimerWalk" type="Timer" parent="SfxWalk" unique_id=1285633304]
wait_time = 0.3
one_shot = true
[node name="Area2DPickup" type="Area2D" parent="." unique_id=1858677050]
collision_layer = 0
collision_mask = 1536
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickup" unique_id=1519370124]
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="." unique_id=900440685]
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="LabelCurrentAnimation" type="Label" parent="." unique_id=2024783119]
visible = false
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="." unique_id=1694102436]
follow_viewport_enabled = true
[node name="TextureProgressBarHealth" type="TextureProgressBar" parent="." unique_id=1783325028]
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="." unique_id=1749167232]
stream = ExtResource("22_jfw4q")
bus = &"Sfx"
[node name="SfxTakeDamage" type="AudioStreamPlayer2D" parent="." unique_id=956824742]
stream = SubResource("AudioStreamRandomizer_hnhes")
bus = &"Sfx"
[node name="TimerGrab" type="Timer" parent="." unique_id=129649929]
wait_time = 0.2
one_shot = true
[connection signal="timeout" from="Camera2D/Timer" to="Camera2D" method="_on_timer_timeout"]

View File

@@ -1,237 +0,0 @@
[gd_scene load_steps=31 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="Script" uid="uid://co7kkfmgjc54b" path="res://scripts/entities/player/input_synchronizer.gd" id="2_fgrik"]
[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://cvraydpaetsmk" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Facial Hairstyles/Beardstyle1Brown.png" id="6_0iycr"]
[ext_resource type="Texture2D" uid="uid://cojaw33qd0lxj" path="res://assets/gfx/Puny-Characters/Layer 4 - Hairstyle/Hairstyles/M Hairstyle 3/MHairstyle3Black.png" id="7_o33e0"]
[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://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"]
[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(".:player_id")
properties/0/spawn = true
properties/0/replication_mode = 2
properties/1/path = NodePath(".:position")
properties/1/spawn = true
properties/1/replication_mode = 1
properties/2/path = NodePath(".:is_attacking")
properties/2/spawn = true
properties/2/replication_mode = 2
properties/3/path = NodePath(".:is_using")
properties/3/spawn = true
properties/3/replication_mode = 2
properties/4/path = NodePath(".:current_animation")
properties/4/spawn = true
properties/4/replication_mode = 2
properties/5/path = NodePath(".:last_direction")
properties/5/spawn = true
properties/5/replication_mode = 2
properties/6/path = NodePath(".:is_grabbing")
properties/6/spawn = true
properties/6/replication_mode = 2
properties/7/path = NodePath(".:is_lifting")
properties/7/spawn = true
properties/7/replication_mode = 2
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_sgemx"]
properties/0/path = NodePath("InputSynchronizer:direction_vector")
properties/0/spawn = true
properties/0/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_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)
[node name="Player" type="CharacterBody2D"]
collision_layer = 512
collision_mask = 704
script = ExtResource("1_sgemx")
[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="InputSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_sgemx")
script = ExtResource("2_fgrik")
[node name="TimerGrab" type="Timer" parent="InputSynchronizer"]
wait_time = 0.3
one_shot = true
[node name="Sprite2DShadow" type="Sprite2D" parent="."]
position = Vector2(0, 2)
texture = SubResource("GradientTexture2D_0818e")
[node name="Sprite2DBody" type="Sprite2D" parent="."]
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("6_0iycr")
hframes = 35
vframes = 8
[node name="Sprite2DHair" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_8ugno")
position = Vector2(0, -5)
texture = ExtResource("7_o33e0")
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="."]
visible = false
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 = -13.28
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)
[connection signal="timeout" from="Camera2D/Timer" to="Camera2D" method="_on_timer_timeout"]

View File

@@ -1,356 +0,0 @@
[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

@@ -1,51 +0,0 @@
[gd_scene load_steps=8 format=3 uid="uid://dgtfy455abe1t"]
[ext_resource type="Texture2D" uid="uid://7r43xnr812km" path="res://assets/gfx/Puny-Characters/Layer 0 - Skins/Human1.png" id="1_kwarp"]
[ext_resource type="Script" uid="uid://cvvy2s6620mcw" path="res://scripts/entities/player/player.gd" id="1_sgemx"]
[ext_resource type="Script" uid="uid://co7kkfmgjc54b" path="res://scripts/entities/player/input_synchronizer.gd" id="2_fgrik"]
[ext_resource type="Script" uid="uid://yid4hjp68enj" path="res://scripts/entities/player/camera_2d.gd" id="4_n1hb6"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_fgrik"]
properties/0/path = NodePath(".:player_id")
properties/0/spawn = true
properties/0/replication_mode = 2
properties/1/path = NodePath(".:position")
properties/1/spawn = true
properties/1/replication_mode = 2
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_sgemx"]
properties/0/path = NodePath("InputSynchronizer:direction_vector")
properties/0/spawn = true
properties/0/replication_mode = 2
[sub_resource type="RectangleShape2D" id="RectangleShape2D_sgemx"]
size = Vector2(8, 6)
[node name="Player" type="CharacterBody2D"]
collision_layer = 512
collision_mask = 64
script = ExtResource("1_sgemx")
[node name="PlayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_fgrik")
[node name="InputSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_sgemx")
script = ExtResource("2_fgrik")
[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(0, -4)
texture = ExtResource("1_kwarp")
hframes = 29
vframes = 8
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_sgemx")
[node name="Camera2D" type="Camera2D" parent="."]
zoom = Vector2(3, 3)
script = ExtResource("4_n1hb6")
[node name="Timer" type="Timer" parent="Camera2D"]
[connection signal="timeout" from="Camera2D/Timer" to="Camera2D" method="_on_timer_timeout"]

View File

@@ -1,500 +0,0 @@
extends CharacterBody2D
var tileParticleScene = preload("res://scripts/components/TileParticle.tscn")
var liftable = true
var thrown_by = null
var entity_id = ""
var object_name = "pot"
@export var is_being_thrown = false
@export var is_being_lifted = false
@export var is_being_put_down = false
@export var is_being_grabbed = false
@export var is_moving = false
@export var is_spawning = false
var throw_speed = 200
var throw_height = 30
var current_height = 0
var gravity = 800
var holder = null
var flipFromWall = false
# Add Z-axis variables similar to loot.gd
@export var positionZ = 0.0
var velocityZ = 0.0
var accelerationZ = -330.0 # Gravity
var bounceRestitution = 0.3
var minBounceVelocity = 60.0
var destroy_initiated = false
@export var is_destroyed = false
# Smooth lifting variables
var target_position = Vector2.ZERO
var lift_height = 12.0 # Height above player's head
var lift_speed = 10.0 # Speed of smooth movement
var put_down_start_pos = Vector2.ZERO
var put_down_target_pos = Vector2.ZERO
@export var lift_progress = 0.0
var re_enable_collision_after_time = 0.0
var re_enable_time = 0.17
var previousFrameVel = Vector2.ZERO
var hasShownSmokePuffs = false
func _ready() -> void:
if is_spawning:
liftable = false
$Area2DCollision.set_deferred("monitoring", false)
self.set_collision_layer_value(8, false)
self.set_collision_mask_value(9, false)
self.set_collision_mask_value(10, false)
self.set_collision_mask_value(8, false)
update_sprite_scale()
pass
indicate(false)
pass
func _physics_process(delta: float) -> void:
if multiplayer.is_server():
if is_being_thrown:
re_enable_collision_after_time -= delta
if re_enable_collision_after_time <= 0.0:
# enable collisions again
self.set_collision_layer_value(8, true)
self.set_collision_mask_value(9, true)
self.set_collision_mask_value(10, true)
self.set_collision_mask_value(8, true)
re_enable_collision_after_time = 0
# Apply gravity to vertical movement
velocityZ += accelerationZ * delta
positionZ += velocityZ * delta
if positionZ <= 0:
# Pot has hit the ground
positionZ = 0
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if abs(velocityZ) > minBounceVelocity:
velocityZ = -velocityZ * bounceRestitution
else:
velocityZ = 0
is_being_thrown = false
velocity = velocity.lerp(Vector2.ZERO, 0.5)
if velocity.x == 0 and velocity.y == 0:
thrown_by = null
# Move horizontally
var collision = move_and_collide(velocity * delta)
if collision:
if positionZ == 0:
is_being_thrown = false
update_sprite_scale()
elif is_being_put_down:
lift_progress -= delta * lift_speed
if lift_progress <= 0.0:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
lift_progress = 0
is_being_put_down = false
is_being_lifted = false
holder = null
positionZ = 0
# enable collisions again
self.set_collision_layer_value(8, true)
self.set_collision_mask_value(9, true)
self.set_collision_mask_value(10, true)
self.set_collision_mask_value(7, true)
self.set_collision_mask_value(8, true)
$Area2DCollision.set_deferred("monitoring", true)
else:
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:
is_moving = true
#hasShownSmokePuffs = true
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if !$SfxDrag2.playing:
$SfxDrag2.play()
else:
is_moving = false
if $SfxDrag2.playing:
$SfxDrag2.stop()
move_and_collide(velocity * delta)
previousFrameVel = velocity
pass
else: # it just spawned:
# Apply gravity to vertical movement
velocityZ += accelerationZ * delta
positionZ += velocityZ * delta
if positionZ <= 0:
if is_spawning:
is_spawning = false
liftable = true
$Area2DCollision.set_deferred("monitoring", true)
self.set_collision_layer_value(8, true)
self.set_collision_mask_value(9, true)
self.set_collision_mask_value(10, true)
self.set_collision_mask_value(8, true)
# Pot has hit the ground
positionZ = 0
if abs(velocityZ) > 30:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if abs(velocityZ) > minBounceVelocity:
velocityZ = -velocityZ * bounceRestitution
else:
velocityZ = 0
velocity = velocity.lerp(Vector2.ZERO, 0.5)
move_and_collide(velocity * delta)
update_sprite_scale()
pass
else:
# for client:'
if is_being_thrown:
if $SfxDrag2.playing:
$SfxDrag2.stop()
if positionZ <= 0:
if !$SfxLand.playing:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
elif is_being_put_down:
if $SfxDrag2.playing:
$SfxDrag2.stop()
if !$SfxLand.playing:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
elif is_being_lifted:
if $SfxDrag2.playing:
$SfxDrag2.stop()
$GPUParticles2D.emitting = false
elif is_being_grabbed:
if is_moving:
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if !$SfxDrag2.playing:
$SfxDrag2.play()
else:
if $SfxDrag2.playing:
$SfxDrag2.stop()
else:
if is_spawning:
if positionZ <= 0:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
else:
if $SfxLand.playing:
$SfxLand.stop()
if $SfxDrag2.playing:
$SfxDrag2.stop()
pass
update_sprite_scale()
if is_destroyed and !destroy_initiated:
destroy_initiated = true
show_destroy_effect()
pass
pass
func update_sprite_scale() -> void:
# Calculate scale based on height
# Maximum height will have scale 1.3, ground will have scale 1.0
var height_factor = positionZ / 50.0 # Assuming 50 is max height
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
# Also update shadow position and scale
if is_being_lifted:
$Sprite2D.z_as_relative = false
$Sprite2D.z_index = 12
$Sprite2DShadow.offset.y = 0 # Base shadow position
else:
$Sprite2D.z_as_relative = true
$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.modulate.
func throw(direction: Vector2, initial_velocity: float = 200):
self.set_collision_mask_value(7, true)
$Area2DCollision.set_deferred("monitoring", true)
$SfxThrow.play()
is_being_lifted = false
is_being_thrown = true
is_being_put_down = false
thrown_by = holder
holder = null
velocity = direction * initial_velocity
velocityZ = throw_height
positionZ = lift_height
current_height = 0
re_enable_collision_after_time = re_enable_time
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...
holder = new_holder
is_being_grabbed = true
indicate(false)
return true
return false
func release():
holder = null
is_being_grabbed = false
hasShownSmokePuffs = false
indicate(true)
if $SfxDrag2.playing:
$SfxDrag2.stop()
pass
func lift(new_holder: CharacterBody2D):
if (new_holder != holder and holder != null) and "lose_held_entity" in holder:
# steal from holder
holder.lose_held_entity(self)
indicate(false)
$Area2DCollision.set_deferred("monitoring", false)
thrown_by = null
holder = new_holder
# disable collisions
self.set_collision_layer_value(8, false)
self.set_collision_mask_value(7, false)
self.set_collision_mask_value(8, false)
self.set_collision_mask_value(9, false)
self.set_collision_mask_value(10, false)
is_being_lifted = true
is_being_grabbed = false
is_being_thrown = false
is_being_put_down = false
lift_progress = 0.0
velocityZ = 0
$SfxLand.play()
func put_down() -> bool:
if not is_being_lifted or is_being_put_down:
return false
var dropDir = holder.last_direction
dropDir.x *= 12
if dropDir.y > 0:
dropDir.y *= 10
else:
dropDir.y *= 10
put_down_target_pos = holder.global_position + dropDir
# First check: Direct space state query for walls
var space_state = get_world_2d().direct_space_state
var params = PhysicsPointQueryParameters2D.new()
params.position = put_down_target_pos
params.collision_mask = 64 # Layer for walls (usually layer 7)
params.collide_with_areas = true
params.collide_with_bodies = true
var results = space_state.intersect_point(params)
if results.size() > 0:
# Found overlapping walls at target position
return false
# Second check: Line of sight between player and target position
var query = PhysicsRayQueryParameters2D.create(
holder.global_position,
put_down_target_pos,
64 # Wall collision mask
)
query.collide_with_areas = true
query.collide_with_bodies = true
var result = space_state.intersect_ray(query)
if result:
# Hit something between player and target position
return false
# Third check: Make sure we're not placing on top of another pot or object
params.collision_mask = 128 # Layer for pots/objects
results = space_state.intersect_point(params)
if results.size() > 0:
# Found overlapping objects at target position
return false
$Area2DCollision.set_deferred("monitoring", false)
# Position is valid, proceed with putting down
self.set_collision_mask_value(7, true) # instantly reenenable collision with wall
is_being_put_down = true
is_being_lifted = false
put_down_start_pos = global_position
thrown_by = null
holder = null
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)
fade_tween.tween_property($Sprite2D, "modulate:a", 0.0, 0.6)
#await fade_tween.finished
await $SfxShatter.finished
if $SfxLand.playing:
$SfxLand.stop()
if $SfxDrag2.playing:
$SfxDrag2.stop()
$GPUParticles2D.emitting = false
#$SfxShatter.stop()
if multiplayer.is_server():
call_deferred("queue_free")
pass
func create4TileParticles():
var sprite_texture = $Sprite2D.texture
var frame_width = sprite_texture.get_width() / $Sprite2D.hframes
var frame_height = sprite_texture.get_height() / $Sprite2D.vframes
var frame_x = ($Sprite2D.frame % $Sprite2D.hframes) * frame_width
var frame_y = ($Sprite2D.frame / $Sprite2D.hframes) * frame_height
# Create 4 particles with different directions and different parts of the texture
var directions = [
Vector2(-1, -1).normalized(), # Top-left
Vector2(1, -1).normalized(), # Top-right
Vector2(-1, 1).normalized(), # Bottom-left
Vector2(1, 1).normalized() # Bottom-right
]
var regions = [
Rect2(frame_x, frame_y, frame_width / 2, frame_height / 2), # Top-left
Rect2(frame_x + frame_width / 2, frame_y, frame_width / 2, frame_height / 2), # Top-right
Rect2(frame_x, frame_y + frame_height / 2, frame_width / 2, frame_height / 2), # Bottom-left
Rect2(frame_x + frame_width / 2, frame_y + frame_height / 2, frame_width / 2, frame_height / 2) # Bottom-right
]
for i in range(4):
var tp = tileParticleScene.instantiate() as CharacterBody2D
var spr2D = tp.get_node("Sprite2D") as Sprite2D
tp.global_position = global_position
# Set up the sprite's texture and region
spr2D.texture = sprite_texture
spr2D.region_enabled = true
spr2D.region_rect = regions[i]
# Add some randomness to the velocity
var speed = randf_range(170, 200)
var dir = directions[i] + Vector2(randf_range(-0.2, 0.2), randf_range(-0.2, 0.2))
tp.velocity = dir * speed
# Add some rotation
tp.angular_velocity = randf_range(-7, 7)
get_parent().call_deferred("add_child", tp)
func indicate(iIndicate: bool):
$Indicator.visible = iIndicate
if !liftable or is_being_lifted:
$Indicator.visible = false
pass
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:
var collider = collision_shape[0]
var normal = (global_position - collider.global_position).normalized()
if abs(velocity.x) > 0.05 or abs(velocity.y) > 0.05:
if "take_damage" in body or body is TileMapLayer or collider is TileMapLayer:
if "take_damage" in body:
body.take_damage(self, thrown_by)
elif collider != self and "breakPot" in collider:
collider.take_damage(self, thrown_by)
# create particles from pot:
take_damage.rpc(null, null)
pass
normal = velocity.normalized()
velocity = velocity.bounce(normal) * 0.4 # slow down
pass # Replace with function body.
func show_destroy_effect():
$GPUParticles2D.emitting = true
$Sprite2D.frame = 13 + 19 + 19
$Sprite2DShadow.visible = false
liftable = false
indicate(false)
create4TileParticles()
is_being_thrown = false
$Sprite2DShadow.visible = false
# Play shatter sound
$SfxShatter.play()
self.call_deferred("remove")
pass
@rpc("call_local")
func take_damage(_iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
is_destroyed = true # will trigger show_destroy_effect for clients...
show_destroy_effect()
# remove all kind of collision since it's broken now...
self.set_deferred("monitoring", false)
self.set_collision_layer_value(8, false)
self.set_collision_mask_value(7, false)
self.set_collision_mask_value(8, false)
self.set_collision_mask_value(9, false)
self.set_collision_mask_value(10, false)
pass
func _on_area_2d_collision_body_exited(_body: Node2D) -> void:
pass # Replace with function body.
func _on_area_2d_pickup_body_entered(_body: Node2D) -> void:
indicate(true)
pass # Replace with function body.
func _on_area_2d_pickup_body_exited(_body: Node2D) -> void:
indicate(false)
pass # Replace with function body.
func _on_timer_smoke_particles_timeout() -> void:
$GPUParticles2D.emitting = false
pass # Replace with function body.

View File

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

View File

@@ -1,904 +0,0 @@
[gd_scene load_steps=60 format=3 uid="uid://d2ijcp7chc0hu"]
[ext_resource type="Script" uid="uid://bj0ueurl3vovc" path="res://scripts/entities/world/pot.gd" id="1_hsjxb"]
[ext_resource type="Texture2D" uid="uid://ceitcsfb2fq6m" path="res://assets/gfx/fx/big-explosion.png" id="2_8u6fk"]
[ext_resource type="Texture2D" uid="uid://bknascfv4twmi" path="res://assets/gfx/smoke_puffs.png" id="2_cmff4"]
[ext_resource type="Texture2D" uid="uid://b82ej8cgwsd6u" path="res://assets/gfx/pickups/bomb.png" id="4_c3n38"]
[ext_resource type="AudioStream" uid="uid://c880tw8ix4las" path="res://assets/audio/sfx/ambience/rock_rubble_01.wav.mp3" id="5_7nchq"]
[ext_resource type="Texture2D" uid="uid://b1twy68vd7f20" path="res://assets/gfx/pickups/indicator.png" id="10_nb533"]
[ext_resource type="AudioStream" uid="uid://bcy4qh0j2yuss" path="res://assets/audio/sfx/z3/lift.wav" id="11_lq20m"]
[ext_resource type="AudioStream" uid="uid://x0hhwyr2e1u7" path="res://assets/audio/sfx/environment/pot/pot_sweep_move_01.mp3" id="13_hd4fl"]
[ext_resource type="AudioStream" uid="uid://cc6clnct61uk7" path="res://assets/audio/sfx/environment/pot/pot_sweep_move_02.mp3" id="14_0qg0s"]
[ext_resource type="AudioStream" uid="uid://cdjtqf2gbagra" path="res://assets/audio/sfx/environment/pot/pot_sweep_move_03.mp3" id="15_p028i"]
[ext_resource type="AudioStream" uid="uid://bxsowyqt7v637" path="res://assets/audio/sfx/environment/pot/pot_place_01.mp3" id="16_fvw42"]
[ext_resource type="AudioStream" uid="uid://b8x1clggitcoa" path="res://assets/audio/sfx/environment/pot/pot_place_02.mp3" id="17_qjm0l"]
[ext_resource type="AudioStream" uid="uid://bgfvvwyvn128g" path="res://assets/audio/sfx/environment/pot/pot_place_03.mp3" id="18_xfa6j"]
[ext_resource type="AudioStream" uid="uid://67u74sfddmd6" path="res://assets/audio/sfx/environment/pot/pot_place_04.mp3" id="19_3e0oi"]
[ext_resource type="AudioStream" uid="uid://2w73l4k3704x" path="res://assets/audio/sfx/environment/pot/pot_drag1.mp3" id="19_p028i"]
[ext_resource type="AudioStream" uid="uid://cy740ysgtt5n7" path="res://assets/audio/sfx/environment/pot/pot_place_05.mp3" id="20_v2r3y"]
[ext_resource type="AudioStream" uid="uid://bnuh7ima5cq0n" path="res://assets/audio/sfx/environment/pot/pot_drag2.mp3" id="20_wv4em"]
[ext_resource type="AudioStream" uid="uid://b88qwpaix6gjk" path="res://assets/audio/sfx/ambience/explode_01.wav.mp3" id="21_0g7xm"]
[ext_resource type="AudioStream" uid="uid://co7i1f4t8qtqp" path="res://assets/audio/sfx/environment/pot/pot_place_06.mp3" id="21_0qg0s"]
[ext_resource type="AudioStream" uid="uid://ohm0t5c7hw0w" path="res://assets/audio/sfx/player/throw/throw_01.wav.mp3" id="21_hd4fl"]
[ext_resource type="AudioStream" uid="uid://d4dweg04wrw6a" path="res://assets/audio/sfx/sub_weapons/bomb_fuse.mp3" id="22_1n54r"]
[ext_resource type="Texture2D" uid="uid://bd4wdplgk7es5" path="res://assets/gfx/fx/kenney_particle_pack/smoke_01.png" id="26_c3n38"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_hsjxb"]
properties/0/path = NodePath(".:position")
properties/0/spawn = true
properties/0/replication_mode = 2
properties/1/path = NodePath(".:positionZ")
properties/1/spawn = true
properties/1/replication_mode = 2
properties/2/path = NodePath(".:is_being_thrown")
properties/2/spawn = true
properties/2/replication_mode = 2
properties/3/path = NodePath(".:is_being_lifted")
properties/3/spawn = true
properties/3/replication_mode = 2
properties/4/path = NodePath(".:is_being_put_down")
properties/4/spawn = true
properties/4/replication_mode = 2
properties/5/path = NodePath(".:collision_mask")
properties/5/spawn = true
properties/5/replication_mode = 2
properties/6/path = NodePath(".:collision_layer")
properties/6/spawn = true
properties/6/replication_mode = 2
properties/7/path = NodePath("Area2DCollision:monitoring")
properties/7/spawn = true
properties/7/replication_mode = 2
properties/8/path = NodePath(".:is_destroyed")
properties/8/spawn = true
properties/8/replication_mode = 2
properties/9/path = NodePath(".:is_being_grabbed")
properties/9/spawn = true
properties/9/replication_mode = 2
properties/10/path = NodePath(".:is_moving")
properties/10/spawn = true
properties/10/replication_mode = 2
properties/11/path = NodePath(".:is_spawning")
properties/11/spawn = true
properties/11/replication_mode = 2
properties/12/path = NodePath(".:is_fused")
properties/12/spawn = true
properties/12/replication_mode = 2
[sub_resource type="Animation" id="Animation_0uxu3"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("ExplosionSprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [8]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("ExplosionSprite:visible")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite2DShadow:visible")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Sprite2D:visible")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath(".:rotation")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.0]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("GlowLight:enabled")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/6/type = "value"
tracks/6/imported = false
tracks/6/enabled = true
tracks/6/path = NodePath("GlowLight:texture_scale")
tracks/6/interp = 1
tracks/6/loop_wrap = true
tracks/6/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [1.0]
}
tracks/7/type = "value"
tracks/7/imported = false
tracks/7/enabled = true
tracks/7/path = NodePath("GlowLight:energy")
tracks/7/interp = 1
tracks/7/loop_wrap = true
tracks/7/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [2.55]
}
tracks/8/type = "value"
tracks/8/imported = false
tracks/8/enabled = true
tracks/8/path = NodePath("FuseParticles:emitting")
tracks/8/interp = 1
tracks/8/loop_wrap = true
tracks/8/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/9/type = "value"
tracks/9/imported = false
tracks/9/enabled = true
tracks/9/path = NodePath("GlowLight:position")
tracks/9/interp = 1
tracks/9/loop_wrap = true
tracks/9/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(0, 0)]
}
tracks/10/type = "value"
tracks/10/imported = false
tracks/10/enabled = true
tracks/10/path = NodePath("GlowLight:scale")
tracks/10/interp = 1
tracks/10/loop_wrap = true
tracks/10/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(1, 1)]
}
tracks/11/type = "value"
tracks/11/imported = false
tracks/11/enabled = true
tracks/11/path = NodePath("SmokeParticles:emitting")
tracks/11/interp = 1
tracks/11/loop_wrap = true
tracks/11/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
[sub_resource type="Animation" id="Animation_5mm6m"]
resource_name = "explosion"
length = 0.9
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("ExplosionSprite:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.0333333, 0.0610968, 0.0930812, 0.120627, 0.149375, 0.185276, 0.219713, 0.25692),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("ExplosionSprite:visible")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.280232),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [true, false]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite2DShadow:visible")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Sprite2D:visible")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath(".:rotation")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.0]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("GlowLight:enabled")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/6/type = "value"
tracks/6/imported = false
tracks/6/enabled = true
tracks/6/path = NodePath("GlowLight:energy")
tracks/6/interp = 1
tracks/6/loop_wrap = true
tracks/6/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.533333),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [2.55, 0.49, 0.0]
}
tracks/7/type = "value"
tracks/7/imported = false
tracks/7/enabled = true
tracks/7/path = NodePath("GlowLight:texture_scale")
tracks/7/interp = 1
tracks/7/loop_wrap = true
tracks/7/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.5]
}
tracks/8/type = "value"
tracks/8/imported = false
tracks/8/enabled = true
tracks/8/path = NodePath("FuseParticles:emitting")
tracks/8/interp = 1
tracks/8/loop_wrap = true
tracks/8/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
tracks/9/type = "value"
tracks/9/imported = false
tracks/9/enabled = true
tracks/9/path = NodePath("GlowLight:position")
tracks/9/interp = 1
tracks/9/loop_wrap = true
tracks/9/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(0, 0)]
}
tracks/10/type = "value"
tracks/10/imported = false
tracks/10/enabled = true
tracks/10/path = NodePath("SmokeParticles:emitting")
tracks/10/interp = 1
tracks/10/loop_wrap = true
tracks/10/keys = {
"times": PackedFloat32Array(0, 0.2, 0.9),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [false, true, false]
}
[sub_resource type="Animation" id="Animation_c3n38"]
resource_name = "fused"
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("FuseParticles:emitting")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite2D:visible")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("GlowLight:enabled")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("GlowLight:texture_scale")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.17]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("GlowLight:position")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(7, -6.5)]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("GlowLight:scale")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(1, 1)]
}
tracks/6/type = "value"
tracks/6/imported = false
tracks/6/enabled = true
tracks/6/path = NodePath("ExplosionSprite:visible")
tracks/6/interp = 1
tracks/6/loop_wrap = true
tracks/6/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
[sub_resource type="Animation" id="Animation_8u6fk"]
resource_name = "idle"
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("FuseParticles:emitting")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite2D:visible")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("GlowLight:enabled")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("GlowLight:texture_scale")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.17]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("GlowLight:position")
tracks/4/interp = 1
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(7, -6.5)]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("GlowLight:scale")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(0.24, 0.24)]
}
tracks/6/type = "value"
tracks/6/imported = false
tracks/6/enabled = true
tracks/6/path = NodePath("ExplosionSprite:visible")
tracks/6/interp = 1
tracks/6/loop_wrap = true
tracks/6/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_1uhwj"]
_data = {
&"RESET": SubResource("Animation_0uxu3"),
&"explosion": SubResource("Animation_5mm6m"),
&"fused": SubResource("Animation_c3n38"),
&"idle": SubResource("Animation_8u6fk")
}
[sub_resource type="Gradient" id="Gradient_nb533"]
offsets = PackedFloat32Array(0.847255, 0.861575)
colors = PackedColorArray(0, 0, 0, 0.764706, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_87nuj"]
gradient = SubResource("Gradient_nb533")
width = 16
height = 6
fill = 1
fill_from = Vector2(0.504274, 0.478632)
fill_to = Vector2(0.897436, 0.0769231)
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_lq20m"]
particles_animation = true
particles_anim_h_frames = 4
particles_anim_v_frames = 2
particles_anim_loop = false
[sub_resource type="Curve" id="Curve_76fyq"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.780549, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
point_count = 3
[sub_resource type="CurveTexture" id="CurveTexture_m11t2"]
curve = SubResource("Curve_76fyq")
[sub_resource type="Curve" id="Curve_sb38x"]
_limits = [0.0, 100.0, 0.0, 1.0]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.733167, 4.55855), 0.0, 0.0, 0, 0, Vector2(0.815461, 91.8906), 0.0, 0.0, 0, 0, Vector2(0.892768, 100), 0.0, 0.0, 0, 0]
point_count = 4
[sub_resource type="CurveTexture" id="CurveTexture_ui3li"]
curve = SubResource("Curve_sb38x")
[sub_resource type="Curve" id="Curve_dtubv"]
_limits = [0.0, 1.0, -1.0, 1.0]
_data = [Vector2(-1, 0), 0.0, 0.0, 0, 0, Vector2(0.0124688, 1), 0.0, 0.0, 0, 0, Vector2(0.516209, 1), 0.0, 0.0, 0, 0, Vector2(0.947631, 0), 0.0, 0.0, 0, 0]
point_count = 4
[sub_resource type="CurveXYZTexture" id="CurveXYZTexture_tjjlx"]
curve_x = SubResource("Curve_dtubv")
[sub_resource type="Curve" id="Curve_1webc"]
_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.0224439, 1), 0.0, 0.0, 0, 0, Vector2(0.880299, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
point_count = 4
[sub_resource type="CurveTexture" id="CurveTexture_sp8mg"]
curve = SubResource("Curve_1webc")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_iw3no"]
particle_flag_disable_z = true
direction = Vector3(1, 0.2, 0)
spread = 62.79
initial_velocity_min = -30.0
initial_velocity_max = 30.0
directional_velocity_min = -25.0
directional_velocity_max = 25.0
directional_velocity_curve = SubResource("CurveXYZTexture_tjjlx")
gravity = Vector3(0, 0, 0)
damping_max = 100.0
damping_curve = SubResource("CurveTexture_ui3li")
scale_min = 0.8
scale_max = 1.2
scale_curve = SubResource("CurveTexture_sp8mg")
color = Color(1, 1, 1, 0.709804)
alpha_curve = SubResource("CurveTexture_m11t2")
anim_offset_max = 1.0
[sub_resource type="Gradient" id="Gradient_1oak7"]
colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 0)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_jcrig"]
gradient = SubResource("Gradient_1oak7")
[sub_resource type="Curve" id="Curve_whlmf"]
_limits = [-1.0, 1.0, 0.0, 1.0]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 2
[sub_resource type="CurveTexture" id="CurveTexture_vvkl4"]
curve = SubResource("Curve_whlmf")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_ayymp"]
particle_flag_disable_z = true
direction = Vector3(1, -1, 0)
spread = 96.695
initial_velocity_min = 8.0
initial_velocity_max = 29.0
gravity = Vector3(0, 0, 0)
linear_accel_min = -15.28
linear_accel_max = 5.3
radial_accel_min = 12.62
radial_accel_max = 13.95
tangential_accel_min = -4.65
tangential_accel_max = 7.28
color = Color(1, 0.564706, 0, 1)
color_ramp = SubResource("GradientTexture1D_jcrig")
hue_variation_min = -0.14
hue_variation_max = 0.14
hue_variation_curve = SubResource("CurveTexture_vvkl4")
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hsjxb"]
size = Vector2(12, 8)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_87nuj"]
size = Vector2(18, 15)
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_wv4em"]
streams_count = 2
stream_0/stream = ExtResource("19_p028i")
stream_1/stream = ExtResource("20_wv4em")
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_fvw42"]
streams_count = 3
stream_0/stream = ExtResource("13_hd4fl")
stream_1/stream = ExtResource("14_0qg0s")
stream_2/stream = ExtResource("15_p028i")
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_icnv3"]
streams_count = 6
stream_0/stream = ExtResource("16_fvw42")
stream_1/stream = ExtResource("17_qjm0l")
stream_2/stream = ExtResource("18_xfa6j")
stream_3/stream = ExtResource("19_3e0oi")
stream_4/stream = ExtResource("20_v2r3y")
stream_5/stream = ExtResource("21_0qg0s")
[sub_resource type="Animation" id="Animation_lq20m"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:offset")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(0, 0)]
}
[sub_resource type="Animation" id="Animation_cmff4"]
resource_name = "indicate"
length = 0.8
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:offset")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.4, 0.8),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector2(0, 0), Vector2(0, -1), Vector2(0, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_76fyq"]
_data = {
&"RESET": SubResource("Animation_lq20m"),
&"indicate": SubResource("Animation_cmff4")
}
[sub_resource type="RectangleShape2D" id="RectangleShape2D_nb533"]
size = Vector2(14, 10)
[sub_resource type="Gradient" id="Gradient_ccs6w"]
offsets = PackedFloat32Array(0.202864, 1)
colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_mqeoh"]
gradient = SubResource("Gradient_ccs6w")
fill = 1
fill_from = Vector2(0.508547, 0.478632)
fill_to = Vector2(0.854701, 0.145299)
[sub_resource type="Gradient" id="Gradient_5mm6m"]
offsets = PackedFloat32Array(0.0506667, 0.592, 1)
colors = PackedColorArray(0.137255, 0.0470588, 0.0156863, 0.886275, 0.266667, 0.258824, 0.254902, 0.443137, 0, 0, 0, 0)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_0uxu3"]
gradient = SubResource("Gradient_5mm6m")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_1uhwj"]
particle_flag_disable_z = true
angle_min = -47.8
angle_max = 52.5
inherit_velocity_ratio = 0.226
direction = Vector3(1, -1, 0)
spread = 180.0
initial_velocity_min = 5.0
initial_velocity_max = 14.0
angular_velocity_min = -4.00002
angular_velocity_max = 3.99998
radial_velocity_min = -1.00002
radial_velocity_max = 0.999978
gravity = Vector3(0, -10, 0)
linear_accel_min = -5.13
linear_accel_max = 5.0
radial_accel_min = -4.0
radial_accel_max = 5.0
tangential_accel_min = -2.0
tangential_accel_max = 2.0
damping_max = 0.1
scale_min = 0.05
scale_max = 0.1
color_ramp = SubResource("GradientTexture1D_0uxu3")
[node name="Bomb" type="CharacterBody2D"]
collision_layer = 128
collision_mask = 960
script = ExtResource("1_hsjxb")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_hsjxb")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_1uhwj")
}
autoplay = "idle"
[node name="ExplosionSprite" type="Sprite2D" parent="."]
position = Vector2(0, -19)
texture = ExtResource("2_8u6fk")
hframes = 9
frame = 8
[node name="Sprite2DShadow" type="Sprite2D" parent="."]
z_index = -1
position = Vector2(0, 3)
texture = SubResource("GradientTexture2D_87nuj")
[node name="GPUParticles2D" type="GPUParticles2D" parent="."]
material = SubResource("CanvasItemMaterial_lq20m")
emitting = false
amount = 16
texture = ExtResource("2_cmff4")
interp_to_end = 0.026
preprocess = 0.16
explosiveness = 0.5
randomness = 0.48
use_fixed_seed = true
seed = 1565624367
process_material = SubResource("ParticleProcessMaterial_iw3no")
[node name="TimerSmokeParticles" type="Timer" parent="GPUParticles2D"]
wait_time = 0.12
[node name="FuseParticles" type="GPUParticles2D" parent="."]
position = Vector2(6, -10)
amount = 12
lifetime = 0.4
process_material = SubResource("ParticleProcessMaterial_ayymp")
[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(0, -4)
texture = ExtResource("4_c3n38")
metadata/_edit_lock_ = true
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
visible = false
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_hsjxb")
[node name="Area2DPickup" type="Area2D" parent="."]
visible = false
collision_layer = 1024
collision_mask = 512
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickup"]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_87nuj")
debug_color = Color(0.688142, 0.7, 0.0440007, 0.42)
[node name="SfxShatter" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("5_7nchq")
attenuation = 9.84915
panning_strength = 1.46
bus = &"Sfx"
metadata/_edit_lock_ = true
[node name="SfxDrag" type="AudioStreamPlayer2D" parent="."]
stream = SubResource("AudioStreamRandomizer_wv4em")
volume_db = -10.142
bus = &"Sfx"
metadata/_edit_lock_ = true
[node name="SfxDrag2" type="AudioStreamPlayer2D" parent="."]
stream = SubResource("AudioStreamRandomizer_fvw42")
volume_db = -9.703
pitch_scale = 0.77
max_distance = 749.0
attenuation = 10.1965
panning_strength = 1.5
bus = &"Sfx"
metadata/_edit_lock_ = true
[node name="SfxLand" type="AudioStreamPlayer2D" parent="."]
stream = SubResource("AudioStreamRandomizer_icnv3")
attenuation = 6.9644
panning_strength = 1.25
bus = &"Sfx"
metadata/_edit_lock_ = true
[node name="SfxThrow" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("21_hd4fl")
volume_db = -4.708
pitch_scale = 0.54
bus = &"Sfx"
metadata/_edit_lock_ = true
[node name="SfxDrop" type="AudioStreamPlayer2D" parent="."]
bus = &"Sfx"
metadata/_edit_lock_ = true
[node name="SfxPickup" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("11_lq20m")
metadata/_edit_lock_ = true
[node name="Indicator" type="Sprite2D" parent="."]
visible = false
position = Vector2(0, -11)
texture = ExtResource("10_nb533")
[node name="AnimationPlayer" type="AnimationPlayer" parent="Indicator"]
libraries = {
&"": SubResource("AnimationLibrary_76fyq")
}
autoplay = "indicate"
[node name="Area2DCollision" type="Area2D" parent="."]
visible = false
collision_layer = 1024
collision_mask = 704
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DCollision"]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_nb533")
debug_color = Color(0.7, 0.132592, 0.232379, 0.42)
[node name="GlowLight" type="PointLight2D" parent="."]
energy = 2.55
texture = SubResource("GradientTexture2D_mqeoh")
[node name="SmokeParticles" type="GPUParticles2D" parent="."]
position = Vector2(0, -18)
amount = 16
texture = ExtResource("26_c3n38")
lifetime = 2.0
explosiveness = 0.93
randomness = 0.75
process_material = SubResource("ParticleProcessMaterial_1uhwj")
[node name="SfxExplosion" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("21_0g7xm")
attenuation = 6.72717
panning_strength = 1.39
bus = &"Sfx"
[node name="SfxBombFuse" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("22_1n54r")
max_distance = 1648.0
attenuation = 6.72717
panning_strength = 1.42
bus = &"Sfx"
[connection signal="timeout" from="GPUParticles2D/TimerSmokeParticles" to="." method="_on_timer_smoke_particles_timeout"]
[connection signal="body_entered" from="Area2DPickup" to="." method="_on_area_2d_pickup_body_entered"]
[connection signal="body_exited" from="Area2DPickup" to="." method="_on_area_2d_pickup_body_exited"]
[connection signal="body_entered" from="Area2DCollision" to="." method="_on_area_2d_collision_body_entered"]
[connection signal="body_exited" from="Area2DCollision" to="." method="_on_area_2d_collision_body_exited"]

View File

@@ -1,35 +0,0 @@
[gd_scene load_steps=5 format=3 uid="uid://bptbhvomylpn"]
[ext_resource type="Texture2D" uid="uid://b2umirwiauk7p" path="res://assets/gfx/pickups/chest.png" id="1_08kib"]
[ext_resource type="AudioStream" uid="uid://cyvxb4c5k75mn" path="res://assets/audio/sfx/player/pickup/item_collect_01.wav" id="2_5eugl"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_3kvu5"]
size = Vector2(16, 11)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_08kib"]
size = Vector2(10, 1)
[node name="Chest" type="CharacterBody2D"]
collision_layer = 128
collision_mask = 64
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("1_08kib")
hframes = 2
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, 1.5)
shape = SubResource("RectangleShape2D_3kvu5")
[node name="SfxOpen" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("2_5eugl")
volume_db = -14.205
[node name="Area2DInteraction" type="Area2D" parent="."]
collision_layer = 2048
collision_mask = 512
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DInteraction"]
position = Vector2(0, 7.5)
shape = SubResource("RectangleShape2D_08kib")
debug_color = Color(0.7, 0.673001, 0.190349, 0.42)

View File

@@ -1,974 +0,0 @@
extends CharacterBody2D
var tileParticleScene = preload("res://scripts/components/TileParticle.tscn")
var liftable = true
var thrown_by = null
var entity_id = ""
var object_name = "bomb"
@export var is_being_thrown = false
@export var is_being_lifted = false:
set(value):
is_being_lifted = value
@export var is_being_put_down = false:
set(value):
is_being_put_down = value
@export var is_being_grabbed = false
@export var is_moving = false
@export var is_spawning = false
@export var is_fused = false
var throw_speed = 200
var throw_height = 30
var current_height = 0
var gravity = 800
var holder = null
var flipFromWall = false
# Add Z-axis variables similar to loot.gd
@export var positionZ = 0.0
var velocityZ = 0.0
var accelerationZ = -330.0 # Gravity
var bounceRestitution = 0.3
var minBounceVelocity = 60.0
var destroy_initiated = false
@export var is_destroyed = false
# Smooth lifting variables
var target_position = Vector2.ZERO
var lift_height = 12.0 # Height above player's head
var lift_speed = 10.0 # Speed of smooth movement
var put_down_start_pos = Vector2.ZERO
var put_down_target_pos = Vector2.ZERO
@export var lift_progress = 0.0
@export var re_enable_collision_after_time = 0.0
var re_enable_time = 0.08
var previousFrameVel = Vector2.ZERO
var hasShownSmokePuffs = false
var grab_follow_speed = 15.0 # Speed at which pot follows holder when grabbed
var previous_holder_position = Vector2.ZERO # Track previous holder position to calculate movement delta
var previous_client_position = Vector2.ZERO # Track previous position on client for movement detection
var push_lead_factor = 1.1 # When pushing, pot moves slightly ahead (110% of player movement)
var min_push_distance = 12.0 # Minimum distance pot should maintain from player when pushing to avoid blocking
var _throw_collision_initialized = false # Track if collision state has been initialized for throw on client
@export var holder_peer_id: int = 0:
set(value):
holder_peer_id = value
# Clear state when holder is cleared
if value == 0:
holder = null
# Always clear grabbed and lifted states when holder is cleared
is_being_grabbed = false
is_being_lifted = false
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))
func _ready() -> void:
if is_spawning:
liftable = false
$Area2DCollision.set_deferred("monitoring", false)
self.set_collision_layer_value(8, false)
self.set_collision_mask_value(9, false)
self.set_collision_mask_value(10, false)
self.set_collision_mask_value(8, false)
update_sprite_scale()
pass
indicate(false)
pass
func _physics_process(delta: float) -> void:
# Update holder based on holder_peer_id for network sync
# CRITICAL: holder_peer_id is the source of truth - if it's 0, there's no holder
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:
holder = player
elif holder_peer_id == 0:
# No holder - clear everything
# BUT: Don't clear velocity if pot is being thrown (it needs velocity to fly)
if holder != null:
holder = null
if is_being_grabbed and not is_being_thrown:
is_being_grabbed = false
if is_being_lifted and not is_being_thrown:
is_being_lifted = false
if velocity != Vector2.ZERO and not is_being_thrown:
velocity = Vector2.ZERO
# 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
# Use the same calculation on both server and client to ensure consistent Y position
var target_pos = holder.global_position + Vector2(0, 0)
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
update_sprite_scale() # Ensure sprite scale/offset is updated consistently
else:
# Debug: Check why pot is not following
if is_being_lifted and !holder:
# 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():
# CRITICAL: If holder_peer_id is 0, ALWAYS clear grab state immediately
# This must happen before ANY logic runs to prevent movement after release
# BUT: Don't clear velocity if pot is being thrown (it needs velocity to fly)
if holder_peer_id == 0 and not is_being_thrown:
is_being_grabbed = false
if holder != null:
holder = null
if velocity != Vector2.ZERO:
velocity = Vector2.ZERO
if is_moving:
is_moving = false
if $SfxDrag2.playing:
$SfxDrag2.stop()
# Skip all grab logic if holder_peer_id is 0
pass
if is_being_thrown:
re_enable_collision_after_time -= delta
if re_enable_collision_after_time <= 0.0:
# enable collisions with players again after the delay
self.set_collision_layer_value(8, true)
# Collision layer is already enabled, just re-enable collision mask with players
self.set_collision_mask_value(9, true) # Re-enable collision with players
self.set_collision_mask_value(10, true) # Re-enable collision with players (if using both)
re_enable_collision_after_time = 0
# Apply gravity to vertical movement
velocityZ += accelerationZ * delta
positionZ += velocityZ * delta
if positionZ <= 0:
# Pot has hit the ground
positionZ = 0
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if abs(velocityZ) > minBounceVelocity:
velocityZ = - velocityZ * bounceRestitution
else:
velocityZ = 0
is_being_thrown = false
velocity = velocity.lerp(Vector2.ZERO, 0.5)
if velocity.x == 0 and velocity.y == 0:
thrown_by = null
# Move horizontally
if self.get_collision_layer_value(8) == false:
move_and_slide()
else:
var collision = move_and_collide(velocity * delta)
if collision:
if positionZ == 0:
is_being_thrown = false
update_sprite_scale()
elif is_being_put_down:
lift_progress -= delta * lift_speed
if lift_progress <= 0.0:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
lift_progress = 0
is_being_put_down = false
is_being_lifted = false
holder = null
positionZ = 0
# enable collisions again
self.set_collision_layer_value(8, true)
self.set_collision_mask_value(9, true)
self.set_collision_mask_value(10, true)
self.set_collision_mask_value(7, true)
self.set_collision_mask_value(8, true)
$Area2DCollision.set_deferred("monitoring", true)
else:
global_position = put_down_start_pos.lerp(put_down_target_pos, 1.0 - lift_progress)
positionZ = lift_height * lift_progress
update_sprite_scale()
# CRITICAL: Only follow if holder_peer_id is NOT 0 (this is the source of truth)
# Check holder_peer_id FIRST before is_being_grabbed to ensure we never follow when released
# ONLY run this on server (pot has authority 1 = server)
# DOUBLE CHECK: holder_peer_id must be non-zero AND holder must exist AND match
# BUT: Don't run grab logic if pot is being thrown (throw logic handles movement)
if holder_peer_id != 0 and is_being_grabbed and holder != null and not is_being_thrown:
# Only follow if holder's authority matches holder_peer_id
if holder.get_multiplayer_authority() == holder_peer_id:
# Calculate how much the player has moved since last frame
var player_movement = holder.global_position - previous_holder_position
# Only move pot if player has moved
if player_movement.length() > 0.01:
# Get the locked grab direction to determine push/pull axis
var locked_dir = Vector2.ZERO
if "locked_grab_direction" in holder and holder.locked_grab_direction != Vector2.ZERO:
locked_dir = holder.locked_grab_direction.normalized()
# Calculate desired movement based on push/pull
var desired_movement = Vector2.ZERO
if locked_dir != Vector2.ZERO:
# Project player's movement onto the push/pull direction
var movement_along_axis = player_movement.project(locked_dir)
# Determine if pushing (moving in locked_dir direction) or pulling (moving opposite)
var is_pushing = movement_along_axis.dot(locked_dir) > 0
if is_pushing:
# PUSHING: Pot moves first, ahead of player
# Move pot by slightly more than player movement so it leads (moves first)
desired_movement = movement_along_axis * push_lead_factor
else:
# PULLING: Player moves first, pot follows behind
# Move pot by the exact same amount as player (pot follows immediately)
desired_movement = movement_along_axis
else:
# No locked direction, just move pot by player's movement
desired_movement = player_movement
# Check for collisions before moving - use space query to test
var space_state = get_world_2d().direct_space_state
var query = PhysicsShapeQueryParameters2D.new()
query.shape = $CollisionShape2D.shape
query.transform = global_transform.translated(desired_movement)
query.collision_mask = collision_mask
query.exclude = [self, holder] # Exclude self and holder from query
var results = space_state.intersect_shape(query, 1)
var can_move = true
# Check if any collision would block movement
for result in results:
var collider = result.collider
if collider != null:
# If it's a wall, another pot, or another player, block movement
can_move = false
break
if can_move:
# No blocking collision - move the pot
global_position += desired_movement
velocity = desired_movement / delta if delta > 0 else Vector2.ZERO
else:
# Something is blocking - don't move the pot
velocity = Vector2.ZERO
# Update previous position for next frame
previous_holder_position = holder.global_position
if velocity.length() > 1.0:
is_moving = true
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if !$SfxDrag2.playing:
$SfxDrag2.play()
else:
is_moving = false
$GPUParticles2D.emitting = false
if $SfxDrag2.playing:
$SfxDrag2.stop()
previousFrameVel = velocity
else:
# Player hasn't moved - keep pot at current position
velocity = Vector2.ZERO
is_moving = false
if $SfxDrag2.playing:
$SfxDrag2.stop()
# Still update previous position even if not moving
previous_holder_position = holder.global_position
else:
# No valid holder, clear grab state and stop movement immediately
is_being_grabbed = false
is_moving = false
if $SfxDrag2.playing:
$SfxDrag2.stop()
velocity = Vector2.ZERO
holder = null
holder_peer_id = 0 # Force clear to ensure no following
pass
# CRITICAL FINAL CHECK: If holder_peer_id is 0, STOP ALL MOVEMENT immediately
# This must run AFTER all grab logic to catch any cases where holder_peer_id was set to 0
# but the pot is still trying to move
# BUT: Don't clear velocity if pot is being thrown (it needs velocity to fly)
if holder_peer_id == 0 and not is_being_thrown:
if is_being_grabbed:
is_being_grabbed = false
if holder != null:
holder = null
if velocity != Vector2.ZERO:
velocity = Vector2.ZERO
if is_moving:
is_moving = false
if $SfxDrag2.playing:
$SfxDrag2.stop()
# Only handle free-falling if we're not being held (holder_peer_id == 0 is the source of truth)
# BUT: Don't run free-falling logic if pot is being thrown (throw logic handles movement)
if holder_peer_id == 0 and !is_being_lifted and !is_being_thrown: # it just spawned or is free-falling:
# Apply gravity to vertical movement
velocityZ += accelerationZ * delta
positionZ += velocityZ * delta
if positionZ <= 0:
if is_spawning:
is_spawning = false
liftable = true
$Area2DCollision.set_deferred("monitoring", true)
self.set_collision_layer_value(8, true)
self.set_collision_mask_value(9, true)
self.set_collision_mask_value(10, true)
self.set_collision_mask_value(8, true)
# Pot has hit the ground
positionZ = 0
if abs(velocityZ) > 30:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if abs(velocityZ) > minBounceVelocity:
velocityZ = - velocityZ * bounceRestitution
else:
velocityZ = 0
velocity = velocity.lerp(Vector2.ZERO, 0.5)
move_and_collide(velocity * delta)
update_sprite_scale()
pass
else:
# CRITICAL: If holder_peer_id is 0, ALWAYS clear grab state immediately (client side)
# This must happen before any grab logic runs
if holder_peer_id == 0:
is_being_grabbed = false
if holder != null:
holder = null
if is_fused and $AnimationPlayer.current_animation == "idle":
if $SfxBombFuse.playing:
$SfxBombFuse.play()
$AnimationPlayer.play("fused")
# for client:'
if is_being_thrown:
if $SfxDrag2.playing:
$SfxDrag2.stop()
# Timer is synced from server via sync_throw_timer RPC
# If not initialized yet, wait for RPC (fallback initialization)
if not _throw_collision_initialized:
# Fallback: initialize if RPC hasn't arrived yet
self.set_collision_layer_value(8, false)
self.set_collision_mask_value(7, true)
self.set_collision_mask_value(9, false)
self.set_collision_mask_value(10, false)
# CRITICAL: Set timer to a positive value to prevent immediate expiration
re_enable_collision_after_time = re_enable_time
_throw_collision_initialized = true
# Apply the same throw movement logic on client for smooth movement
# Handle collision re-enable timer (same as server)
# CRITICAL: Only decrement and check timer if it's been initialized AND is positive
if _throw_collision_initialized and re_enable_collision_after_time > 0.0:
re_enable_collision_after_time -= delta
if re_enable_collision_after_time <= 0.0:
# enable collisions with players again after the delay
self.set_collision_layer_value(8, true)
# Collision layer is already enabled, just re-enable collision mask with players
self.set_collision_mask_value(9, true) # Re-enable collision with players
self.set_collision_mask_value(10, true) # Re-enable collision with players (if using both)
re_enable_collision_after_time = 0
# Apply gravity to vertical movement
velocityZ += accelerationZ * delta
positionZ += velocityZ * delta
if positionZ <= 0:
# Pot has hit the ground
positionZ = 0
if !$SfxLand.playing:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
if abs(velocityZ) > minBounceVelocity:
velocityZ = - velocityZ * bounceRestitution
else:
velocityZ = 0
is_being_thrown = false
velocity = velocity.lerp(Vector2.ZERO, 0.5)
if velocity.x == 0 and velocity.y == 0:
thrown_by = null
# Move horizontally using the same logic as server
if self.get_collision_layer_value(8) == false:
move_and_slide()
else:
var collision = move_and_collide(velocity * delta)
if collision:
if positionZ == 0:
is_being_thrown = false
update_sprite_scale()
else:
# Pot is no longer being thrown - reset initialization flag
if _throw_collision_initialized:
_throw_collision_initialized = false
if is_being_put_down:
if $SfxDrag2.playing:
$SfxDrag2.stop()
if !$SfxLand.playing:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
elif is_being_lifted:
if $SfxDrag2.playing:
$SfxDrag2.stop()
$GPUParticles2D.emitting = false
# Update position on client to follow holder
if holder:
target_position = holder.global_position + Vector2(0, 0)
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()
# CRITICAL: On client, do NOT move the pot - only the server should move it
# The pot's position is synced via replication from the server
# We only handle visual/audio effects here
elif holder_peer_id == 0:
# No holder - ensure we're not in grabbed state
if is_being_grabbed:
is_being_grabbed = false
is_moving = false
if $SfxDrag2.playing:
$SfxDrag2.stop()
elif is_being_grabbed and holder_peer_id != 0:
# Only handle visual/audio effects on client, don't move the pot
# The server handles the actual movement and syncs position via replication
# is_moving is already replicated from server, so use that to control particles
if holder != null and holder.get_multiplayer_authority() == holder_peer_id:
# Use replicated is_moving state to control particles (more efficient than syncing emitting)
if is_moving:
# Keep particles emitting continuously while moving (don't let timer stop them)
$GPUParticles2D.emitting = true
# Continuously restart timer while moving to keep particles emitting (like server does)
$GPUParticles2D/TimerSmokeParticles.start()
if !$SfxDrag2.playing:
$SfxDrag2.play()
else:
$GPUParticles2D.emitting = false
if $SfxDrag2.playing:
$SfxDrag2.stop()
else:
# No valid holder, clear state
is_being_grabbed = false
is_moving = false
if $SfxDrag2.playing:
$SfxDrag2.stop()
elif !is_being_grabbed:
if is_spawning:
if positionZ <= 0:
$SfxLand.play()
$GPUParticles2D.emitting = true
$GPUParticles2D/TimerSmokeParticles.start()
else:
if $SfxLand.playing:
$SfxLand.stop()
if $SfxDrag2.playing:
$SfxDrag2.stop()
pass
# Fix stuck put_down state
if is_being_put_down and lift_progress <= 0:
is_being_put_down = false
update_sprite_scale()
if is_destroyed and !destroy_initiated:
destroy_initiated = true
show_destroy_effect()
# Update debug label
if has_node("LabelPotStateNDirectionNSpeed"):
var collision_layer_8 = self.get_collision_layer_value(8)
var collision_str = "Col 8: %s" % ("TRUE" if collision_layer_8 else "FALSE")
collision_str += "\nthrown? %s" % ("TRUE" if is_being_thrown else "FALSE")
$LabelPotStateNDirectionNSpeed.text = collision_str
pass
pass
func update_sprite_scale() -> void:
# Calculate scale based on height
# Maximum height will have scale 1.3, ground will have scale 1.0
var height_factor = positionZ / 50.0 # Assuming 50 is max height
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
# Also update shadow position and scale
if is_being_lifted:
$Sprite2D.z_as_relative = false
$Sprite2D.z_index = 12
$Sprite2DShadow.offset.y = 0 # Base shadow position
else:
$Sprite2D.z_as_relative = true
$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.modulate.
func throw(direction: Vector2, initial_velocity: float = 200):
# When thrown, enable collision layer so pot can use move_and_collide
# But disable collision mask with players temporarily (re-enabled after re_enable_collision_after_time)
# Enable collision with walls so pot can bounce off walls
self.set_collision_layer_value(8, false) # Enable pot's collision layer (needed for move_and_collide to work)
self.set_collision_mask_value(7, true) # Enable collision with walls
self.set_collision_mask_value(9, false) # Disable collision with players initially
self.set_collision_mask_value(10, false) # Disable collision with players initially (if using both)
$Area2DCollision.set_deferred("monitoring", true)
$SfxThrow.play()
is_being_lifted = false
is_being_thrown = true
is_being_put_down = false
is_being_grabbed = false # Clear grab state
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
# Sync timer to clients so they know when to re-enable collisions
sync_throw_timer.rpc(re_enable_time)
@rpc("any_peer", "reliable")
func sync_throw_timer(timer_value: float):
# Client receives timer value from server
if not multiplayer.is_server():
# CRITICAL: Always set collision layer to false FIRST when receiving timer sync
# This ensures it's false even if something else tried to set it to true
self.set_collision_layer_value(8, false)
self.set_collision_mask_value(7, true) # Enable collision with walls
self.set_collision_mask_value(9, false) # Disable collision with players initially
self.set_collision_mask_value(10, false) # Disable collision with players initially
# Set timer value from server (ensures it's positive)
re_enable_collision_after_time = timer_value
_throw_collision_initialized = true
@rpc("any_peer", "reliable")
func throw_rpc(direction: Vector2, initial_velocity: float = 200):
# 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...
holder = new_holder
holder_peer_id = new_holder.get_multiplayer_authority()
is_being_grabbed = true
indicate(false)
# Initialize previous position to current position so we can track movement
previous_holder_position = new_holder.global_position
previous_client_position = global_position # Initialize client position tracking
# Keep pot's collision enabled so it can collide with other players while being pushed/pulled
# The pot uses direct position updates (not move_and_collide) but still needs collision for other players
# Don't change pot's position - it should stay where it is and only move when player pushes/pulls
return true
return false
func release():
# Clear all grab-related state
# CRITICAL: Don't re-enable collision layer if pot is being thrown (throw logic handles it)
if is_being_thrown:
# Pot is being thrown - don't change collision layer, throw logic will handle it
holder = null
holder_peer_id = 0
is_being_grabbed = false
hasShownSmokePuffs = false
#velocity = Vector2.ZERO
indicate(true)
if $SfxDrag2.playing:
$SfxDrag2.stop()
return
# CRITICAL: If we're not the server, we need to notify the server to release
# The pot has authority 1 (server), so the server must be the one to clear holder_peer_id
if not multiplayer.is_server():
# Client requests server to release - use holder_peer_id directly
if holder_peer_id != 0:
request_release_pot.rpc_id(1, get_path(), holder_peer_id)
# Also clear locally for immediate visual feedback
holder = null
holder_peer_id = 0
is_being_grabbed = false
hasShownSmokePuffs = false
#velocity = Vector2.ZERO
# Re-enable pot's collision layer when released
#self.set_collision_layer_value(8, true) # Re-enable pot's collision layer
indicate(true)
if $SfxDrag2.playing:
$SfxDrag2.stop()
else:
# Server can release directly
holder = null
holder_peer_id = 0
is_being_grabbed = false
hasShownSmokePuffs = false
#velocity = Vector2.ZERO
# Re-enable pot's collision layer when released
#self.set_collision_layer_value(8, true) # Re-enable pot's collision layer
indicate(true)
if $SfxDrag2.playing:
$SfxDrag2.stop()
pass
@rpc("any_peer", "reliable")
func request_release_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 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):
pot.holder = null
pot.holder_peer_id = 0
pot.is_being_grabbed = false
pot.velocity = Vector2.ZERO
# Re-enable pot's collision layer when released
pot.set_collision_layer_value(8, true) # Re-enable pot's collision layer
pot.indicate(true)
if $SfxDrag2.playing:
$SfxDrag2.stop()
pass
func lift(new_holder: CharacterBody2D):
if (new_holder != holder and holder != null) and "lose_held_entity" in holder:
# steal from holder
holder.lose_held_entity(self)
indicate(false)
$Area2DCollision.set_deferred("monitoring", false)
thrown_by = null
holder = new_holder
holder_peer_id = new_holder.get_multiplayer_authority()
# disable collisions with walls and players when lifted
self.set_collision_layer_value(8, false) # Disable pot's collision layer (so players can't collide with it)
self.set_collision_mask_value(7, false) # Disable collision with walls
self.set_collision_mask_value(8, false) # Disable collision with other pots
self.set_collision_mask_value(9, false) # Disable collision with players
self.set_collision_mask_value(10, false) # Disable collision with players (if using both)
is_being_lifted = true
is_being_grabbed = false
is_being_thrown = false
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()
@rpc("any_peer", "reliable")
func lift_rpc(holder_path: NodePath):
# 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:
lift(holder_node)
func put_down() -> bool:
if not is_being_lifted or is_being_put_down:
return false
var dropDir = holder.last_direction
dropDir.x *= 12
if dropDir.y > 0:
dropDir.y *= 10
else:
dropDir.y *= 10
put_down_target_pos = holder.global_position + dropDir
# First check: Direct space state query for walls
var space_state = get_world_2d().direct_space_state
var params = PhysicsPointQueryParameters2D.new()
params.position = put_down_target_pos
params.collision_mask = 64 # Layer for walls (usually layer 7)
params.collide_with_areas = true
params.collide_with_bodies = true
var results = space_state.intersect_point(params)
if results.size() > 0:
# Found overlapping walls at target position
return false
# Second check: Line of sight between player and target position
var query = PhysicsRayQueryParameters2D.create(
holder.global_position,
put_down_target_pos,
64 # Wall collision mask
)
query.collide_with_areas = true
query.collide_with_bodies = true
var result = space_state.intersect_ray(query)
if result:
# Hit something between player and target position
return false
# Third check: Make sure we're not placing on top of another pot or object
params.collision_mask = 128 # Layer for pots/objects
results = space_state.intersect_point(params)
if results.size() > 0:
# Found overlapping objects at target position
return false
$Area2DCollision.set_deferred("monitoring", false)
# Position is valid, proceed with putting down
self.set_collision_mask_value(7, true) # instantly reenenable collision with wall
is_being_put_down = true
is_being_lifted = false
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)
fade_tween.tween_property($Sprite2D, "modulate:a", 0.0, 0.6)
#await fade_tween.finished
await $SfxShatter.finished
if $SfxLand.playing:
$SfxLand.stop()
if $SfxDrag2.playing:
$SfxDrag2.stop()
$GPUParticles2D.emitting = false
#$SfxShatter.stop()
if multiplayer.is_server():
call_deferred("queue_free")
pass
func create4TileParticles():
var sprite_texture = $Sprite2D.texture
var frame_width = sprite_texture.get_width() / $Sprite2D.hframes
var frame_height = sprite_texture.get_height() / $Sprite2D.vframes
var frame_x = ($Sprite2D.frame % $Sprite2D.hframes) * frame_width
var frame_y = ($Sprite2D.frame / $Sprite2D.hframes) * frame_height
# Create 4 particles with different directions and different parts of the texture
var directions = [
Vector2(-1, -1).normalized(), # Top-left
Vector2(1, -1).normalized(), # Top-right
Vector2(-1, 1).normalized(), # Bottom-left
Vector2(1, 1).normalized() # Bottom-right
]
var regions = [
Rect2(frame_x, frame_y, frame_width / 2, frame_height / 2), # Top-left
Rect2(frame_x + frame_width / 2, frame_y, frame_width / 2, frame_height / 2), # Top-right
Rect2(frame_x, frame_y + frame_height / 2, frame_width / 2, frame_height / 2), # Bottom-left
Rect2(frame_x + frame_width / 2, frame_y + frame_height / 2, frame_width / 2, frame_height / 2) # Bottom-right
]
for i in range(4):
var tp = tileParticleScene.instantiate() as CharacterBody2D
var spr2D = tp.get_node("Sprite2D") as Sprite2D
tp.global_position = global_position
# Set up the sprite's texture and region
spr2D.texture = sprite_texture
spr2D.region_enabled = true
spr2D.region_rect = regions[i]
# Add some randomness to the velocity
var speed = randf_range(170, 200)
var dir = directions[i] + Vector2(randf_range(-0.2, 0.2), randf_range(-0.2, 0.2))
tp.velocity = dir * speed
# Add some rotation
tp.angular_velocity = randf_range(-7, 7)
get_parent().call_deferred("add_child", tp)
func indicate(iIndicate: bool):
$Indicator.visible = iIndicate
if !liftable or is_being_lifted:
$Indicator.visible = false
pass
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:
var collider = collision_shape[0]
var normal = (global_position - collider.global_position).normalized()
if abs(velocity.x) > 0.05 or abs(velocity.y) > 0.05:
if "take_damage" in body or body is TileMapLayer or collider is TileMapLayer:
if "take_damage" in body:
# Check if body is a player - if so, check if it's a joiner (needs RPC) or server (direct call)
# Otherwise, call directly (for enemies, etc.)
if "is_player" in body and body.is_player:
# Player - check if it's a joiner (needs RPC) or server (direct call)
# If the player's authority matches the server's unique ID, it's the server's own player
var player_authority = body.get_multiplayer_authority()
var is_server_player = (player_authority == 1) # Server's peer ID is 1
if is_server_player:
# Server's own player - call directly (server has authority)
body.take_damage(self, thrown_by)
else:
# Joiner player - use RPC with serializable parameters (joiner has authority)
var damager_pos = self.global_position
var damager_path = thrown_by.get_path() if thrown_by != null else ""
var damager_peer_id = thrown_by.get_multiplayer_authority() if thrown_by != null else 0
body.take_damage_rpc.rpc(damager_pos, damager_path, damager_peer_id)
else:
# Non-player (enemy, etc.) - call directly
body.take_damage(self, thrown_by)
elif collider != self and "breakPot" in collider:
collider.take_damage(self, thrown_by)
# create particles from pot:
take_damage.rpc(null, null)
pass
normal = velocity.normalized()
velocity = velocity.bounce(normal) * 0.4 # slow down
pass # Replace with function body.
func show_destroy_effect():
$GPUParticles2D.emitting = true
$Sprite2D.frame = 13 + 19 + 19
$Sprite2DShadow.visible = false
liftable = false
indicate(false)
create4TileParticles()
is_being_thrown = false
$Sprite2DShadow.visible = false
# Play shatter sound
$SfxShatter.play()
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):
# grab() function already disables collisions, but ensure it's done
player.grabbed_entity = pot
player.grabbed_entity_path = str(pot.get_path())
player.current_animation = "IDLE_PUSH"
# Lock direction to current last_direction when grabbing
player.locked_grab_direction = player.last_direction
# Sync to all clients (including the joiner who requested it)
player.set_grabbed_entity_path_rpc.rpc(str(pot.get_path()))
player.sync_animation.rpc("IDLE_PUSH")
@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
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))
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):
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
@rpc("call_local")
func take_damage(_iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
is_destroyed = true # will trigger show_destroy_effect for clients...
show_destroy_effect()
# remove all kind of collision since it's broken now...
self.set_deferred("monitoring", false)
self.set_collision_layer_value(8, false)
self.set_collision_mask_value(7, false)
self.set_collision_mask_value(8, false)
self.set_collision_mask_value(9, false)
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
pass
func _on_area_2d_collision_body_exited(_body: Node2D) -> void:
pass # Replace with function body.
func _on_area_2d_pickup_body_entered(_body: Node2D) -> void:
indicate(true)
pass # Replace with function body.
func _on_area_2d_pickup_body_exited(_body: Node2D) -> void:
indicate(false)
pass # Replace with function body.
func _on_timer_smoke_particles_timeout() -> void:
# Only stop particles if pot is not moving (for one-time effects like landing)
# If pot is being pushed/pulled, keep particles emitting
if not is_moving:
$GPUParticles2D.emitting = false
pass # Replace with function body.

View File

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

View File

@@ -1,349 +0,0 @@
[gd_scene format=3 uid="uid://bdlg5orah64m5"]
[ext_resource type="Script" uid="uid://bj0ueurl3vovc" path="res://scripts/entities/world/pot.gd" id="1_hsjxb"]
[ext_resource type="Texture2D" uid="uid://bu4dq78f8lgj5" path="res://assets/gfx/sheet_18.png" id="1_rxnv2"]
[ext_resource type="Texture2D" uid="uid://bknascfv4twmi" path="res://assets/gfx/smoke_puffs.png" id="2_cmff4"]
[ext_resource type="AudioStream" uid="uid://fl0rfi4in3n4" path="res://assets/audio/sfx/environment/pot/Drunk lad destroys plant pot.mp3" id="3_vktry"]
[ext_resource type="AudioStream" uid="uid://dejjc0uqthi1b" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound1.mp3" id="4_nb533"]
[ext_resource type="AudioStream" uid="uid://iuxunaogc8xr" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound2.mp3" id="5_cmff4"]
[ext_resource type="AudioStream" uid="uid://bfqusej0pbxem" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound3.mp3" id="6_lq20m"]
[ext_resource type="AudioStream" uid="uid://dq461vpiih3lc" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound4.mp3" id="7_76fyq"]
[ext_resource type="AudioStream" uid="uid://cg1ndvx4t7xtd" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound5.mp3" id="8_m11t2"]
[ext_resource type="AudioStream" uid="uid://bt5npaenq15h2" path="res://assets/audio/sfx/environment/pot/smaller_pot_crash.mp3" id="9_sb38x"]
[ext_resource type="Texture2D" uid="uid://b1twy68vd7f20" path="res://assets/gfx/pickups/indicator.png" id="10_nb533"]
[ext_resource type="AudioStream" uid="uid://bcy4qh0j2yuss" path="res://assets/audio/sfx/z3/lift.wav" id="11_lq20m"]
[ext_resource type="AudioStream" uid="uid://x0hhwyr2e1u7" path="res://assets/audio/sfx/environment/pot/pot_sweep_move_01.mp3" id="13_hd4fl"]
[ext_resource type="AudioStream" uid="uid://cc6clnct61uk7" path="res://assets/audio/sfx/environment/pot/pot_sweep_move_02.mp3" id="14_0qg0s"]
[ext_resource type="AudioStream" uid="uid://cdjtqf2gbagra" path="res://assets/audio/sfx/environment/pot/pot_sweep_move_03.mp3" id="15_p028i"]
[ext_resource type="AudioStream" uid="uid://bxsowyqt7v637" path="res://assets/audio/sfx/environment/pot/pot_place_01.mp3" id="16_fvw42"]
[ext_resource type="AudioStream" uid="uid://b8x1clggitcoa" path="res://assets/audio/sfx/environment/pot/pot_place_02.mp3" id="17_qjm0l"]
[ext_resource type="AudioStream" uid="uid://bgfvvwyvn128g" path="res://assets/audio/sfx/environment/pot/pot_place_03.mp3" id="18_xfa6j"]
[ext_resource type="AudioStream" uid="uid://67u74sfddmd6" path="res://assets/audio/sfx/environment/pot/pot_place_04.mp3" id="19_3e0oi"]
[ext_resource type="AudioStream" uid="uid://2w73l4k3704x" path="res://assets/audio/sfx/environment/pot/pot_drag1.mp3" id="19_p028i"]
[ext_resource type="AudioStream" uid="uid://cy740ysgtt5n7" path="res://assets/audio/sfx/environment/pot/pot_place_05.mp3" id="20_v2r3y"]
[ext_resource type="AudioStream" uid="uid://bnuh7ima5cq0n" path="res://assets/audio/sfx/environment/pot/pot_drag2.mp3" id="20_wv4em"]
[ext_resource type="AudioStream" uid="uid://co7i1f4t8qtqp" path="res://assets/audio/sfx/environment/pot/pot_place_06.mp3" id="21_0qg0s"]
[ext_resource type="AudioStream" uid="uid://ohm0t5c7hw0w" path="res://assets/audio/sfx/player/throw/throw_01.wav.mp3" id="21_hd4fl"]
[ext_resource type="FontFile" uid="uid://bajcvmidrnc33" path="res://assets/fonts/standard_font.png" id="25_p028i"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_hsjxb"]
properties/0/path = NodePath(".:position")
properties/0/spawn = true
properties/0/replication_mode = 2
properties/1/path = NodePath(".:positionZ")
properties/1/spawn = true
properties/1/replication_mode = 1
properties/2/path = NodePath(".:is_being_thrown")
properties/2/spawn = true
properties/2/replication_mode = 2
properties/3/path = NodePath(".:is_being_lifted")
properties/3/spawn = true
properties/3/replication_mode = 2
properties/4/path = NodePath(".:is_being_put_down")
properties/4/spawn = true
properties/4/replication_mode = 2
properties/5/path = NodePath(".:collision_mask")
properties/5/spawn = true
properties/5/replication_mode = 2
properties/6/path = NodePath(".:collision_layer")
properties/6/spawn = true
properties/6/replication_mode = 2
properties/7/path = NodePath("Area2DCollision:monitoring")
properties/7/spawn = true
properties/7/replication_mode = 2
properties/8/path = NodePath(".:is_destroyed")
properties/8/spawn = true
properties/8/replication_mode = 2
properties/9/path = NodePath(".:is_being_grabbed")
properties/9/spawn = true
properties/9/replication_mode = 2
properties/10/path = NodePath(".:is_moving")
properties/10/spawn = true
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)
colors = PackedColorArray(0, 0, 0, 0.764706, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_87nuj"]
gradient = SubResource("Gradient_nb533")
width = 16
height = 6
fill = 1
fill_from = Vector2(0.504274, 0.478632)
fill_to = Vector2(0.897436, 0.0769231)
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_lq20m"]
particles_animation = true
particles_anim_h_frames = 4
particles_anim_v_frames = 2
particles_anim_loop = false
[sub_resource type="Curve" id="Curve_76fyq"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.780549, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
point_count = 3
[sub_resource type="CurveTexture" id="CurveTexture_m11t2"]
curve = SubResource("Curve_76fyq")
[sub_resource type="Curve" id="Curve_sb38x"]
_limits = [0.0, 100.0, 0.0, 1.0]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.733167, 4.55855), 0.0, 0.0, 0, 0, Vector2(0.815461, 91.8906), 0.0, 0.0, 0, 0, Vector2(0.892768, 100), 0.0, 0.0, 0, 0]
point_count = 4
[sub_resource type="CurveTexture" id="CurveTexture_ui3li"]
curve = SubResource("Curve_sb38x")
[sub_resource type="Curve" id="Curve_dtubv"]
_limits = [0.0, 1.0, -1.0, 1.0]
_data = [Vector2(-1, 0), 0.0, 0.0, 0, 0, Vector2(0.0124688, 1), 0.0, 0.0, 0, 0, Vector2(0.516209, 1), 0.0, 0.0, 0, 0, Vector2(0.947631, 0), 0.0, 0.0, 0, 0]
point_count = 4
[sub_resource type="Curve" id="Curve_0qg0s"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 2
[sub_resource type="Curve" id="Curve_p028i"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 2
[sub_resource type="CurveXYZTexture" id="CurveXYZTexture_tjjlx"]
curve_x = SubResource("Curve_dtubv")
curve_y = SubResource("Curve_0qg0s")
curve_z = SubResource("Curve_p028i")
[sub_resource type="Curve" id="Curve_1webc"]
_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.0224439, 1), 0.0, 0.0, 0, 0, Vector2(0.880299, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
point_count = 4
[sub_resource type="CurveTexture" id="CurveTexture_sp8mg"]
curve = SubResource("Curve_1webc")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_iw3no"]
particle_flag_disable_z = true
direction = Vector3(1, 0.2, 0)
spread = 62.79
initial_velocity_min = -30.0
initial_velocity_max = 30.0
directional_velocity_min = -25.0
directional_velocity_max = 25.0
directional_velocity_curve = SubResource("CurveXYZTexture_tjjlx")
gravity = Vector3(0, 0, 0)
damping_max = 100.0
damping_curve = SubResource("CurveTexture_ui3li")
scale_min = 0.8
scale_max = 1.2
scale_curve = SubResource("CurveTexture_sp8mg")
color = Color(1, 1, 1, 0.709804)
alpha_curve = SubResource("CurveTexture_m11t2")
anim_offset_max = 1.0
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hsjxb"]
size = Vector2(12, 8)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_87nuj"]
size = Vector2(18, 15)
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_ui3li"]
streams_count = 7
stream_0/stream = ExtResource("3_vktry")
stream_1/stream = ExtResource("4_nb533")
stream_2/stream = ExtResource("5_cmff4")
stream_3/stream = ExtResource("6_lq20m")
stream_4/stream = ExtResource("7_76fyq")
stream_5/stream = ExtResource("8_m11t2")
stream_6/stream = ExtResource("9_sb38x")
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_wv4em"]
streams_count = 2
stream_0/stream = ExtResource("19_p028i")
stream_1/stream = ExtResource("20_wv4em")
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_fvw42"]
streams_count = 3
stream_0/stream = ExtResource("13_hd4fl")
stream_1/stream = ExtResource("14_0qg0s")
stream_2/stream = ExtResource("15_p028i")
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_icnv3"]
streams_count = 6
stream_0/stream = ExtResource("16_fvw42")
stream_1/stream = ExtResource("17_qjm0l")
stream_2/stream = ExtResource("18_xfa6j")
stream_3/stream = ExtResource("19_3e0oi")
stream_4/stream = ExtResource("20_v2r3y")
stream_5/stream = ExtResource("21_0qg0s")
[sub_resource type="Animation" id="Animation_lq20m"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:offset")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(0, 0)]
}
[sub_resource type="Animation" id="Animation_cmff4"]
resource_name = "indicate"
length = 0.8
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:offset")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.4, 0.8),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector2(0, 0), Vector2(0, -1), Vector2(0, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_76fyq"]
_data = {
&"RESET": SubResource("Animation_lq20m"),
&"indicate": SubResource("Animation_cmff4")
}
[sub_resource type="RectangleShape2D" id="RectangleShape2D_nb533"]
size = Vector2(14, 10)
[node name="Pot" type="CharacterBody2D" unique_id=364317200]
collision_layer = 128
collision_mask = 960
script = ExtResource("1_hsjxb")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=1524006920]
replication_config = SubResource("SceneReplicationConfig_hsjxb")
[node name="Sprite2DShadow" type="Sprite2D" parent="." unique_id=817113939]
z_index = -1
position = Vector2(0, 3)
texture = SubResource("GradientTexture2D_87nuj")
[node name="GPUParticles2D" type="GPUParticles2D" parent="." unique_id=779103664]
material = SubResource("CanvasItemMaterial_lq20m")
emitting = false
amount = 16
texture = ExtResource("2_cmff4")
interp_to_end = 0.026
preprocess = 0.16
explosiveness = 0.5
randomness = 0.48
use_fixed_seed = true
seed = 1565624367
process_material = SubResource("ParticleProcessMaterial_iw3no")
[node name="TimerSmokeParticles" type="Timer" parent="GPUParticles2D" unique_id=895713341]
wait_time = 0.12
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=720950344]
position = Vector2(0, -4)
texture = ExtResource("1_rxnv2")
hframes = 19
vframes = 19
frame = 14
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1841795873]
visible = false
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_hsjxb")
[node name="Area2DPickup" type="Area2D" parent="." unique_id=867581108]
visible = false
collision_layer = 1024
collision_mask = 512
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickup" unique_id=1113571525]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_87nuj")
debug_color = Color(0.688142, 0.7, 0.0440007, 0.42)
[node name="SfxShatter" type="AudioStreamPlayer2D" parent="." unique_id=301402142]
stream = SubResource("AudioStreamRandomizer_ui3li")
attenuation = 9.84915
panning_strength = 1.46
bus = &"Sfx"
[node name="SfxDrag" type="AudioStreamPlayer2D" parent="." unique_id=955994941]
stream = SubResource("AudioStreamRandomizer_wv4em")
volume_db = -10.142
bus = &"Sfx"
[node name="SfxDrag2" type="AudioStreamPlayer2D" parent="." unique_id=895274113]
stream = SubResource("AudioStreamRandomizer_fvw42")
volume_db = -9.703
pitch_scale = 0.77
max_distance = 749.0
attenuation = 10.1965
panning_strength = 1.5
bus = &"Sfx"
[node name="SfxLand" type="AudioStreamPlayer2D" parent="." unique_id=37979613]
stream = SubResource("AudioStreamRandomizer_icnv3")
attenuation = 6.9644
panning_strength = 1.25
bus = &"Sfx"
[node name="SfxThrow" type="AudioStreamPlayer2D" parent="." unique_id=465813543]
stream = ExtResource("21_hd4fl")
volume_db = -4.708
pitch_scale = 0.54
bus = &"Sfx"
[node name="SfxDrop" type="AudioStreamPlayer2D" parent="." unique_id=1042116419]
bus = &"Sfx"
[node name="SfxPickup" type="AudioStreamPlayer2D" parent="." unique_id=1755295916]
stream = ExtResource("11_lq20m")
[node name="Indicator" type="Sprite2D" parent="." unique_id=1563579882]
position = Vector2(0, -11)
texture = ExtResource("10_nb533")
[node name="AnimationPlayer" type="AnimationPlayer" parent="Indicator" unique_id=112267323]
libraries/ = SubResource("AnimationLibrary_76fyq")
autoplay = &"indicate"
[node name="Area2DCollision" type="Area2D" parent="." unique_id=15533254]
visible = false
collision_layer = 1024
collision_mask = 704
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DCollision" unique_id=1567360351]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_nb533")
debug_color = Color(0.7, 0.132592, 0.232379, 0.42)
[node name="LabelPotStateNDirectionNSpeed" type="Label" parent="." unique_id=1790795234]
z_index = 18
z_as_relative = false
offset_left = -29.82
offset_top = -40.0
offset_right = 30.18
offset_bottom = -34.0
size_flags_horizontal = 3
size_flags_vertical = 6
theme_override_constants/outline_size = 6
theme_override_fonts/font = ExtResource("25_p028i")
theme_override_font_sizes/font_size = 6
horizontal_alignment = 1
[connection signal="timeout" from="GPUParticles2D/TimerSmokeParticles" to="." method="_on_timer_smoke_particles_timeout"]
[connection signal="body_entered" from="Area2DPickup" to="." method="_on_area_2d_pickup_body_entered"]
[connection signal="body_exited" from="Area2DPickup" to="." method="_on_area_2d_pickup_body_exited"]
[connection signal="body_entered" from="Area2DCollision" to="." method="_on_area_2d_collision_body_entered"]
[connection signal="body_exited" from="Area2DCollision" to="." method="_on_area_2d_collision_body_exited"]

View File

@@ -1,152 +0,0 @@
[gd_scene load_steps=21 format=3 uid="uid://bdlg5orah64m5"]
[ext_resource type="Script" uid="uid://bj0ueurl3vovc" path="res://scripts/entities/world/pot.gd" id="1_hsjxb"]
[ext_resource type="Texture2D" uid="uid://bu4dq78f8lgj5" path="res://assets/gfx/sheet_18.png" id="1_rxnv2"]
[ext_resource type="AudioStream" uid="uid://fl0rfi4in3n4" path="res://assets/audio/sfx/environment/pot/Drunk lad destroys plant pot.mp3" id="3_vktry"]
[ext_resource type="AudioStream" uid="uid://dejjc0uqthi1b" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound1.mp3" id="4_nb533"]
[ext_resource type="AudioStream" uid="uid://iuxunaogc8xr" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound2.mp3" id="5_cmff4"]
[ext_resource type="AudioStream" uid="uid://bfqusej0pbxem" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound3.mp3" id="6_lq20m"]
[ext_resource type="AudioStream" uid="uid://dq461vpiih3lc" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound4.mp3" id="7_76fyq"]
[ext_resource type="AudioStream" uid="uid://cg1ndvx4t7xtd" path="res://assets/audio/sfx/environment/pot/pot_destroy_sound5.mp3" id="8_m11t2"]
[ext_resource type="AudioStream" uid="uid://bt5npaenq15h2" path="res://assets/audio/sfx/environment/pot/smaller_pot_crash.mp3" id="9_sb38x"]
[ext_resource type="Texture2D" uid="uid://b1twy68vd7f20" path="res://assets/gfx/pickups/indicator.png" id="10_nb533"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_hsjxb"]
properties/0/path = NodePath(".:position")
properties/0/spawn = true
properties/0/replication_mode = 2
[sub_resource type="Gradient" id="Gradient_nb533"]
offsets = PackedFloat32Array(0.847255, 0.861575)
colors = PackedColorArray(0, 0, 0, 0.764706, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_87nuj"]
gradient = SubResource("Gradient_nb533")
width = 16
height = 6
fill = 1
fill_from = Vector2(0.504274, 0.478632)
fill_to = Vector2(0.897436, 0.0769231)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hsjxb"]
size = Vector2(12, 8)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_87nuj"]
size = Vector2(18, 15)
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_ui3li"]
streams_count = 7
stream_0/stream = ExtResource("3_vktry")
stream_1/stream = ExtResource("4_nb533")
stream_2/stream = ExtResource("5_cmff4")
stream_3/stream = ExtResource("6_lq20m")
stream_4/stream = ExtResource("7_76fyq")
stream_5/stream = ExtResource("8_m11t2")
stream_6/stream = ExtResource("9_sb38x")
[sub_resource type="Animation" id="Animation_cmff4"]
resource_name = "indicate"
length = 0.8
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:offset")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.4, 0.8),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector2(0, 0), Vector2(0, -1), Vector2(0, 0)]
}
[sub_resource type="Animation" id="Animation_lq20m"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:offset")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(0, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_76fyq"]
_data = {
&"RESET": SubResource("Animation_lq20m"),
&"indicate": SubResource("Animation_cmff4")
}
[sub_resource type="RectangleShape2D" id="RectangleShape2D_nb533"]
size = Vector2(14, 9)
[node name="Pot" type="CharacterBody2D"]
collision_layer = 128
collision_mask = 64
script = ExtResource("1_hsjxb")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_hsjxb")
[node name="Sprite2DShadow" type="Sprite2D" parent="."]
position = Vector2(0, 3)
texture = SubResource("GradientTexture2D_87nuj")
[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(0, -4)
texture = ExtResource("1_rxnv2")
hframes = 19
vframes = 19
frame = 14
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_hsjxb")
[node name="Area2DPickup" type="Area2D" parent="."]
collision_layer = 1024
collision_mask = 512
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickup"]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_87nuj")
debug_color = Color(0.688142, 0.7, 0.0440007, 0.42)
[node name="SfxShatter" type="AudioStreamPlayer2D" parent="."]
stream = SubResource("AudioStreamRandomizer_ui3li")
attenuation = 9.84915
panning_strength = 1.46
bus = &"Sfx"
[node name="SfxThrow" type="AudioStreamPlayer2D" parent="."]
[node name="SfxDrop" type="AudioStreamPlayer2D" parent="."]
[node name="Indicator" type="Sprite2D" parent="."]
position = Vector2(0, -11)
texture = ExtResource("10_nb533")
[node name="AnimationPlayer" type="AnimationPlayer" parent="Indicator"]
libraries = {
&"": SubResource("AnimationLibrary_76fyq")
}
autoplay = "indicate"
[node name="Area2DCollision" type="Area2D" parent="."]
collision_layer = 1024
collision_mask = 704
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DCollision"]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_nb533")
debug_color = Color(0.7, 0.132592, 0.232379, 0.42)
[connection signal="body_entered" from="Area2DPickup" to="." method="_on_area_2d_pickup_body_entered"]
[connection signal="body_exited" from="Area2DPickup" to="." method="_on_area_2d_pickup_body_exited"]
[connection signal="body_entered" from="Area2DCollision" to="." method="_on_area_2d_collision_body_entered"]
[connection signal="body_exited" from="Area2DCollision" to="." method="_on_area_2d_collision_body_exited"]

View File

@@ -1,175 +0,0 @@
[gd_scene load_steps=19 format=3 uid="uid://bcxk63irehw1d"]
[ext_resource type="Texture2D" uid="uid://bbvdtm5iqv7a" path="res://assets/gfx/props/wall_torch.png" id="1_wyl82"]
[ext_resource type="Shader" uid="uid://cvksy3guq65ie" path="res://assets/shaders/fire.gdshader" id="2_hab2u"]
[ext_resource type="AudioStream" uid="uid://y4632vubgvmk" path="res://assets/audio/sfx/environment/torch/torch_burn_02.wav.mp3" id="3_hab2u"]
[sub_resource type="Gradient" id="Gradient_wyl82"]
colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_hab2u"]
gradient = SubResource("Gradient_wyl82")
[sub_resource type="Gradient" id="Gradient_7mycd"]
[sub_resource type="GradientTexture1D" id="GradientTexture1D_272bh"]
gradient = SubResource("Gradient_7mycd")
[sub_resource type="Gradient" id="Gradient_5vw27"]
[sub_resource type="FastNoiseLite" id="FastNoiseLite_kek77"]
noise_type = 0
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_4c57u"]
color_ramp = SubResource("Gradient_5vw27")
noise = SubResource("FastNoiseLite_kek77")
[sub_resource type="ShaderMaterial" id="ShaderMaterial_kek77"]
shader = ExtResource("2_hab2u")
shader_parameter/noise_tex = SubResource("NoiseTexture2D_4c57u")
shader_parameter/gradient_tex = SubResource("GradientTexture1D_272bh")
shader_parameter/brighter_color = Color(1, 0.8, 0, 1)
shader_parameter/middle_color = Color(1, 0.474005, 0.175976, 1)
shader_parameter/darker_color = Color(0.621094, 0.123033, 0.0859316, 1)
shader_parameter/spread = 0.526
shader_parameter/amount = 32
[sub_resource type="Gradient" id="Gradient_272bh"]
offsets = PackedFloat32Array(0.603819, 0.673031)
colors = PackedColorArray(1, 0.843137, 0.603922, 0.337255, 1, 1, 1, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_5vw27"]
gradient = SubResource("Gradient_272bh")
width = 22
height = 22
fill = 1
fill_from = Vector2(0.512821, 0.461538)
[sub_resource type="Animation" id="Animation_272bh"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [0]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("FireGlow:scale")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(1, 1)]
}
[sub_resource type="Animation" id="Animation_7mycd"]
resource_name = "torch"
length = 1.6
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("FireGlow:scale")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.199912, 0.401399, 0.601311, 0.794927, 0.994839, 1.19633, 1.39624),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [Vector2(1, 1), Vector2(1.1, 1.1), Vector2(1, 1), Vector2(1.1, 1.1), Vector2(1, 1), Vector2(1.1, 1.1), Vector2(1, 1), Vector2(1.1, 1.1)]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("TorchLight:scale")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.3, 0.4, 0.7, 0.8, 1.1, 1.2, 1.5),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [Vector2(1, 1), Vector2(1.05, 1.05), Vector2(1.1, 1.1), Vector2(1.05, 1.05), Vector2(1, 1), Vector2(1.05, 1.05), Vector2(1.1, 1.1), Vector2(1.05, 1.05)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_5vw27"]
_data = {
&"RESET": SubResource("Animation_272bh"),
&"torch": SubResource("Animation_7mycd")
}
[sub_resource type="Gradient" id="Gradient_lquwl"]
offsets = PackedFloat32Array(0.742243, 0.74463)
colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_lt5sp"]
gradient = SubResource("Gradient_lquwl")
fill = 1
fill_from = Vector2(0.508547, 0.487179)
fill_to = Vector2(0.974359, 0.0470085)
[node name="TorchWall" type="Sprite2D"]
z_index = 2
y_sort_enabled = true
texture = ExtResource("1_wyl82")
hframes = 3
[node name="Sprite2D" type="Sprite2D" parent="."]
visible = false
texture = SubResource("GradientTexture2D_hab2u")
[node name="ColorRect" type="ColorRect" parent="."]
visible = false
material = SubResource("ShaderMaterial_kek77")
offset_left = -8.0
offset_top = -25.0
offset_right = 7.0
offset_bottom = -3.0
[node name="FireGlow" type="Sprite2D" parent="."]
position = Vector2(0, -2)
texture = SubResource("GradientTexture2D_5vw27")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_5vw27")
}
autoplay = "torch"
[node name="TorchLight" type="PointLight2D" parent="."]
z_index = 10
position = Vector2(0, -1)
blend_mode = 2
range_layer_max = 2
texture = SubResource("GradientTexture2D_lt5sp")
[node name="SfxTorch" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("3_hab2u")
volume_db = 2.996
autoplay = true
max_distance = 509.0
attenuation = 16.5642
max_polyphony = 2
panning_strength = 1.89
bus = &"Sfx"

View File

@@ -0,0 +1,35 @@
extends Node2D
# Floating text that rises and fades out
@onready var label = $Label
var text: String = ""
var color: Color = Color.WHITE
var duration: float = 1.0
var rise_distance: float = 30.0
func setup(text_value: String, text_color: Color):
text = text_value
color = text_color
if label:
label.text = text
label.modulate = color
func _ready():
# Animate rising and fading
var tween = create_tween()
tween.set_parallel(true)
# Move upward
var start_pos = global_position
var end_pos = start_pos + Vector2(0, -rise_distance)
tween.tween_property(self, "global_position", end_pos, duration)
# Fade out
tween.tween_property(label, "modulate:a", 0.0, duration)
# Remove after animation
tween.tween_callback(queue_free).set_delay(duration)

View File

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

114
src/scripts/game_ui.gd Normal file
View File

@@ -0,0 +1,114 @@
extends CanvasLayer
# Game UI - Main menu and multiplayer lobby
@onready var main_menu = $Control/MainMenu
@onready var host_button = $Control/MainMenu/VBoxContainer/HostButton
@onready var join_button = $Control/MainMenu/VBoxContainer/JoinButton
@onready var local_players_spinbox = $Control/MainMenu/VBoxContainer/LocalPlayersContainer/SpinBox
@onready var address_input = $Control/MainMenu/VBoxContainer/AddressContainer/AddressInput
@onready var network_manager = $"/root/NetworkManager"
func _ready():
# Wait for nodes to be ready
await get_tree().process_frame
# Debug: Print node paths
print("GameUI _ready() called")
print("Main menu node: ", main_menu)
print("Host button node: ", host_button)
print("Join button node: ", join_button)
# Verify nodes exist
if not host_button:
push_error("host_button is null! Check node path: $Control/MainMenu/VBoxContainer/HostButton")
return
if not join_button:
push_error("join_button is null! Check node path: $Control/MainMenu/VBoxContainer/JoinButton")
return
# Connect buttons
host_button.pressed.connect(_on_host_pressed)
join_button.pressed.connect(_on_join_pressed)
# Connect network signals
if network_manager:
network_manager.connection_succeeded.connect(_on_connection_succeeded)
network_manager.connection_failed.connect(_on_connection_failed)
else:
push_error("NetworkManager not found!")
# Check for command-line arguments
_check_command_line_args()
func _check_command_line_args():
var args = OS.get_cmdline_args()
# Parse arguments
var should_host = false
var should_join = false
var join_address = "127.0.0.1"
var local_count = 1
for arg in args:
if arg == "--host":
should_host = true
elif arg == "--join":
should_join = true
elif arg.begins_with("--address="):
join_address = arg.split("=")[1]
elif arg.begins_with("--players="):
local_count = int(arg.split("=")[1])
# Auto-start based on arguments
if should_host:
print("Auto-hosting due to --host argument")
network_manager.set_local_player_count(local_count)
if network_manager.host_game():
_start_game()
elif should_join:
print("Auto-joining to ", join_address, " due to --join argument")
address_input.text = join_address
network_manager.set_local_player_count(local_count)
if network_manager.join_game(join_address):
# Connection callback will handle starting the game
pass
func _on_host_pressed():
var local_count = int(local_players_spinbox.value)
network_manager.set_local_player_count(local_count)
if network_manager.host_game():
print("Hosting game with ", local_count, " local players")
_start_game()
func _on_join_pressed():
var address = address_input.text
if address.is_empty():
address = "127.0.0.1"
var local_count = int(local_players_spinbox.value)
network_manager.set_local_player_count(local_count)
if network_manager.join_game(address):
print("Joining game at ", address, " with ", local_count, " local players")
func _on_connection_succeeded():
print("Connection succeeded, starting game")
_start_game()
func _on_connection_failed():
print("Connection failed")
# Show error message
var error_label = Label.new()
error_label.text = "Failed to connect to server"
error_label.modulate = Color.RED
main_menu.add_child(error_label)
func _start_game():
# Hide menu
main_menu.visible = false
# Load the game scene
get_tree().change_scene_to_file("res://scenes/game_world.tscn")

View File

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

1759
src/scripts/game_world.gd Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More