replace with multiplayer-coop files
This commit is contained in:
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
uid://c8t6r0pvo3ko8
|
||||
@@ -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"]
|
||||
@@ -1,3 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://sc01ppjgkfew"]
|
||||
|
||||
[node name="GameManager" type="Node2D"]
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
uid://ct73f1m77ayyp
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
uid://5o1ua60xh3jr
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
uid://cefxlk4jnptp3
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
uid://bkc5yi3tjw22m
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
uid://0qhn3bjmlb24
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
uid://p1jghmgvhqic
|
||||
@@ -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"]
|
||||
1
src/scripts/blood_clot.gd.uid
Normal file
1
src/scripts/blood_clot.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://coukcjucg7e3q
|
||||
@@ -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)
|
||||
@@ -1 +0,0 @@
|
||||
uid://bmr3h7slu80ps
|
||||
@@ -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")
|
||||
@@ -1 +0,0 @@
|
||||
uid://dkaeib51vnhsn
|
||||
@@ -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")
|
||||
@@ -1 +0,0 @@
|
||||
uid://5ik7rqtmhcxb
|
||||
21
src/scripts/create_circle_sprite.gd
Normal file
21
src/scripts/create_circle_sprite.gd
Normal 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)
|
||||
|
||||
1
src/scripts/create_circle_sprite.gd.uid
Normal file
1
src/scripts/create_circle_sprite.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c0s0wojriytx2
|
||||
15
src/scripts/create_rect_sprite.gd
Normal file
15
src/scripts/create_rect_sprite.gd
Normal 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)
|
||||
|
||||
1
src/scripts/create_rect_sprite.gd.uid
Normal file
1
src/scripts/create_rect_sprite.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b51gx0vv01plt
|
||||
24
src/scripts/create_shadow_sprite.gd
Normal file
24
src/scripts/create_shadow_sprite.gd
Normal 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
|
||||
|
||||
1
src/scripts/create_shadow_sprite.gd.uid
Normal file
1
src/scripts/create_shadow_sprite.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cpxabh3uq1kl4
|
||||
1
src/scripts/damage_number.gd.uid
Normal file
1
src/scripts/damage_number.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d0pan7uclj871
|
||||
102
src/scripts/debug_overlay.gd
Normal file
102
src/scripts/debug_overlay.gd
Normal 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)
|
||||
1
src/scripts/debug_overlay.gd.uid
Normal file
1
src/scripts/debug_overlay.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://wff5063ctp7g
|
||||
88
src/scripts/door.gd
Normal file
88
src/scripts/door.gd
Normal 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
1
src/scripts/door.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://do4062ppepheo
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
uid://b0o8i0uuloh7d
|
||||
uid://b080rtj3kr1cr
|
||||
|
||||
456
src/scripts/enemy_base.gd
Normal file
456
src/scripts/enemy_base.gd
Normal 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
|
||||
1
src/scripts/enemy_base.gd.uid
Normal file
1
src/scripts/enemy_base.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bi28l5enc3dsh
|
||||
158
src/scripts/enemy_bat.gd
Normal file
158
src/scripts/enemy_bat.gd
Normal 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()
|
||||
1
src/scripts/enemy_bat.gd.uid
Normal file
1
src/scripts/enemy_bat.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c0wywibyp77c
|
||||
959
src/scripts/enemy_humanoid.gd
Normal file
959
src/scripts/enemy_humanoid.gd
Normal 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)
|
||||
1
src/scripts/enemy_humanoid.gd.uid
Normal file
1
src/scripts/enemy_humanoid.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cp3be4swhhwep
|
||||
131
src/scripts/enemy_rat.gd
Normal file
131
src/scripts/enemy_rat.gd
Normal 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()
|
||||
1
src/scripts/enemy_rat.gd.uid
Normal file
1
src/scripts/enemy_rat.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://buro2p1le1anc
|
||||
232
src/scripts/enemy_slime.gd
Normal file
232
src/scripts/enemy_slime.gd
Normal 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()
|
||||
1
src/scripts/enemy_slime.gd.uid
Normal file
1
src/scripts/enemy_slime.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://id0s5um3dac1
|
||||
201
src/scripts/enemy_spawner.gd
Normal file
201
src/scripts/enemy_spawner.gd
Normal 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")
|
||||
1
src/scripts/enemy_spawner.gd.uid
Normal file
1
src/scripts/enemy_spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://f5h1crx3hl28
|
||||
@@ -1 +0,0 @@
|
||||
uid://qypk3dppiibf
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
uid://dhrlybbkwfyqr
|
||||
@@ -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"]
|
||||
@@ -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")
|
||||
@@ -1,9 +0,0 @@
|
||||
extends CharacterBody2D
|
||||
|
||||
func _ready() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
|
||||
pass
|
||||
@@ -1 +0,0 @@
|
||||
uid://f7yt42uhe246
|
||||
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
uid://djftsndq45u0a
|
||||
@@ -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"]
|
||||
@@ -1 +0,0 @@
|
||||
uid://d3vwfoc63u5fv
|
||||
@@ -1 +0,0 @@
|
||||
uid://j4ww03kd5vmc
|
||||
@@ -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"]
|
||||
@@ -1 +0,0 @@
|
||||
uid://d2ngv2yvtmnaf
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
uid://yid4hjp68enj
|
||||
@@ -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
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
uid://co7kkfmgjc54b
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
uid://cvvy2s6620mcw
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
uid://k2287cw2fdes
|
||||
@@ -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"]
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
uid://bj0ueurl3vovc
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
@@ -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"
|
||||
35
src/scripts/floating_text.gd
Normal file
35
src/scripts/floating_text.gd
Normal 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)
|
||||
|
||||
1
src/scripts/floating_text.gd.uid
Normal file
1
src/scripts/floating_text.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dx5oym20rr2ei
|
||||
114
src/scripts/game_ui.gd
Normal file
114
src/scripts/game_ui.gd
Normal 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")
|
||||
1
src/scripts/game_ui.gd.uid
Normal file
1
src/scripts/game_ui.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ofhtysy8r43v
|
||||
1759
src/scripts/game_world.gd
Normal file
1759
src/scripts/game_world.gd
Normal file
File diff suppressed because it is too large
Load Diff
1
src/scripts/game_world.gd.uid
Normal file
1
src/scripts/game_world.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://db58xcyo4cjk
|
||||
1
src/scripts/inspiration_scripts/character_stats.gd.uid
Normal file
1
src/scripts/inspiration_scripts/character_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dvcubtup4odug
|
||||
1
src/scripts/inspiration_scripts/coin.gd.uid
Normal file
1
src/scripts/inspiration_scripts/coin.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dxkd5t8jbbmdm
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user