Added rpg system for combat

added lots of loot to find
added level up system
This commit is contained in:
2026-01-11 23:12:09 +01:00
parent ab16194c39
commit 3a7fb29d58
32 changed files with 5076 additions and 96 deletions

View File

@@ -0,0 +1,76 @@
[gd_scene format=3 uid="uid://cvjj4wo2agd2k"]
[ext_resource type="Texture2D" uid="uid://ba772auc1t65n" path="res://assets/gfx/arrow.png" id="1_bey2v"]
[ext_resource type="Script" uid="uid://dqbctups3eri6" path="res://scripts/attack_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" unique_id=1519950765]
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="." unique_id=1423726915]
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="." unique_id=1110211076]
texture = ExtResource("1_bey2v")
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1017444254]
shape = SubResource("RectangleShape2D_b6ybh")
[node name="ArrowArea" type="Area2D" parent="." unique_id=1075536929]
collision_layer = 0
collision_mask = 75
priority = 1
[node name="CollisionShape2D" type="CollisionShape2D" parent="ArrowArea" unique_id=926133347]
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="." unique_id=1413495156]
stream = ExtResource("3_o8cb2")
pitch_scale = 1.61
max_polyphony = 4
[node name="SfxImpactWall" type="AudioStreamPlayer2D" parent="." unique_id=1132726967]
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="." unique_id=463883211]
stream = ExtResource("4_ol4b0")
volume_db = -4.685
pitch_scale = 1.47
max_polyphony = 4
[connection signal="area_entered" from="ArrowArea" to="." method="_on_arrow_area_area_entered"]
[connection signal="body_entered" from="ArrowArea" to="." method="_on_arrow_area_body_entered"]

View File

@@ -0,0 +1,86 @@
[gd_scene format=3 uid="uid://tcobiw1iirdw"]
[ext_resource type="Script" uid="uid://bqrtsr3mjvv3j" path="res://scripts/attack_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" unique_id=1568208090]
z_index = 10
y_sort_enabled = true
script = ExtResource("1_xo3v0")
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=461038063]
texture = ExtResource("2_lwt2c")
[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=691292922]
libraries/ = SubResource("AnimationLibrary_hj6i2")
[node name="AttackSwosh" type="AudioStreamPlayer2D" parent="." unique_id=515041905]
stream = ExtResource("3_v2p0x")
pitch_scale = 0.74
autoplay = true
[node name="DamageArea" type="Area2D" parent="." unique_id=985585639]
collision_layer = 0
collision_mask = 75
[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea" unique_id=805714782]
shape = SubResource("RectangleShape2D_3jdng")
debug_color = Color(0.7, 0, 0.18232, 0.42)
[node name="MeleeImpactWall" type="AudioStreamPlayer2D" parent="." unique_id=1016195072]
stream = ExtResource("4_ul7bj")
volume_db = -4.0
pitch_scale = 1.3
max_polyphony = 4
[node name="MeleeImpact" type="AudioStreamPlayer2D" parent="." unique_id=1548143473]
stream = ExtResource("5_whqew")
volume_db = -5.622
pitch_scale = 1.43
max_polyphony = 4
[connection signal="area_entered" from="DamageArea" to="." method="_on_damage_area_area_entered"]
[connection signal="body_entered" from="DamageArea" to="." method="_on_damage_area_body_entered"]

View File

@@ -0,0 +1,86 @@
[gd_scene format=3 uid="uid://b3my31y2ljai1"]
[ext_resource type="Script" uid="uid://ddprn0wrasavr" path="res://scripts/attack_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" unique_id=785354531]
z_index = 10
y_sort_enabled = true
script = ExtResource("1_psi1x")
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=1051719548]
texture = ExtResource("2_rh1o6")
[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=907474922]
libraries/ = SubResource("AnimationLibrary_hj6i2")
[node name="AttackSwosh" type="AudioStreamPlayer2D" parent="." unique_id=1397921535]
stream = ExtResource("3_j7ui3")
pitch_scale = 1.4
autoplay = true
[node name="DamageArea" type="Area2D" parent="." unique_id=1687133888]
collision_layer = 0
collision_mask = 75
[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea" unique_id=376828642]
shape = SubResource("RectangleShape2D_3jdng")
debug_color = Color(0.7, 0, 0.18232, 0.42)
[node name="MeleeImpactWall" type="AudioStreamPlayer2D" parent="." unique_id=651866255]
stream = ExtResource("4_cijfq")
volume_db = -4.0
pitch_scale = 1.3
max_polyphony = 4
[node name="MeleeImpact" type="AudioStreamPlayer2D" parent="." unique_id=35588340]
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"]

169
src/scripts/attack_arrow.gd Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1266,7 +1266,7 @@ func _place_enemies_in_room(room: Dictionary, grid: Array, map_size: Vector2i, r
# Randomize stats (similar to player but weaker)
# Base stats vary by enemy type
var max_health = rng.randf_range(30.0, 60.0)
var max_health = rng.randf_range(18.0, 35.0) # Reduced from 30.0-60.0 for better balance
var move_speed: float
var damage = rng.randf_range(5.0, 15.0)

View File

@@ -4,10 +4,12 @@ extends CharacterBody2D
@export var max_health: float = 50.0
@export var move_speed: float = 80.0
@export var damage: float = 10.0
@export var damage: float = 10.0 # Legacy - use character_stats.damage instead
@export var attack_cooldown: float = 1.0
@export var exp_reward: float = 10.0 # EXP granted when this enemy is defeated
var current_health: float = 50.0
var character_stats: CharacterStats # RPG stats system (same as players)
var is_dead: bool = false
var target_player: Node = null
var attack_timer: float = 0.0
@@ -35,6 +37,9 @@ var anim_speed: float = 0.15 # Seconds per frame
@onready var collision_shape = get_node_or_null("CollisionShape2D")
func _ready():
# Initialize CharacterStats for RPG system
_initialize_character_stats()
current_health = max_health
add_to_group("enemy")
@@ -51,6 +56,27 @@ func _ready():
# Walls are on layer 7 (bit 6 = 64), not layer 4!
collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
# Initialize CharacterStats for this enemy
# Override in subclasses to set specific baseStats
func _initialize_character_stats():
character_stats = CharacterStats.new()
character_stats.character_type = "enemy"
character_stats.character_name = name
# Default stats - override in subclasses
character_stats.baseStats.str = 10
character_stats.baseStats.dex = 10
character_stats.baseStats.int = 10
character_stats.baseStats.end = 10
character_stats.baseStats.wis = 10
character_stats.baseStats.cha = 10
character_stats.baseStats.lck = 10
# Initialize hp and mp
character_stats.hp = character_stats.maxhp
character_stats.mp = character_stats.maxmp
# Sync max_health and current_health from character_stats (for backwards compatibility)
max_health = character_stats.maxhp
current_health = character_stats.hp
func _physics_process(delta):
if is_dead:
# Even when dead, allow knockback to continue briefly
@@ -259,7 +285,7 @@ func _find_nearest_player_to_position(pos: Vector2, max_range: float = 100.0) ->
return nearest
func take_damage(amount: float, from_position: Vector2):
func take_damage(amount: float, from_position: Vector2, is_critical: bool = false):
# Only process damage on server/authority
if not is_multiplayer_authority():
return
@@ -273,8 +299,41 @@ func take_damage(amount: float, from_position: Vector2):
if nearest_player:
killer_player = nearest_player # Update killer to the most recent attacker
current_health -= amount
print(name, " took ", amount, " damage! Health: ", current_health)
# Check for dodge chance (based on DEX) - same as players
var _was_dodged = false
if character_stats:
var dodge_roll = randf()
var dodge_chance = character_stats.dodge_chance
if dodge_roll < dodge_chance:
_was_dodged = true
print(name, " DODGED the attack! (DEX: ", character_stats.baseStats.dex + character_stats.get_pass("dex"), ", dodge chance: ", dodge_chance * 100.0, "%)")
# Show "DODGED" text
_show_damage_number(0.0, from_position, false, false, true) # is_dodged = true
# Sync dodge visual to clients
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)
return # No damage taken, exit early
# If not dodged, apply damage with DEF reduction
var actual_damage = amount
if character_stats:
# Calculate damage after DEF reduction (critical hits pierce 80% of DEF)
actual_damage = character_stats.calculate_damage(amount, false, is_critical) # false = not magical, is_critical = crit pierce
character_stats.modify_health(-actual_damage)
current_health = character_stats.hp
if character_stats.hp <= 0:
character_stats.no_health.emit()
var effective_def = character_stats.defense * (0.2 if is_critical else 1.0)
print(name, " took ", actual_damage, " damage (", amount, " base - ", effective_def, " DEF = ", actual_damage, ")! Health: ", current_health, "/", character_stats.maxhp)
else:
# Fallback for legacy (shouldn't happen if _initialize_character_stats is called)
current_health -= amount
actual_damage = amount
print(name, " took ", amount, " damage! Health: ", current_health, " (critical: ", is_critical, ")")
# Calculate knockback direction (away from attacker)
var knockback_direction = (global_position - from_position).normalized()
@@ -287,10 +346,9 @@ func take_damage(amount: float, from_position: Vector2):
# Flash red (even if dying, show the hit)
_flash_damage()
# Show damage number (red, using dmg_numbers.png font) above enemy
# Only show if damage > 0
if amount > 0:
_show_damage_number(amount, from_position)
# Show damage number (red/orange, using dmg_numbers.png font) above enemy
# Always show damage number, even if 0
_show_damage_number(actual_damage, from_position, is_critical)
# Sync damage visual to clients
# Use game_world to route damage visual sync instead of direct RPC to avoid node path issues
@@ -321,16 +379,14 @@ func take_damage(amount: float, from_position: Vector2):
call_deferred("_notify_doors_enemy_died")
@rpc("any_peer", "reliable")
func rpc_take_damage(amount: float, from_position: Vector2):
func rpc_take_damage(amount: float, from_position: Vector2, is_critical: bool = false):
# RPC version - only process on server/authority
if is_multiplayer_authority():
take_damage(amount, from_position)
take_damage(amount, from_position, is_critical)
func _show_damage_number(amount: float, from_position: Vector2):
# Show damage number (red, using dmg_numbers.png font) above enemy
# Only show if damage > 0
if amount <= 0:
return
func _show_damage_number(amount: float, from_position: Vector2, is_critical: bool = false, is_miss: bool = false, is_dodged: bool = false):
# Show damage number (red/orange for crits, cyan for dodge, gray for miss, using dmg_numbers.png font) above enemy
# Show even if amount is 0 for MISS/DODGED
var damage_number_scene = preload("res://scenes/damage_number.tscn")
if not damage_number_scene:
@@ -340,9 +396,16 @@ func _show_damage_number(amount: float, from_position: Vector2):
if not damage_label:
return
# Set damage text and red color
damage_label.label = str(int(amount))
damage_label.color = Color.RED
# Set text and color based on type
if is_dodged:
damage_label.label = "DODGED"
damage_label.color = Color.CYAN
elif is_miss:
damage_label.label = "MISS"
damage_label.color = Color.GRAY
else:
damage_label.label = str(int(amount))
damage_label.color = Color.ORANGE if is_critical else Color.RED
# Calculate direction from attacker (slight upward variation)
var direction_from_attacker = (global_position - from_position).normalized()
@@ -418,11 +481,16 @@ func _die():
is_dead = true
print(name, " died!")
# Credit kill to the player who dealt the fatal damage
# Credit kill and grant EXP to the player who dealt the fatal damage
if killer_player and is_instance_valid(killer_player) and killer_player.character_stats:
killer_player.character_stats.kills += 1
print(name, " kill credited to ", killer_player.name, " (total kills: ", killer_player.character_stats.kills, ")")
# Grant EXP to the killer
if exp_reward > 0:
killer_player.character_stats.add_xp(exp_reward)
print(name, " granted ", exp_reward, " EXP to ", killer_player.name)
# Sync kill update to client if this player belongs to a client
# Only sync if we're on the server and the killer is a client's player
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority():
@@ -475,16 +543,21 @@ func _spawn_loot():
var loot_chance = randf()
print(name, " loot chance roll: ", loot_chance, " (need > 0.3)")
if loot_chance > 0.3:
# Random loot type
# Decide what to drop: 30% coin, 30% food, 40% item
var drop_roll = randf()
var loot_type = 0
var drop_item = false
# 50% chance for coin
if randf() < 0.5:
if drop_roll < 0.3:
# 30% chance for coin
loot_type = 0 # COIN
# 50% chance for food item
else:
elif drop_roll < 0.6:
# 30% chance for food item
var food_types = [1, 2, 3] # APPLE, BANANA, CHERRY
loot_type = food_types[randi() % food_types.size()]
else:
# 40% chance for Item instance
drop_item = true
# Generate random velocity values (same on all clients)
var random_angle = randf() * PI * 2
@@ -500,10 +573,20 @@ func _spawn_loot():
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:
if not entities_node:
print(name, " ERROR: entities_node is null! Cannot spawn loot!")
return
if drop_item:
# Spawn Item instance as loot
var item = ItemDatabase.get_random_enemy_drop()
if item:
ItemLootHelper.spawn_item_loot(item, global_position, entities_node, game_world)
print(name, " ✓ dropped item: ", item.item_name, " at ", safe_spawn_pos)
else:
# Spawn regular loot (coin or food)
var loot = loot_scene.instantiate()
entities_node.add_child(loot)
loot.global_position = safe_spawn_pos
loot.loot_type = loot_type
@@ -519,8 +602,7 @@ func _spawn_loot():
# 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
# loot_id_counter is declared as a variable in game_world.gd, so it always exists
var loot_id = game_world.loot_id_counter
game_world.loot_id_counter += 1
# Store loot ID on server loot instance
@@ -530,8 +612,6 @@ func _spawn_loot():
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")

View File

@@ -15,16 +15,34 @@ var fly_height: float = 8.0 # Z position when flying
func _ready():
super._ready()
max_health = 30.0
max_health = 18.0 # Reduced from 30.0 for better balance
current_health = max_health
move_speed = 40.0 # Reasonable speed for bats
damage = 5.0
exp_reward = 6.0 # Bats give low-moderate EXP
state_timer = idle_duration
# CRITICAL: Ensure collision mask is set correctly (walls are on layer 7 = bit 6 = 64)
collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
# Override to set weak stats for bats
func _initialize_character_stats():
super._initialize_character_stats()
# Bats are weak enemies - very low END for low DEF
character_stats.baseStats.end = 5 # END=5 gives DEF=1.0 (5 * 0.2)
character_stats.baseStats.str = 5 # Low STR = low damage
character_stats.baseStats.dex = 10 # Higher DEX (bats are agile)
character_stats.baseStats.int = 5
character_stats.baseStats.wis = 5
character_stats.baseStats.lck = 5
# Re-initialize hp and mp with new stats
character_stats.hp = character_stats.maxhp
character_stats.mp = character_stats.maxmp
# Sync max_health from character_stats
max_health = character_stats.maxhp
current_health = max_health
func _physics_process(delta):
# Always update animation (even when dead, and on clients)
_update_animation(delta)

View File

@@ -740,40 +740,47 @@ func _setup_stats():
# Set stats based on type
match humanoid_type:
HumanoidType.CYCLOPS:
max_health = 100.0
max_health = 55.0 # Reduced from 100.0 for better balance
move_speed = 40.0
damage = 15.0
dex = 8 # Slow, strong
exp_reward = 25.0 # Strong enemies give more EXP
HumanoidType.DEMON:
max_health = 80.0
max_health = 45.0 # Reduced from 80.0 for better balance
move_speed = 45.0
damage = 12.0
dex = 12 # Medium speed
exp_reward = 20.0
HumanoidType.HUMANOID:
max_health = 60.0
max_health = 35.0 # Reduced from 60.0 for better balance
move_speed = 50.0
damage = 10.0
dex = 15 # Fast, agile
exp_reward = 15.0
HumanoidType.NIGHTELF:
max_health = 70.0
max_health = 40.0 # Reduced from 70.0 for better balance
move_speed = 55.0
damage = 11.0
dex = 18 # Very fast
exp_reward = 18.0
HumanoidType.GOBLIN:
max_health = 40.0
max_health = 25.0 # Reduced from 40.0 for better balance
move_speed = 60.0
damage = 8.0
dex = 20 # Very fast, weak
exp_reward = 10.0 # Weak enemies give less EXP
HumanoidType.ORC:
max_health = 90.0
max_health = 50.0 # Reduced from 90.0 for better balance
move_speed = 42.0
damage = 14.0
dex = 7 # Slow, very strong
exp_reward = 22.0
HumanoidType.SKELETON:
max_health = 50.0
max_health = 30.0 # Reduced from 50.0 for better balance
move_speed = 48.0
damage = 9.0
dex = 14 # Medium-fast
exp_reward = 12.0
current_health = max_health

View File

@@ -13,16 +13,34 @@ var detection_range: float = 150.0
func _ready():
super._ready()
max_health = 25.0
max_health = 15.0 # Reduced from 25.0 for better balance
current_health = max_health
move_speed = 40.0 # Much slower
damage = 8.0
exp_reward = 5.0 # Rats give low EXP
state_timer = idle_duration
# CRITICAL: Ensure collision mask is set correctly (walls are on layer 7 = bit 6 = 64)
collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
# Override to set weak stats for rats
func _initialize_character_stats():
super._initialize_character_stats()
# Rats are weak enemies - very low END for low DEF
character_stats.baseStats.end = 5 # END=5 gives DEF=1.0 (5 * 0.2)
character_stats.baseStats.str = 7 # Low STR = low damage
character_stats.baseStats.dex = 8 # Slightly higher DEX (rats are fast)
character_stats.baseStats.int = 5
character_stats.baseStats.wis = 5
character_stats.baseStats.lck = 5
# Re-initialize hp and mp with new stats
character_stats.hp = character_stats.maxhp
character_stats.mp = character_stats.maxmp
# Sync max_health from character_stats
max_health = character_stats.maxhp
current_health = max_health
func _ai_behavior(delta):
# Update state timer
state_timer -= delta

View File

@@ -55,10 +55,11 @@ var time_since_last_frame = 0.0
func _ready():
super._ready()
max_health = 20.0
max_health = 12.0 # Reduced from 20.0 for better balance
current_health = max_health
move_speed = 20.0 # Very slow (reduced from 35)
damage = 6.0
exp_reward = 8.0 # Slimes give moderate EXP
state_timer = idle_duration
@@ -70,6 +71,23 @@ func _ready():
if collision_shape and collision_shape.shape:
collision_shape.shape.radius = 6.0 # 12x12 effective size
# Override to set weak stats for slimes
func _initialize_character_stats():
super._initialize_character_stats()
# Slimes are weak enemies - very low END for low DEF
character_stats.baseStats.end = 5 # END=5 gives DEF=1.0 (5 * 0.2)
character_stats.baseStats.str = 6 # Low STR = low damage
character_stats.baseStats.dex = 5 # Low DEX
character_stats.baseStats.int = 5
character_stats.baseStats.wis = 5
character_stats.baseStats.lck = 5
# Re-initialize hp and mp with new stats
character_stats.hp = character_stats.maxhp
character_stats.mp = character_stats.maxmp
# Sync max_health from character_stats
max_health = character_stats.maxhp
current_health = max_health
func _physics_process(delta):
# Always update animation (even when dead, and on clients)
_update_animation(delta)

View File

@@ -39,6 +39,9 @@ func _ready():
# Create chat UI
_create_chat_ui()
# Create inventory UI
_create_inventory_ui()
# Generate dungeon on host
if multiplayer.is_server() or not multiplayer.has_multiplayer_peer():
print("GameWorld: _ready() - Will generate dungeon (is_server: ", multiplayer.is_server(), ", has_peer: ", multiplayer.has_multiplayer_peer(), ")")
@@ -275,6 +278,37 @@ func _sync_loot_spawn(spawn_position: Vector2, loot_type: int, initial_velocity:
loot.is_airborne = true
print("Client spawned loot: ", loot_type, " at ", spawn_position, " authority: ", loot.get_multiplayer_authority())
@rpc("authority", "reliable")
func _sync_item_loot_spawn(spawn_position: Vector2, item_data: Dictionary, initial_velocity: Vector2, initial_velocity_z: float, loot_id: int = -1):
# Clients spawn item loot when server tells them to
if not multiplayer.is_server():
var loot_scene = preload("res://scenes/loot.tscn")
if not loot_scene:
return
var loot = loot_scene.instantiate()
var entities_node = get_node_or_null("Entities")
if entities_node:
# Create Item instance from data
var item = Item.new(item_data)
# Set multiplayer authority to server (peer 1) so RPCs work
if multiplayer.has_multiplayer_peer():
loot.set_multiplayer_authority(1)
# Store unique loot ID for identification
if loot_id >= 0:
loot.set_meta("loot_id", loot_id)
entities_node.add_child(loot)
loot.global_position = spawn_position
loot.loot_type = loot.LootType.ITEM
loot.item = item # Set the item instance
# Set initial velocity before _ready() processes
loot.velocity = initial_velocity
loot.velocity_z = initial_velocity_z
loot.velocity_set_by_spawner = true
loot.is_airborne = true
print("Client spawned item loot: ", item.item_name, " at ", spawn_position, " authority: ", loot.get_multiplayer_authority())
@rpc("authority", "unreliable")
func _sync_enemy_position(enemy_name: String, enemy_index: int, pos: Vector2, vel: Vector2, z_pos: float, dir: int, frame: int, anim: String, frame_num: int, state_value: int):
# Clients receive enemy position updates from server
@@ -2056,6 +2090,19 @@ func _create_chat_ui():
else:
push_error("GameWorld: Failed to instantiate chat_ui.tscn!")
func _create_inventory_ui():
# Create inventory UI programmatically (using CanvasLayer)
var inventory_ui_script = load("res://scripts/inventory_ui.gd")
if not inventory_ui_script:
push_error("GameWorld: Could not load inventory_ui.gd script!")
return
var inventory_ui = CanvasLayer.new()
inventory_ui.set_script(inventory_ui_script)
inventory_ui.name = "InventoryUI"
add_child(inventory_ui)
print("GameWorld: Inventory UI created and added to scene tree")
func _send_player_join_message(peer_id: int, player_info: Dictionary):
# Send a chat message when a player joins
# Only send from server to avoid duplicate messages

View File

@@ -4,6 +4,7 @@ extends Resource
signal health_changed(new_health: float, max_health: float)
signal mana_changed(new_mana: float, max_mana: float)
signal level_changed(new_level: int)
signal level_up_stats(stats_increased: Array) # Emitted when leveling up, contains array of stat names that were increased
signal xp_changed(new_xp: float, xp_to_next: float)
signal no_health
@@ -152,11 +153,13 @@ var maxmp: float:
var damage: float:
get:
return (baseStats.str + get_pass("str")) * 0.2 + get_pass("dmg")
# Increased damage scaling: 0.25 per STR point (was 0.2) - makes players stronger
return (baseStats.str + get_pass("str")) * 0.25 + get_pass("dmg")
var defense: float:
get:
return ((baseStats.end + get_pass("end")) * 0.3) + get_pass("def")
# Reduced DEF scaling: 0.2 per END point (was 0.3) to make it less overpowered for low-level enemies
return ((baseStats.end + get_pass("end")) * 0.2) + get_pass("def")
var spell_amp: float:
get:
@@ -178,9 +181,23 @@ var crit_chance: float:
get:
return (baseStats.lck + get_pass("lck")) * 1.2
var dodge_chance: float:
get:
# Dodge chance based on DEX (very low % per point, as per user request)
# Each point of DEX gives 0.5% dodge chance (so 20 DEX = 10% dodge)
return (baseStats.dex + get_pass("dex")) * 0.005
var hit_chance: float:
get:
# Hit chance based on DEX (higher DEX = better accuracy)
# Base hit chance is 95%, each DEX point adds 0.3% (so ~17 DEX = 100% base hit chance)
# Formula: 95% base + (DEX * 0.3%)
return 0.95 + ((baseStats.dex + get_pass("dex")) * 0.003)
var xp_to_next_level: float:
get:
return pow(level * 10, 1.5)
# Scale EXP requirements more aggressively - gets harder to level as you go
return pow(level * 15, 1.6)
func _init() -> void:
hp = maxhp
@@ -193,25 +210,41 @@ func add_xp(amount: float) -> void:
while xp >= xp_to_next_level:
level_up()
# instead of automatically update all stats, maybe let the player choose which stats to level up.
# Level up - randomly increases 2-3 stats instead of all
func level_up() -> void:
level += 1
xp -= xp_to_next_level
# Increase stats
baseStats.str += 1
baseStats.dex += 1
baseStats.int += 1
baseStats.end += 1
baseStats.wis += 1
baseStats.cha += 1
baseStats.lck += 1
# Randomly select 2-3 stats to increase (random 2 or 3)
var num_stats_to_increase = randi_range(2, 3)
# All available stats (excluding cha for now as per user request)
var available_stats = ["str", "dex", "int", "end", "wis", "lck"]
# Shuffle and pick random stats
var stats_to_increase = []
var shuffled_stats = available_stats.duplicate()
shuffled_stats.shuffle()
for i in range(min(num_stats_to_increase, shuffled_stats.size())):
stats_to_increase.append(shuffled_stats[i])
# Increase selected stats
for stat_name in stats_to_increase:
baseStats[stat_name] += 1
# Store which stats were increased (for display)
var stats_increased = stats_to_increase
# Restore health and mana on level up
hp = maxhp
mp = maxmp
# Emit level_changed signal
level_changed.emit(level)
# Emit level_up_stats signal with which stats were increased
level_up_stats.emit(stats_increased)
health_changed.emit(hp, maxhp)
mana_changed.emit(mp, maxmp)
@@ -225,12 +258,25 @@ func modify_mana(amount: float) -> void:
mana_changed.emit(mp, maxmp)
character_changed.emit(self)
func calculate_damage(base_damage: float, is_magical: bool = false) -> float:
if is_magical:
return base_damage * (1 - (resistances.magic / 100.0))
return base_damage * (1 - (defense / 100.0))
func calculate_damage(base_damage: float, is_magical: bool = false, is_critical: bool = false) -> float:
# Apply defense reduction (DEF reduces damage by a flat amount, not percentage)
# Defense formula: flat reduction based on END and equipment DEF
# Critical hits pierce 80% of DEF (only 20% of DEF applies to crits)
var final_damage = base_damage
if not is_magical:
# Physical damage: reduce by defense value (flat reduction)
var effective_defense = defense
if is_critical:
# Critical hits pierce 80% of DEF (only 20% applies)
effective_defense = defense * 0.2
final_damage = max(0.0, base_damage - effective_defense)
else:
# Magical damage: reduce by magic resistance percentage
final_damage = base_damage * (1 - (resistances.magic / 100.0))
return final_damage
func take_damage(amount: float, is_magical: bool = false) -> float:
# Calculate damage after DEF reduction
var actual_damage = calculate_damage(amount, is_magical)
modify_health(-actual_damage)
if hp <= 0:
@@ -419,6 +465,28 @@ func drop_equipment(iItem:Item):
func add_item(iItem:Item):
self.inventory.push_back(iItem)
# Auto-equip if slot is empty (only for equippable items)
if iItem.item_type == Item.ItemType.Equippable and iItem.equipment_type != Item.EquipmentType.NONE:
var slot_key = ""
match iItem.equipment_type:
Item.EquipmentType.MAINHAND:
slot_key = "mainhand"
Item.EquipmentType.OFFHAND:
slot_key = "offhand"
Item.EquipmentType.HEADGEAR:
slot_key = "headgear"
Item.EquipmentType.ARMOUR:
slot_key = "armour"
Item.EquipmentType.BOOTS:
slot_key = "boots"
Item.EquipmentType.ACCESSORY:
slot_key = "accessory"
# Auto-equip if slot is empty
if slot_key != "" and equipment[slot_key] == null:
equip_item(iItem)
emit_signal("character_changed", self)
pass

View File

@@ -0,0 +1,491 @@
extends CanvasLayer
var is_showing_inventory = true
var finished_tween = true
var selectedItem:Item = null
var selectedEquipment:Item = null
var lastPressedItem:Item = null
var lastPressedType = "Item"
var timerSelect:float = 0.0
func bindInventory():
if GameManager.character_data.is_connected("character_changed", _charChanged):
GameManager.character_data.disconnect("character_changed", _charChanged)
GameManager.character_data.connect("character_changed", _charChanged)
pass
func _ready() -> void:
is_showing_inventory = false
var move_range = -119
$ControlContainer/ControlStats.position.x += move_range
move_range = 80
$ControlContainer/ControlInventory.position.x += move_range
move_range = 33
$ControlContainer/ControlInfo.position.y += move_range
$ControlContainer.mouse_filter = Control.MOUSE_FILTER_IGNORE
# Set up multiplayer peer
'
GameManager.host(21212, false)
GameManager.character_data.connect("character_changed", _charChanged)
#$Player.position = Vector2(30, 30)
var item = Item.new()
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"
GameManager.character_data.add_item(item)
var item2 = Item.new()
item2.item_type = Item.ItemType.Equippable
item2.equipment_type = Item.EquipmentType.HEADGEAR
item2.item_name = "Leather helm"
item2.description = "A nice leather helm"
item2.spriteFrame = 31
item2.modifiers["def"] = 1
item2.equipmentPath = "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Mage/MageHatRed.png"
item2.colorReplacements = [
{ "original": Color(255/255.0, 39/255.0, 44/255.0), "replace": Color(255/255.0,106/255.0,39/255.0)},
{ "original": Color(182/255.0, 0, 0), "replace": Color(182/255.0,106/255.0,0)},
{ "original": Color(118/255.0, 1/255.0, 0), "replace": Color(118/255.0,66/255.0,0)},
{ "original": Color(72/255.0, 0, 12/255.0), "replace": Color(72/255.0,34/255.0,0)}
]
GameManager.character_data.add_item(item2)
var item3 = Item.new()
item3.item_type = Item.ItemType.Equippable
item3.equipment_type = Item.EquipmentType.MAINHAND
item3.weapon_type = Item.WeaponType.SWORD
item3.item_name = "Dagger"
item3.description = "A sharp dagger"
item3.spriteFrame = 5*20 + 10
item3.modifiers["dmg"] = 2
GameManager.character_data.add_item(item3)
var item4 = Item.new()
item4.item_type = Item.ItemType.Equippable
item4.equipment_type = Item.EquipmentType.MAINHAND
item4.weapon_type = Item.WeaponType.AXE
item4.item_name = "Hand Axe"
item4.description = "A sharp hand axe"
item4.spriteFrame = 5*20 + 11
item4.modifiers["dmg"] = 4
GameManager.character_data.add_item(item4)
var item5 = Item.new()
item5.item_type = Item.ItemType.Equippable
item5.equipment_type = Item.EquipmentType.OFFHAND
item5.weapon_type = Item.WeaponType.AMMUNITION
item5.quantity = 13
item5.can_have_multiple_of = true
item5.item_name = "Iron Arrow"
item5.description = "A sharp arrow made of iron and feathers from pelican birds"
item5.spriteFrame = 7*20 + 11
item5.modifiers["dmg"] = 3
GameManager.character_data.add_item(item5)
var item6 = Item.new()
item6.item_type = Item.ItemType.Equippable
item6.equipment_type = Item.EquipmentType.MAINHAND
item6.weapon_type = Item.WeaponType.BOW
item6.item_name = "Wooden Bow"
item6.description = "A wooden bow made of elfish lembas trees"
item6.spriteFrame = 6*20 + 16
item6.modifiers["dmg"] = 3
GameManager.character_data.add_item(item6)
var item7 = Item.new()
item7.item_type = Item.ItemType.Equippable
item7.equipment_type = Item.EquipmentType.BOOTS
item7.weapon_type = Item.WeaponType.NONE
item7.item_name = "Sandals"
item7.description = "A pair of shitty sandals"
item7.equipmentPath = "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/ShoesBrown.png"
item7.spriteFrame = 2*20 + 10
item7.modifiers["def"] = 1
GameManager.character_data.add_item(item7)
'
# clear default stuff
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = null
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = ""
pass
func _charChanged(iChar:CharacterStats):
# update all stats:
$ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer/LabelBaseStatsValue.text = str(iChar.level) + "\r\n" + \
"\r\n" + \
str(floori(iChar.hp)) + "/" + str(floori(iChar.maxhp)) + "\r\n" + \
str(floori(iChar.mp)) + "/" + str(floori(iChar.maxmp)) + "\r\n" + \
"\r\n" + \
str(iChar.baseStats.str) + "\r\n" + \
str(iChar.baseStats.dex) + "\r\n" + \
str(iChar.baseStats.end) + "\r\n" + \
str(iChar.baseStats.int) + "\r\n" + \
str(iChar.baseStats.wis) + "\r\n" + \
str(iChar.baseStats.lck)
$ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer/LabelDerivedStatsValue.text = str(floori(iChar.xp)) + "/" + str(floori(iChar.xp_to_next_level)) + "\r\n" + \
str(iChar.coin) + "\r\n" + \
"\r\n" + \
"\r\n" + \
"\r\n" + \
str(iChar.damage) + "\r\n" + \
str(iChar.defense) + "\r\n" + \
str(iChar.move_speed) + "\r\n" + \
str(iChar.attack_speed) + "\r\n" + \
str(iChar.sight) + "\r\n" + \
str(iChar.spell_amp) + "\r\n" + \
str(iChar.crit_chance) + "%"
# read inventory and populate inventory
var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory
for child in vboxInvent.find_child("HBoxControl1").get_children():
child.get_parent().remove_child(child)
child.propagate_call("queue_free", [])
for child in vboxInvent.find_child("HBoxControl2").get_children():
child.get_parent().remove_child(child)
child.propagate_call("queue_free", [])
for child in vboxInvent.find_child("HBoxControl3").get_children():
child.get_parent().remove_child(child)
child.propagate_call("queue_free", [])
var selected_tex = preload("res://assets/gfx/ui/inventory_slot_kenny_white.png")
var styleBoxHover:StyleBox = StyleBoxTexture.new()
var styleBoxFocused:StyleBox = StyleBoxTexture.new()
var styleBoxPressed:StyleBox = StyleBoxTexture.new()
var styleBoxEmpty:StyleBox = StyleBoxEmpty.new()
styleBoxHover.texture = selected_tex
styleBoxFocused.texture = selected_tex
styleBoxPressed.texture = selected_tex
var quantityFont:Font = load("res://assets/fonts/dmg_numbers.png")
for item:Item in iChar.inventory:
var btn:Button = Button.new()
btn.add_theme_stylebox_override("normal", styleBoxEmpty)
btn.add_theme_stylebox_override("hover", styleBoxHover)
btn.add_theme_stylebox_override("focus", styleBoxFocused)
btn.add_theme_stylebox_override("pressed", styleBoxPressed)
btn.custom_minimum_size = Vector2(24, 24)
btn.size = Vector2(24, 24)
vboxInvent.find_child("HBoxControl1").add_child(btn)
var spr:Sprite2D = Sprite2D.new()
spr.texture = load(item.spritePath)
spr.hframes = item.spriteFrames.x
spr.vframes = item.spriteFrames.y
spr.frame = item.spriteFrame
spr.centered = false
spr.position = Vector2(4,4)
btn.add_child(spr)
if item.can_have_multiple_of:
var lblQuantity:Label = Label.new()
lblQuantity.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
lblQuantity.size = Vector2(24, 24)
lblQuantity.custom_minimum_size = Vector2(0,0)
lblQuantity.position = Vector2(10, 2)
lblQuantity.text = str(item.quantity)
lblQuantity.add_theme_font_override("font", quantityFont)
lblQuantity.add_theme_font_size_override("font", 8)
lblQuantity.scale = Vector2(0.5, 0.5)
btn.add_child(lblQuantity)
pass
btn.connect("pressed", _selectItem.bind(item, true))
btn.connect("focus_entered", _selectItem.bind(item, false))
pass
# clear eq container...
var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer
for child in eqContainer.get_children():
child.get_parent().remove_child(child)
child.propagate_call("queue_free", [])
'"mainhand": null,
"offhand": null,
"headgear": null,
"armour": null,
"boots": null,
"accessory": null'
addEq(iChar, "mainhand", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont)
addEq(iChar, "offhand", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont)
addEq(iChar, "headgear", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont)
addEq(iChar, "armour", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont)
addEq(iChar, "boots", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont)
addEq(iChar, "accessory", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont)
pass
func addEq(iChar:CharacterStats, iPiece:String, eqContainer:Control, styleBoxEmpty: StyleBox, styleBoxHover: StyleBox, styleBoxFocused: StyleBox, styleBoxPressed: StyleBox, quantityFont: Font):
if iChar.equipment[iPiece] != null:
var btn:Button = Button.new()
btn.add_theme_stylebox_override("normal", styleBoxEmpty)
btn.add_theme_stylebox_override("hover", styleBoxHover)
btn.add_theme_stylebox_override("focus", styleBoxFocused)
btn.add_theme_stylebox_override("pressed", styleBoxPressed)
btn.custom_minimum_size = Vector2(24, 24)
btn.position = Vector2(1,9)
match iPiece:
"armour":
btn.position = Vector2(28, 9)
pass
"offhand":
btn.position = Vector2(55, 9)
pass
"headgear":
btn.position = Vector2(1, 36)
pass
"boots":
btn.position = Vector2(28, 36)
pass
"accessory":
btn.position = Vector2(55, 36)
pass
pass
eqContainer.add_child(btn)
var spr:Sprite2D = Sprite2D.new()
spr.texture = load(iChar.equipment[iPiece].spritePath)
spr.hframes = iChar.equipment[iPiece].spriteFrames.x
spr.vframes = iChar.equipment[iPiece].spriteFrames.y
spr.frame = iChar.equipment[iPiece].spriteFrame
spr.centered = false
spr.position = Vector2(4,4)
btn.add_child(spr)
btn.connect("pressed", _selectEquipment.bind(iChar.equipment[iPiece], true))
btn.connect("focus_entered", _selectEquipment.bind(iChar.equipment[iPiece], false))
if iChar.equipment[iPiece].can_have_multiple_of:
var lblQuantity:Label = Label.new()
lblQuantity.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
lblQuantity.size = Vector2(24, 24)
lblQuantity.custom_minimum_size = Vector2(0,0)
lblQuantity.position = Vector2(10, 2)
lblQuantity.text = str(iChar.equipment[iPiece].quantity)
lblQuantity.add_theme_font_override("font", quantityFont)
lblQuantity.add_theme_font_size_override("font", 8)
lblQuantity.scale = Vector2(0.5, 0.5)
btn.add_child(lblQuantity)
pass
pass
pass
# draw equipment buttons (for unequipping)
func _selectEquipment(item:Item, isPress: bool):
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.visible = true
if !is_showing_inventory:
return
if lastPressedItem == item and lastPressedType == "Equipment" and timerSelect > 0:
$SfxUnequip.play()
GameManager.character_data.unequip_item(selectedEquipment)
selectedItem = selectedEquipment
lastPressedItem = null
selectedEquipment = null
lastPressedType = "Item"
var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory
for child in vboxInvent.find_child("HBoxControl1").get_children():
child.grab_focus()
break
return
lastPressedType = "Equipment"
selectedEquipment = item
# update description in bottom
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = load(item.spritePath)
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.hframes = item.spriteFrames.x
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.vframes = item.spriteFrames.y
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.frame = item.spriteFrame
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = item.description
if isPress:
lastPressedItem = item
timerSelect = 0.4
# unequip item
pass
func _selectItem(item:Item, isPress: bool):
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.visible = true
if !is_showing_inventory:
return
if lastPressedItem == item and lastPressedType == "Item" and timerSelect > 0:
timerSelect = 0
$SfxEquip.play()
GameManager.character_data.equip_item(selectedItem)
selectedEquipment = selectedItem
selectedItem = null
lastPressedItem = null
lastPressedType = "Equipment"
var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer
for child in eqContainer.get_children():
child.grab_focus()
break
return
lastPressedType = "Item"
selectedItem = item
# update description in bottom
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = load(item.spritePath)
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.hframes = item.spriteFrames.x
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.vframes = item.spriteFrames.y
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.frame = item.spriteFrame
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = item.description
if isPress:
lastPressedItem = item
timerSelect = 0.4
pass
func _process(delta: float) -> void:
if timerSelect > 0:
timerSelect -= delta
if timerSelect <= 0:
timerSelect = 0
pass
func _input(_event: InputEvent):
#print("connected to newtwork:", GameManager.is_network_connected)
#print("finished tween:", finished_tween)
if !GameManager.is_network_connected or finished_tween == false:
return
if is_showing_inventory:
if !$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.has_focus():
if lastPressedType == "Equipment" and selectedEquipment != null and Input.is_action_just_pressed("ui_accept"):
$SfxUnequip.play()
GameManager.character_data.unequip_item(selectedEquipment)
selectedItem = selectedEquipment
selectedEquipment = null
lastPressedItem = null
lastPressedType = "Item"
var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory
for child in vboxInvent.find_child("HBoxControl1").get_children():
child.grab_focus()
break
return
if lastPressedType == "Item" and selectedItem != null and Input.is_action_just_pressed("ui_accept"):
$SfxEquip.play()
GameManager.character_data.equip_item(selectedItem)
selectedEquipment = selectedItem
selectedItem = null
lastPressedItem = null
lastPressedType = "Equipment"
var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer
for child in eqContainer.get_children():
child.grab_focus()
break
return
if Input.is_action_just_pressed("inventory") and finished_tween == true:
finished_tween = false
var move_range = 119
var move_duration = 0.3
if is_showing_inventory:
move_range = -move_range
#stats movement
var move_tween = get_tree().create_tween()
move_tween.tween_property($ControlContainer/ControlStats, "position:x",$ControlContainer/ControlStats.position.x + move_range,move_duration).from($ControlContainer/ControlStats.position.x).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_IN_OUT)
move_range = -80
if is_showing_inventory:
move_range = -move_range
#inventory movement
var move_tween2 = get_tree().create_tween()
move_tween2.tween_property($ControlContainer/ControlInventory, "position:x",$ControlContainer/ControlInventory.position.x + move_range,move_duration).from($ControlContainer/ControlInventory.position.x).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_IN_OUT)
move_range = -33
if is_showing_inventory:
$SfxInventoryClose.play()
move_range = -move_range
else:
$SfxInventoryOpen.play()
#info movement
var move_tween3 = get_tree().create_tween()
move_tween3.tween_property($ControlContainer/ControlInfo, "position:y",$ControlContainer/ControlInfo.position.y + move_range,move_duration).from($ControlContainer/ControlInfo.position.y).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_IN_OUT)
is_showing_inventory = !is_showing_inventory
await move_tween3.finished
finished_tween = true
if is_showing_inventory:
# preselect first item
var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory
var hadInventoryItem = false
for child in vboxInvent.find_child("HBoxControl1").get_children():
child.grab_focus()
hadInventoryItem = true
break
lastPressedType = "Item"
if hadInventoryItem == false:
# preselect something in equipment instead
selectedItem = null
lastPressedItem = null
lastPressedType = "Equipment"
var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer
for child in eqContainer.get_children():
child.grab_focus()
break
pass
pass
'
if Input.is_action_just_pressed("ui_right"):
$ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer.
pass
if Input.is_action_just_pressed("ui_left"):
pass'
'
if Input.is_action_just_pressed("ui_up"):
$ControlContainer/ControlInventory/Sprite2DSelector.position.y -= 20
pass
if Input.is_action_just_pressed("ui_down"):
$ControlContainer/ControlInventory/Sprite2DSelector.position.y += 20
pass'
func _on_button_drop_pressed() -> void:
if !is_showing_inventory:
return
if lastPressedType == "Item":
if selectedItem != null:
GameManager.character_data.drop_item(selectedItem)
selectedItem = null
# clear default stuff
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = null
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = ""
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.visible = false
else:
if selectedEquipment != null:
GameManager.character_data.drop_equipment(selectedEquipment)
selectedEquipment = null
# clear default stuff
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = null
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = ""
$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.visible = false
var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory
var hadInventoryItem = false
for child in vboxInvent.find_child("HBoxControl1").get_children():
child.grab_focus()
hadInventoryItem = true
break
lastPressedType = "Item"
if hadInventoryItem == false:
selectedItem = null
lastPressedItem = null
lastPressedType = "Equipment"
var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer
for child in eqContainer.get_children():
child.grab_focus()
break
pass # Replace with function body.

View File

@@ -0,0 +1 @@
uid://20kfmxrtt20e

View File

@@ -0,0 +1,693 @@
[gd_scene format=3 uid="uid://du1cpug8yag6w"]
[ext_resource type="Texture2D" uid="uid://b7u7dbiaub8lp" path="res://assets/gfx/ruinborn_mImwZSNWBM.png" id="1_0amil"]
[ext_resource type="Script" uid="uid://20kfmxrtt20e" path="res://assets/scripts/ui/inventory.gd" id="1_k81k7"]
[ext_resource type="Texture2D" uid="uid://hib38y541eog" path="res://assets/gfx/items_n_shit.png" id="2_7vwhs"]
[ext_resource type="Texture2D" uid="uid://ct0rllwve2s1y" path="res://assets/gfx/ui/inventory_panel_small.png" id="2_voqm7"]
[ext_resource type="Texture2D" uid="uid://who0clhmi5cl" path="res://assets/gfx/ui/inventory_panel.png" id="4_nlhqn"]
[ext_resource type="Texture2D" uid="uid://cxend0ndnfn32" path="res://assets/gfx/ui/inventory_slot_kenny_white.png" id="4_nxmsh"]
[ext_resource type="Texture2D" uid="uid://bsnfadlf1dgnw" path="res://assets/gfx/ui/inventory_slot_kenny_black_sword.png" id="6_k81k7"]
[ext_resource type="FontFile" uid="uid://cbmcfue0ek0tk" path="res://assets/fonts/dmg_numbers.png" id="7_kiwfx"]
[ext_resource type="Texture2D" uid="uid://tdivehfcj0el" path="res://assets/gfx/ui/inventory_slot_kenny_black_shield.png" id="7_vardb"]
[ext_resource type="Texture2D" uid="uid://b1l30o2ljhl2t" path="res://assets/gfx/ui/inventory_slot_kenny_black_armour.png" id="8_mnwqb"]
[ext_resource type="Texture2D" uid="uid://jgbrhnsaxvg" path="res://assets/gfx/ui/inventory_slot_kenny_black_helm.png" id="9_nbh80"]
[ext_resource type="Texture2D" uid="uid://b71gs7h2v0rdi" path="res://assets/gfx/ui/inventory_slot_kenny_black_ring.png" id="10_kiwfx"]
[ext_resource type="Texture2D" uid="uid://ckctmypotajtf" path="res://assets/gfx/ui/inventory_slot_kenny_black_shoes.png" id="11_ylqbh"]
[ext_resource type="Texture2D" uid="uid://c21a60s4funrr" path="res://assets/gfx/ui/inventory_info_panel.png" id="13_vardb"]
[ext_resource type="AudioStream" uid="uid://x6lxrywls7e2" path="res://assets/audio/sfx/inventory/inventory_open.mp3" id="14_mnwqb"]
[ext_resource type="AudioStream" uid="uid://cfsubtwvpi7yn" path="res://assets/audio/sfx/inventory/inventory_open_inverted.mp3" id="14_nbh80"]
[ext_resource type="AudioStream" uid="uid://djw6c5rb4mm60" path="res://assets/audio/sfx/cloth/leather_cloth_02.wav.mp3" id="17_51fgf"]
[ext_resource type="AudioStream" uid="uid://umoxmryvbm01" path="res://assets/audio/sfx/cloth/leather_cloth_01.wav.mp3" id="18_qk47y"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_nbh80"]
texture = ExtResource("4_nxmsh")
modulate_color = Color(0.511719, 0.511719, 0.511719, 1)
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_kiwfx"]
texture = ExtResource("4_nxmsh")
modulate_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_ylqbh"]
texture = ExtResource("4_nxmsh")
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_nbh80"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_51fgf"]
texture = ExtResource("4_nxmsh")
[node name="Inventory" type="CanvasLayer"]
layer = 21
script = ExtResource("1_k81k7")
[node name="Sprite2D" type="Sprite2D" parent="."]
visible = false
modulate = Color(0.980057, 0.975295, 1, 1)
z_index = -2
z_as_relative = false
position = Vector2(164.625, 92.75)
scale = Vector2(0.258803, 0.263268)
texture = ExtResource("1_0amil")
[node name="ControlContainer" type="Control" parent="."]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ControlStats" type="Control" parent="ControlContainer"]
layout_mode = 1
anchors_preset = 9
anchor_bottom = 1.0
grow_vertical = 2
[node name="Sprite2DPanelz" type="Sprite2D" parent="ControlContainer/ControlStats"]
texture = ExtResource("4_nlhqn")
centered = false
[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlStats"]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
theme_override_constants/margin_left = 3
theme_override_constants/margin_top = 3
theme_override_constants/margin_right = 3
theme_override_constants/margin_bottom = 3
[node name="VBoxContainer" type="VBoxContainer" parent="ControlContainer/ControlStats/MarginContainer"]
layout_mode = 2
[node name="LabelPlayerName" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 6
text = "Fronko"
horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="LabelBaseStats" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/line_spacing = 0
theme_override_font_sizes/font_size = 6
text = "Level
Hp
Mp
Str
Dex
End
Int
Wis
Lck"
[node name="LabelBaseStatsValue" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/line_spacing = 0
theme_override_font_sizes/font_size = 6
text = "1
30 / 30
20 / 20
10
10
10
10
10
10"
horizontal_alignment = 2
[node name="LabelDerivedStats" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/line_spacing = 0
theme_override_font_sizes/font_size = 6
text = "Exp
Coin
Damage
Defense
MovSpd
AtkSpd
Sight
SpellAmp
CritChance"
[node name="LabelDerivedStatsValue" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/line_spacing = 0
theme_override_font_sizes/font_size = 6
text = "400 / 1000
1
2
3
2.1
1.4
7.0
3.0
12.0%"
horizontal_alignment = 2
[node name="ControlInventory" type="Control" parent="ControlContainer"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 1
anchors_preset = 11
anchor_left = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 0
grow_vertical = 2
[node name="Sprite2DPanel" type="Sprite2D" parent="ControlContainer/ControlInventory"]
modulate = Color(0, 0, 0, 0.862745)
texture = ExtResource("2_voqm7")
centered = false
[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInventory"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 2
theme_override_constants/margin_top = 2
theme_override_constants/margin_right = 2
theme_override_constants/margin_bottom = 2
[node name="VBoxContainer" type="VBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="LabelInventory" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 6
text = "Inventory"
horizontal_alignment = 1
[node name="ControlInventory" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 72)
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory"]
layout_mode = 2
offset_left = 2.0
offset_top = 2.0
offset_right = 78.0
offset_bottom = 70.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 2
theme_override_constants/margin_top = 2
theme_override_constants/margin_right = 2
theme_override_constants/margin_bottom = 2
[node name="ScrollContainer" type="ScrollContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer"]
layout_mode = 2
follow_focus = true
[node name="VBoxContainerInventory" type="VBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer"]
layout_mode = 2
theme_override_constants/separation = -4
[node name="HBoxControl1" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 0
[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button3"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="LabelStack" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button3"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -26.0
offset_top = 2.0
offset_right = -2.0
offset_bottom = 26.0
grow_horizontal = 0
theme_override_fonts/font = ExtResource("7_kiwfx")
theme_override_font_sizes/font_size = 8
text = "5"
horizontal_alignment = 2
[node name="Button6" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button6"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button5"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button4"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="HBoxControl2" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 0
[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button3"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button5"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button4"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="HBoxControl3" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 0
[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button3"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button5"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button4"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="ControlEquipment" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(80, 64)
layout_mode = 2
[node name="Label" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -21.5
offset_right = 21.5
offset_bottom = 23.0
grow_horizontal = 2
theme_override_font_sizes/font_size = 6
text = "Equipment"
horizontal_alignment = 1
[node name="Sprite2D" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(13, 21)
texture = ExtResource("6_k81k7")
[node name="Sprite2D2" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(67, 21)
texture = ExtResource("7_vardb")
[node name="Sprite2D3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(40, 21)
texture = ExtResource("8_mnwqb")
[node name="Sprite2D4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(13, 48)
texture = ExtResource("9_nbh80")
[node name="Sprite2D9" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(67, 48)
texture = ExtResource("10_kiwfx")
[node name="Sprite2D5" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(40, 48)
texture = ExtResource("11_ylqbh")
[node name="ControlEquipmentContainer" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Button" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 1.0
offset_top = 9.0
offset_right = 25.0
offset_bottom = 33.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button2" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 28.0
offset_top = 9.0
offset_right = 52.0
offset_bottom = 33.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button2"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 55.0
offset_top = 9.0
offset_right = 79.0
offset_bottom = 33.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button3"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 1.0
offset_top = 36.0
offset_right = 25.0
offset_bottom = 60.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button4"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 28.0
offset_top = 36.0
offset_right = 52.0
offset_bottom = 60.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button5"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button6" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 55.0
offset_top = 36.0
offset_right = 79.0
offset_bottom = 60.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button6"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="ControlInfo" type="Control" parent="ControlContainer"]
custom_minimum_size = Vector2(0, 33)
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 0
[node name="Control" type="Control" parent="ControlContainer/ControlInfo"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Sprite2D" type="Sprite2D" parent="ControlContainer/ControlInfo/Control"]
texture = ExtResource("13_vardb")
centered = false
[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInfo/Control"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 5
[node name="HBoxContainer" type="HBoxContainer" parent="ControlContainer/ControlInfo/Control/MarginContainer"]
layout_mode = 2
[node name="Control" type="Control" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_vertical = 4
[node name="Sprite2DSelector" type="Sprite2D" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control"]
modulate = Color(0.20704, 0.205805, 0.214844, 1)
texture = ExtResource("4_nxmsh")
centered = false
[node name="Sprite2DItem" type="Sprite2D" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="LabelItemDescription" type="Label" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_vertical = 1
theme_override_font_sizes/font_size = 6
text = "A small, but sturdy wooden shield. + 1 DEF"
[node name="ButtonDrop" type="Button" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 8
text = "Drop"
[node name="SfxInventoryClose" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("14_nbh80")
volume_db = -10.0
[node name="SfxInventoryOpen" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("14_mnwqb")
volume_db = -10.0
[node name="SfxUnequip" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("17_51fgf")
[node name="SfxEquip" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("18_qk47y")
[connection signal="pressed" from="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop" to="." method="_on_button_drop_pressed"]

View File

@@ -0,0 +1,692 @@
[gd_scene load_steps=24 format=3 uid="uid://du1cpug8yag6w"]
[ext_resource type="Texture2D" uid="uid://b7u7dbiaub8lp" path="res://assets/gfx/ruinborn_mImwZSNWBM.png" id="1_0amil"]
[ext_resource type="Script" uid="uid://20kfmxrtt20e" path="res://assets/scripts/ui/inventory.gd" id="1_k81k7"]
[ext_resource type="Texture2D" uid="uid://hib38y541eog" path="res://assets/gfx/items_n_shit.png" id="2_7vwhs"]
[ext_resource type="Texture2D" uid="uid://ct0rllwve2s1y" path="res://assets/gfx/ui/inventory_panel_small.png" id="2_voqm7"]
[ext_resource type="Texture2D" uid="uid://who0clhmi5cl" path="res://assets/gfx/ui/inventory_panel.png" id="4_nlhqn"]
[ext_resource type="Texture2D" uid="uid://cxend0ndnfn32" path="res://assets/gfx/ui/inventory_slot_kenny_white.png" id="4_nxmsh"]
[ext_resource type="Texture2D" uid="uid://bsnfadlf1dgnw" path="res://assets/gfx/ui/inventory_slot_kenny_black_sword.png" id="6_k81k7"]
[ext_resource type="FontFile" uid="uid://cbmcfue0ek0tk" path="res://assets/fonts/dmg_numbers.png" id="7_kiwfx"]
[ext_resource type="Texture2D" uid="uid://tdivehfcj0el" path="res://assets/gfx/ui/inventory_slot_kenny_black_shield.png" id="7_vardb"]
[ext_resource type="Texture2D" uid="uid://b1l30o2ljhl2t" path="res://assets/gfx/ui/inventory_slot_kenny_black_armour.png" id="8_mnwqb"]
[ext_resource type="Texture2D" uid="uid://jgbrhnsaxvg" path="res://assets/gfx/ui/inventory_slot_kenny_black_helm.png" id="9_nbh80"]
[ext_resource type="Texture2D" uid="uid://b71gs7h2v0rdi" path="res://assets/gfx/ui/inventory_slot_kenny_black_ring.png" id="10_kiwfx"]
[ext_resource type="Texture2D" uid="uid://ckctmypotajtf" path="res://assets/gfx/ui/inventory_slot_kenny_black_shoes.png" id="11_ylqbh"]
[ext_resource type="Texture2D" uid="uid://c21a60s4funrr" path="res://assets/gfx/ui/inventory_info_panel.png" id="13_vardb"]
[ext_resource type="AudioStream" uid="uid://x6lxrywls7e2" path="res://assets/audio/sfx/inventory/inventory_open.mp3" id="14_mnwqb"]
[ext_resource type="AudioStream" uid="uid://cfsubtwvpi7yn" path="res://assets/audio/sfx/inventory/inventory_open_inverted.mp3" id="14_nbh80"]
[ext_resource type="AudioStream" uid="uid://djw6c5rb4mm60" path="res://assets/audio/sfx/cloth/leather_cloth_02.wav.mp3" id="17_51fgf"]
[ext_resource type="AudioStream" uid="uid://umoxmryvbm01" path="res://assets/audio/sfx/cloth/leather_cloth_01.wav.mp3" id="18_qk47y"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_nbh80"]
texture = ExtResource("4_nxmsh")
modulate_color = Color(0.511719, 0.511719, 0.511719, 1)
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_kiwfx"]
texture = ExtResource("4_nxmsh")
modulate_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_ylqbh"]
texture = ExtResource("4_nxmsh")
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_nbh80"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_51fgf"]
texture = ExtResource("4_nxmsh")
[node name="Inventory" type="CanvasLayer"]
layer = 21
script = ExtResource("1_k81k7")
[node name="Sprite2D" type="Sprite2D" parent="."]
visible = false
modulate = Color(0.980057, 0.975295, 1, 1)
z_index = -2
z_as_relative = false
position = Vector2(164.625, 92.75)
scale = Vector2(0.258803, 0.263268)
texture = ExtResource("1_0amil")
[node name="ControlContainer" type="Control" parent="."]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ControlStats" type="Control" parent="ControlContainer"]
layout_mode = 1
anchors_preset = 9
anchor_bottom = 1.0
grow_vertical = 2
[node name="Sprite2DPanelz" type="Sprite2D" parent="ControlContainer/ControlStats"]
texture = ExtResource("4_nlhqn")
centered = false
[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlStats"]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
theme_override_constants/margin_left = 3
theme_override_constants/margin_top = 3
theme_override_constants/margin_right = 3
theme_override_constants/margin_bottom = 3
[node name="VBoxContainer" type="VBoxContainer" parent="ControlContainer/ControlStats/MarginContainer"]
layout_mode = 2
[node name="LabelPlayerName" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 6
text = "Fronko"
horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="LabelBaseStats" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/line_spacing = 0
theme_override_font_sizes/font_size = 6
text = "Level
Hp
Mp
Str
Dex
End
Int
Wis
Lck"
[node name="LabelBaseStatsValue" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/line_spacing = 0
theme_override_font_sizes/font_size = 6
text = "1
30 / 30
20 / 20
10
10
10
10
10
10"
horizontal_alignment = 2
[node name="LabelDerivedStats" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/line_spacing = 0
theme_override_font_sizes/font_size = 6
text = "Exp
Coin
Damage
Defense
MovSpd
AtkSpd
Sight
SpellAmp
CritChance"
[node name="LabelDerivedStatsValue" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/line_spacing = 0
theme_override_font_sizes/font_size = 6
text = "400 / 1000
1
2
3
2.1
1.4
7.0
3.0
12.0%"
horizontal_alignment = 2
[node name="ControlInventory" type="Control" parent="ControlContainer"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 1
anchors_preset = 11
anchor_left = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 0
grow_vertical = 2
[node name="Sprite2DPanel" type="Sprite2D" parent="ControlContainer/ControlInventory"]
modulate = Color(0, 0, 0, 0.862745)
texture = ExtResource("2_voqm7")
centered = false
[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInventory"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 2
theme_override_constants/margin_top = 2
theme_override_constants/margin_right = 2
theme_override_constants/margin_bottom = 2
[node name="VBoxContainer" type="VBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 0
[node name="LabelInventory" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 6
text = "Inventory"
horizontal_alignment = 1
[node name="ControlInventory" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 72)
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory"]
layout_mode = 2
offset_left = 2.0
offset_top = 2.0
offset_right = 78.0
offset_bottom = 70.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 2
theme_override_constants/margin_top = 2
theme_override_constants/margin_right = 2
theme_override_constants/margin_bottom = 2
[node name="ScrollContainer" type="ScrollContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer"]
layout_mode = 2
follow_focus = true
[node name="VBoxContainerInventory" type="VBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer"]
layout_mode = 2
theme_override_constants/separation = -4
[node name="HBoxControl1" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 0
[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button3"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="LabelStack" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button3"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -26.0
offset_top = 2.0
offset_right = -2.0
offset_bottom = 26.0
grow_horizontal = 0
theme_override_fonts/font = ExtResource("7_kiwfx")
theme_override_font_sizes/font_size = 8
text = "5"
horizontal_alignment = 2
[node name="Button6" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button6"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button5"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button4"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="HBoxControl2" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 0
[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button3"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button5"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button4"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="HBoxControl3" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 0
[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button3"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button5"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80")
theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx")
theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh")
theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80")
text = " "
[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button4"]
process_mode = 4
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="ControlEquipment" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(80, 64)
layout_mode = 2
[node name="Label" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -21.5
offset_right = 21.5
offset_bottom = 23.0
grow_horizontal = 2
theme_override_font_sizes/font_size = 6
text = "Equipment"
horizontal_alignment = 1
[node name="Sprite2D" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(13, 21)
texture = ExtResource("6_k81k7")
[node name="Sprite2D2" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(67, 21)
texture = ExtResource("7_vardb")
[node name="Sprite2D3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(40, 21)
texture = ExtResource("8_mnwqb")
[node name="Sprite2D4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(13, 48)
texture = ExtResource("9_nbh80")
[node name="Sprite2D9" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(67, 48)
texture = ExtResource("10_kiwfx")
[node name="Sprite2D5" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
modulate = Color(1, 1, 1, 0.670588)
position = Vector2(40, 48)
texture = ExtResource("11_ylqbh")
[node name="ControlEquipmentContainer" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Button" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 1.0
offset_top = 9.0
offset_right = 25.0
offset_bottom = 33.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button2" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 28.0
offset_top = 9.0
offset_right = 52.0
offset_bottom = 33.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button2"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 55.0
offset_top = 9.0
offset_right = 79.0
offset_bottom = 33.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button3"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 1.0
offset_top = 36.0
offset_right = 25.0
offset_bottom = 60.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button4"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 28.0
offset_top = 36.0
offset_right = 52.0
offset_bottom = 60.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button5"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="Button6" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 0
offset_left = 55.0
offset_top = 36.0
offset_right = 79.0
offset_bottom = 60.0
theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf")
[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button6"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="ControlInfo" type="Control" parent="ControlContainer"]
custom_minimum_size = Vector2(0, 33)
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 0
[node name="Control" type="Control" parent="ControlContainer/ControlInfo"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Sprite2D" type="Sprite2D" parent="ControlContainer/ControlInfo/Control"]
texture = ExtResource("13_vardb")
centered = false
[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInfo/Control"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 5
[node name="HBoxContainer" type="HBoxContainer" parent="ControlContainer/ControlInfo/Control/MarginContainer"]
layout_mode = 2
[node name="Control" type="Control" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_vertical = 4
[node name="Sprite2DSelector" type="Sprite2D" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control"]
modulate = Color(0.20704, 0.205805, 0.214844, 1)
texture = ExtResource("4_nxmsh")
centered = false
[node name="Sprite2DItem" type="Sprite2D" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control"]
position = Vector2(4, 4)
texture = ExtResource("2_7vwhs")
centered = false
hframes = 20
vframes = 14
frame = 8
[node name="LabelItemDescription" type="Label" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_vertical = 1
theme_override_font_sizes/font_size = 6
text = "A small, but sturdy wooden shield. + 1 DEF"
[node name="ButtonEquip" type="Button" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"]
visible = false
layout_mode = 2
theme_override_font_sizes/font_size = 8
text = "Equip"
[node name="SfxInventoryClose" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("14_nbh80")
volume_db = -10.0
[node name="SfxInventoryOpen" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("14_mnwqb")
volume_db = -10.0
[node name="SfxUnequip" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("17_51fgf")
[node name="SfxEquip" type="AudioStreamPlayer2D" parent="."]
stream = ExtResource("18_qk47y")

View File

@@ -27,7 +27,11 @@ enum WeaponType {
AMMUNITION,
BOW,
SWORD,
AXE
AXE,
DAGGER,
STAFF,
SPEAR,
MACE
}
var use_function = null

View File

@@ -279,6 +279,19 @@ func _break_into_pieces():
$Shadow.visible = false
$Sprite2DAbove.visible = false
$Sprite2D.visible = false
# Spawn item loot when breaking (30% chance)
if is_multiplayer_authority():
var drop_chance = randf()
if drop_chance < 0.3: # 30% chance to drop item
var item = ItemDatabase.get_random_container_item()
if item:
var entities_node = get_parent()
var game_world = get_tree().get_first_node_in_group("game_world")
if entities_node and game_world:
ItemLootHelper.spawn_item_loot(item, global_position, entities_node, game_world)
print(name, " dropped item: ", item.item_name, " when broken")
if ($SfxShatter.playing):
await $SfxShatter.finished
if ($SfxBreakCrate.playing):
@@ -562,13 +575,13 @@ func _open_chest(by_player: Node = null):
$SfxChestOpen.play()
print(name, " opened by ", by_player.name, "! Item given: ", selected_loot.name)
# Sync chest opening visual to all clients (item already given on server)
if multiplayer.has_multiplayer_peer():
var player_peer_id = by_player.get_multiplayer_authority() if by_player else 0
_sync_chest_open.rpc(selected_loot.type if by_player else "coin", player_peer_id)
else:
push_error("Chest: ERROR - No valid player to give item to!")
# Sync chest opening visual to all clients (item already given on server)
if multiplayer.has_multiplayer_peer():
var player_peer_id = by_player.get_multiplayer_authority() if by_player else 0
_sync_chest_open.rpc(selected_loot.type if by_player else "coin", player_peer_id)
@rpc("any_peer", "reliable")
func _request_chest_open(player_peer_id: int):

566
src/scripts/inventory_ui.gd Normal file
View File

@@ -0,0 +1,566 @@
extends CanvasLayer
# Minimalistic Inventory UI with Stats Panel
# Toggle with Tab key, shows stats, equipment slots and inventory items
var is_open: bool = false
var local_player: Node = null
# Selection tracking
var selected_item: Item = null # Selected inventory item
var selected_slot: String = "" # Selected equipment slot name
var selected_type: String = "" # "item" or "equipment"
# UI Nodes
var container: Control = null
var stats_panel: Control = null
var equipment_panel: Control = null
var inventory_grid: GridContainer = null
var scroll_container: ScrollContainer = null
# Stats labels
var label_base_stats: Label = null
var label_base_stats_value: Label = null
var label_derived_stats: Label = null
var label_derived_stats_value: Label = null
# Store button/item mappings for selection highlighting
var inventory_buttons: Dictionary = {} # item -> button
var equipment_buttons: Dictionary = {} # slot_name -> button
# Equipment slot buttons
var equipment_slots: Dictionary = {
"mainhand": null,
"offhand": null,
"headgear": null,
"armour": null,
"boots": null,
"accessory": null
}
func _ready():
# Set layer to be above game but below chat
layer = 150
# Create UI structure
_setup_ui()
# Find local player
call_deferred("_find_local_player")
func _find_local_player():
# Find the local player
var game_world = get_tree().get_first_node_in_group("game_world")
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:
local_player = local_players[0]
if local_player and local_player.character_stats:
# Connect to character_changed signal
if local_player.character_stats.character_changed.is_connected(_on_character_changed):
local_player.character_stats.character_changed.disconnect(_on_character_changed)
local_player.character_stats.character_changed.connect(_on_character_changed)
# Initial update
_update_ui()
_update_stats()
func _setup_ui():
# Main container (anchored to bottom-left, larger to fit stats + inventory)
container = Control.new()
container.name = "InventoryContainer"
container.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
container.offset_left = 10 # 10px padding from left
container.offset_right = 10 + 620 # Width 620px (stats 200px + inventory 400px + gap 20px)
container.offset_top = -350 # Height 340px (350 - 10)
container.offset_bottom = -10 # 10px padding from bottom
container.visible = false
add_child(container)
# Background panel
var background = ColorRect.new()
background.name = "Background"
background.color = Color(0.1, 0.1, 0.1, 0.85)
background.mouse_filter = Control.MOUSE_FILTER_IGNORE
container.add_child(background)
background.set_anchors_preset(Control.PRESET_FULL_RECT)
# HBox for stats and inventory side by side
var hbox = HBoxContainer.new()
hbox.name = "HBox"
container.add_child(hbox)
hbox.set_anchors_preset(Control.PRESET_FULL_RECT)
hbox.add_theme_constant_override("separation", 10)
# Stats panel (left side)
_create_stats_panel()
hbox.add_child(stats_panel)
# Inventory/Equipment panel (right side)
var inv_panel = VBoxContainer.new()
inv_panel.name = "InventoryPanel"
inv_panel.custom_minimum_size = Vector2(400, 0)
hbox.add_child(inv_panel)
inv_panel.add_theme_constant_override("separation", 5)
# Equipment label
var eq_label = Label.new()
eq_label.text = "Equipment"
eq_label.add_theme_font_size_override("font_size", 14)
var standard_font_resource = null
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
standard_font_resource = load("res://assets/fonts/standard_font.png")
if standard_font_resource:
eq_label.add_theme_font_override("font", standard_font_resource)
inv_panel.add_child(eq_label)
equipment_panel = GridContainer.new()
equipment_panel.name = "EquipmentPanel"
equipment_panel.columns = 3
equipment_panel.add_theme_constant_override("h_separation", 5)
equipment_panel.add_theme_constant_override("v_separation", 5)
inv_panel.add_child(equipment_panel)
# Create equipment slot buttons
_create_equipment_slots()
# Inventory label
var inv_label = Label.new()
inv_label.text = "Inventory"
inv_label.add_theme_font_size_override("font_size", 14)
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
standard_font_resource = load("res://assets/fonts/standard_font.png")
if standard_font_resource:
inv_label.add_theme_font_override("font", standard_font_resource)
inv_panel.add_child(inv_label)
# Scroll container for inventory
scroll_container = ScrollContainer.new()
scroll_container.name = "InventoryScroll"
scroll_container.custom_minimum_size = Vector2(380, 120)
inv_panel.add_child(scroll_container)
# Inventory grid
inventory_grid = GridContainer.new()
inventory_grid.name = "InventoryGrid"
inventory_grid.columns = 6
inventory_grid.add_theme_constant_override("h_separation", 3)
inventory_grid.add_theme_constant_override("v_separation", 3)
scroll_container.add_child(inventory_grid)
func _create_stats_panel():
stats_panel = VBoxContainer.new()
stats_panel.name = "StatsPanel"
stats_panel.custom_minimum_size = Vector2(200, 0)
# Stats label
var stats_label = Label.new()
stats_label.text = "Stats"
stats_label.add_theme_font_size_override("font_size", 14)
var standard_font_resource = null
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
standard_font_resource = load("res://assets/fonts/standard_font.png")
if standard_font_resource:
stats_label.add_theme_font_override("font", standard_font_resource)
stats_panel.add_child(stats_label)
# HBox for stats columns
var stats_hbox = HBoxContainer.new()
stats_hbox.name = "StatsHBox"
stats_panel.add_child(stats_hbox)
stats_hbox.add_theme_constant_override("separation", 5)
# Base stats label
label_base_stats = Label.new()
label_base_stats.name = "LabelBaseStats"
label_base_stats.text = "Level\n\nHP\nMP\n\nSTR\nDEX\nEND\nINT\nWIS\nLCK"
label_base_stats.add_theme_font_size_override("font_size", 10)
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
var font_resource = load("res://assets/fonts/standard_font.png")
if font_resource:
label_base_stats.add_theme_font_override("font", font_resource)
stats_hbox.add_child(label_base_stats)
# Base stats values label
label_base_stats_value = Label.new()
label_base_stats_value.name = "LabelBaseStatsValue"
label_base_stats_value.text = "1\n\n30/30\n20/20\n\n10\n10\n10\n10\n10\n10"
label_base_stats_value.add_theme_font_size_override("font_size", 10)
label_base_stats_value.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
var font_resource = load("res://assets/fonts/standard_font.png")
if font_resource:
label_base_stats_value.add_theme_font_override("font", font_resource)
stats_hbox.add_child(label_base_stats_value)
# Derived stats label
label_derived_stats = Label.new()
label_derived_stats.name = "LabelDerivedStats"
label_derived_stats.text = "XP\nCoin\n\n\n\nDMG\nDEF\nMovSpd\nAtkSpd\nSight\nSpellAmp\nCrit%"
label_derived_stats.add_theme_font_size_override("font_size", 10)
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
var font_resource = load("res://assets/fonts/standard_font.png")
if font_resource:
label_derived_stats.add_theme_font_override("font", font_resource)
stats_hbox.add_child(label_derived_stats)
# Derived stats values label
label_derived_stats_value = Label.new()
label_derived_stats_value.name = "LabelDerivedStatsValue"
label_derived_stats_value.text = "0/100\n0\n\n\n\n2.0\n2.0\n2.1\n1.4\n7.0\n5.0\n12.0%"
label_derived_stats_value.add_theme_font_size_override("font_size", 10)
label_derived_stats_value.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
var font_resource = load("res://assets/fonts/standard_font.png")
if font_resource:
label_derived_stats_value.add_theme_font_override("font", font_resource)
stats_hbox.add_child(label_derived_stats_value)
func _update_stats():
if not local_player or not local_player.character_stats:
return
var char_stats = local_player.character_stats
# Update base stats
label_base_stats_value.text = str(char_stats.level) + "\n\n" + \
str(int(char_stats.hp)) + "/" + str(int(char_stats.maxhp)) + "\n" + \
str(int(char_stats.mp)) + "/" + str(int(char_stats.maxmp)) + "\n\n" + \
str(char_stats.baseStats.str) + "\n" + \
str(char_stats.baseStats.dex) + "\n" + \
str(char_stats.baseStats.end) + "\n" + \
str(char_stats.baseStats.int) + "\n" + \
str(char_stats.baseStats.wis) + "\n" + \
str(char_stats.baseStats.lck)
# Update derived stats
label_derived_stats_value.text = str(int(char_stats.xp)) + "/" + str(int(char_stats.xp_to_next_level)) + "\n" + \
str(char_stats.coin) + "\n\n\n\n" + \
str(char_stats.damage) + "\n" + \
str(char_stats.defense) + "\n" + \
str(char_stats.move_speed) + "\n" + \
str(char_stats.attack_speed) + "\n" + \
str(char_stats.sight) + "\n" + \
str(char_stats.spell_amp) + "\n" + \
str(char_stats.crit_chance) + "%"
func _create_equipment_slots():
# Equipment slot order: mainhand, offhand, headgear, armour, boots, accessory
var slot_names = ["mainhand", "offhand", "headgear", "armour", "boots", "accessory"]
var slot_labels = ["Weapon", "Shield", "Head", "Armour", "Boots", "Accessory"]
for i in range(slot_names.size()):
var slot_name = slot_names[i]
var slot_label = slot_labels[i]
# Container for slot
var slot_container = VBoxContainer.new()
slot_container.name = slot_name + "_slot"
equipment_panel.add_child(slot_container)
# Label
var label = Label.new()
label.text = slot_label
label.add_theme_font_size_override("font_size", 10)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
var font_resource = load("res://assets/fonts/standard_font.png")
if font_resource:
label.add_theme_font_override("font", font_resource)
slot_container.add_child(label)
# Button
var button = Button.new()
button.name = slot_name + "_btn"
button.custom_minimum_size = Vector2(60, 60)
button.flat = true
button.connect("pressed", _on_equipment_slot_pressed.bind(slot_name))
slot_container.add_child(button)
equipment_slots[slot_name] = button
equipment_buttons[slot_name] = button
func _on_equipment_slot_pressed(slot_name: String):
if not local_player or not local_player.character_stats:
return
# Select this slot
selected_slot = slot_name
selected_item = local_player.character_stats.equipment[slot_name]
selected_type = "equipment" if selected_item else ""
_update_selection_highlight()
func _update_selection_highlight():
# Reset all button styles
for button in equipment_buttons.values():
if button:
var highlight = button.get_node_or_null("Highlight")
if highlight:
highlight.queue_free()
for button in inventory_buttons.values():
if button:
var highlight = button.get_node_or_null("Highlight")
if highlight:
highlight.queue_free()
# Highlight selected equipment slot
if selected_type == "equipment" and selected_slot != "":
var button = equipment_buttons.get(selected_slot)
if button:
var highlight = ColorRect.new()
highlight.name = "Highlight"
highlight.color = Color(1.0, 1.0, 0.0, 0.6)
highlight.mouse_filter = Control.MOUSE_FILTER_IGNORE
highlight.z_index = 10
button.add_child(highlight)
highlight.set_anchors_preset(Control.PRESET_FULL_RECT)
# Highlight selected inventory item
if selected_type == "item" and selected_item:
var button = inventory_buttons.get(selected_item)
if button:
var highlight = ColorRect.new()
highlight.name = "Highlight"
highlight.color = Color(1.0, 1.0, 0.0, 0.6)
highlight.mouse_filter = Control.MOUSE_FILTER_IGNORE
highlight.z_index = 10
button.add_child(highlight)
highlight.set_anchors_preset(Control.PRESET_FULL_RECT)
func _update_ui():
if not local_player or not local_player.character_stats:
return
var char_stats = local_player.character_stats
# Clear button mappings
inventory_buttons.clear()
# Update equipment slots
for slot_name in equipment_slots.keys():
var button = equipment_slots[slot_name]
if not button:
continue
# Clear existing children (sprite, highlight)
for child in button.get_children():
child.queue_free()
var equipped_item = char_stats.equipment[slot_name]
if equipped_item:
var sprite = Sprite2D.new()
var texture_path = ""
var use_equipment_path = false
if slot_name == "mainhand" or slot_name == "offhand":
texture_path = equipped_item.spritePath
use_equipment_path = false
else:
texture_path = equipped_item.equipmentPath if equipped_item.equipmentPath != "" else equipped_item.spritePath
use_equipment_path = (equipped_item.equipmentPath != "")
var texture = load(texture_path)
if texture:
sprite.texture = texture
if use_equipment_path:
sprite.hframes = 35
sprite.vframes = 8
else:
sprite.hframes = equipped_item.spriteFrames.x if equipped_item.spriteFrames.x > 0 else 20
sprite.vframes = equipped_item.spriteFrames.y if equipped_item.spriteFrames.y > 0 else 14
sprite.frame = equipped_item.spriteFrame
sprite.scale = Vector2(1.2, 1.2)
button.add_child(sprite)
# Update inventory grid
for child in inventory_grid.get_children():
child.queue_free()
for item in char_stats.inventory:
var button = Button.new()
button.custom_minimum_size = Vector2(60, 60)
button.flat = true
button.connect("pressed", _on_inventory_item_pressed.bind(item))
if item.spritePath != "":
var sprite = Sprite2D.new()
var texture = load(item.spritePath)
if texture:
sprite.texture = texture
sprite.hframes = item.spriteFrames.x if item.spriteFrames.x > 0 else 20
sprite.vframes = item.spriteFrames.y if item.spriteFrames.y > 0 else 14
sprite.frame = item.spriteFrame
sprite.scale = Vector2(1.2, 1.2)
button.add_child(sprite)
inventory_grid.add_child(button)
inventory_buttons[item] = button
# Update selection highlight
_update_selection_highlight()
# Update stats
_update_stats()
func _on_inventory_item_pressed(item: Item):
if not local_player or not local_player.character_stats:
return
selected_item = item
selected_slot = ""
selected_type = "item"
_update_selection_highlight()
func _on_character_changed(_char: CharacterStats):
_update_ui()
_update_stats()
func _input(event):
# Toggle with Tab key
if event is InputEventKey and event.keycode == KEY_TAB and event.pressed and not event.echo:
if is_open:
_close_inventory()
else:
_open_inventory()
get_viewport().set_input_as_handled()
return
if not is_open:
return
# F key: Unequip/equip items
if event is InputEventKey and event.keycode == KEY_F and event.pressed and not event.echo:
_handle_f_key()
get_viewport().set_input_as_handled()
return
# E key: Drop selected item
if event is InputEventKey and event.keycode == KEY_E and event.pressed and not event.echo:
_handle_e_key()
get_viewport().set_input_as_handled()
return
func _handle_f_key():
if not local_player or not local_player.character_stats:
return
var char_stats = local_player.character_stats
if selected_type == "equipment" and selected_slot != "":
var equipped_item = char_stats.equipment[selected_slot]
if equipped_item:
char_stats.unequip_item(equipped_item)
selected_item = null
selected_slot = ""
selected_type = ""
return
if selected_type == "item" and selected_item:
if selected_item.item_type == Item.ItemType.Equippable and selected_item.equipment_type != Item.EquipmentType.NONE:
char_stats.equip_item(selected_item)
selected_item = null
selected_slot = ""
selected_type = ""
elif selected_item.item_type == Item.ItemType.Restoration:
_use_consumable_item(selected_item)
selected_item = null
selected_slot = ""
selected_type = ""
func _use_consumable_item(item: Item):
if not local_player or not local_player.character_stats:
return
var char_stats = local_player.character_stats
if item.modifiers.has("hp"):
var hp_heal = item.modifiers["hp"]
if local_player.has_method("heal"):
local_player.heal(hp_heal)
if item.modifiers.has("mp"):
var mana_amount = item.modifiers["mp"]
char_stats.restore_mana(mana_amount)
var index = char_stats.inventory.find(item)
if index >= 0:
char_stats.inventory.remove_at(index)
char_stats.character_changed.emit(char_stats)
print(local_player.name, " used item: ", item.item_name)
func _handle_e_key():
if not local_player or not local_player.character_stats:
return
if selected_type != "item" or not selected_item:
return
var char_stats = local_player.character_stats
if not selected_item in char_stats.inventory:
return
var index = char_stats.inventory.find(selected_item)
if index >= 0:
char_stats.inventory.remove_at(index)
var drop_position = local_player.global_position + Vector2(randf_range(-20, 20), randf_range(-20, 20))
var game_world = get_tree().get_first_node_in_group("game_world")
var entities_node = null
if game_world:
entities_node = game_world.get_node_or_null("Entities")
if entities_node:
var loot = ItemLootHelper.spawn_item_loot(selected_item, drop_position, entities_node, game_world)
if loot:
if local_player.has_method("get_multiplayer_authority"):
var player_peer_id = local_player.get_multiplayer_authority()
loot.set_meta("dropped_by_peer_id", player_peer_id)
loot.set_meta("drop_time", Time.get_ticks_msec())
selected_item = null
selected_slot = ""
selected_type = ""
char_stats.character_changed.emit(char_stats)
print(local_player.name, " dropped item")
func _open_inventory():
if is_open:
return
is_open = true
if container:
container.visible = true
_lock_player_controls(true)
_update_ui()
if not local_player:
_find_local_player()
func _close_inventory():
if not is_open:
return
is_open = false
if container:
container.visible = false
selected_item = null
selected_slot = ""
selected_type = ""
_lock_player_controls(false)
func _lock_player_controls(lock: bool):
var game_world = get_tree().get_first_node_in_group("game_world")
if game_world:
var player_manager = game_world.get_node_or_null("PlayerManager")
if player_manager:
var local_players = player_manager.get_local_players()
for player in local_players:
player.controls_disabled = lock

View File

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

1361
src/scripts/item_database.gd Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,73 @@
class_name ItemLootHelper
extends RefCounted
# Helper class for spawning Item instances as loot
# Spawn an Item instance as loot at a position
static func spawn_item_loot(item: Item, position: Vector2, entities_node: Node, game_world: Node = null) -> Node:
if not item:
push_error("ItemLootHelper: Cannot spawn null item!")
return null
var loot_scene = preload("res://scenes/loot.tscn")
if not loot_scene:
push_error("ItemLootHelper: Could not load loot.tscn scene!")
return null
# Generate random velocity for physics
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)
var initial_velocity = Vector2(cos(random_angle), sin(random_angle)) * random_force
# Find safe spawn position if game_world is provided
var safe_spawn_pos = position
if game_world and game_world.has_method("_find_nearby_safe_spawn_position"):
safe_spawn_pos = game_world._find_nearby_safe_spawn_position(position, 64.0)
# Spawn loot instance
var loot = loot_scene.instantiate()
if not entities_node:
push_error("ItemLootHelper: entities_node is null!")
return null
# Set properties before adding to scene tree (to avoid physics state change errors)
loot.global_position = safe_spawn_pos
loot.loot_type = loot.LootType.ITEM
loot.item = item # Set the item instance
# 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
# Add to scene tree using call_deferred to avoid physics state change errors
entities_node.call_deferred("add_child", loot)
# Set multiplayer authority to server if in multiplayer
# Since this is a static helper function (RefCounted), we can't access the global multiplayer directly
# Instead, check if game_world is provided and has the sync method (which implies multiplayer)
# We'll also check if entities_node is in a scene tree with multiplayer
var is_multiplayer = false
if game_world and game_world.has_method("_sync_item_loot_spawn"):
# If game_world has the sync method, we're likely in multiplayer
# Use a script call to check multiplayer (game_world is a Node and has access to multiplayer)
# We'll rely on the sync method existence as an indicator
is_multiplayer = true
if is_multiplayer:
loot.set_multiplayer_authority(1)
# Sync to clients if game_world is provided
if game_world and game_world.has_method("_sync_item_loot_spawn"):
# Generate unique loot ID
# loot_id_counter is declared as a variable in game_world.gd, so it always exists
var loot_id = game_world.loot_id_counter
game_world.loot_id_counter += 1
loot.set_meta("loot_id", loot_id)
# Sync item data to clients
game_world._sync_item_loot_spawn.rpc(safe_spawn_pos, item.save(), initial_velocity, random_velocity_z, loot_id)
print("ItemLootHelper: Spawned item loot: ", item.item_name, " at ", safe_spawn_pos)
return loot

View File

@@ -0,0 +1 @@
uid://2jn53es04orw

View File

@@ -7,7 +7,8 @@ enum LootType {
APPLE,
BANANA,
CHERRY,
KEY
KEY,
ITEM # Item instance (equipment, consumables, etc.)
}
@export var loot_type: LootType = LootType.COIN
@@ -29,6 +30,7 @@ var bounce_timer: float = 0.0 # Prevent rapid bounce sounds
var coin_value: int = 1
var heal_amount: float = 20.0
var collected: bool = false
var item: Item = null # Item instance (for LootType.ITEM)
@onready var sprite = $Sprite2D
@onready var shadow = $Shadow
@@ -125,6 +127,20 @@ func _setup_sprite():
sprite.hframes = 20
sprite.vframes = 14
sprite.frame = (13 * 20) + 10
LootType.ITEM:
# Item instance - use item's spritePath and spriteFrame
if item and item.spritePath != "":
var items_texture = load(item.spritePath)
if items_texture:
sprite.texture = items_texture
sprite.hframes = item.spriteFrames.x if item.spriteFrames.x > 0 else 20
sprite.vframes = item.spriteFrames.y if item.spriteFrames.y > 0 else 14
sprite.frame = item.spriteFrame
print("Loot: Set up item sprite for ", item.item_name, " frame=", sprite.frame)
else:
print("Loot: ERROR - Could not load texture from spritePath: ", item.spritePath)
else:
print("Loot: ERROR - Item loot has no item instance or spritePath! item=", item)
func _setup_collision_shape():
if not collision_shape:
@@ -278,6 +294,21 @@ func _animate_coin(delta):
func _on_pickup_area_body_entered(body):
if body and body.is_in_group("player") and not body.is_dead:
# Check if this item was dropped by this player recently (5 second cooldown)
if has_meta("dropped_by_peer_id") and has_meta("drop_time"):
var dropped_by_peer_id = get_meta("dropped_by_peer_id")
var drop_time = get_meta("drop_time")
var current_time = Time.get_ticks_msec()
var time_since_drop = (current_time - drop_time) / 1000.0 # Convert to seconds
# Check if this player dropped the item and cooldown hasn't expired
if body.has_method("get_multiplayer_authority"):
var player_peer_id = body.get_multiplayer_authority()
if player_peer_id == dropped_by_peer_id and time_since_drop < 5.0:
# Player can't pick up their own dropped item for 5 seconds
print("Loot: Player ", body.name, " cannot pick up item they dropped (", time_since_drop, "s ago, need 5s cooldown)")
return
print("Loot: Pickup area entered by player: ", body.name, " is_local: ", body.is_local_player if "is_local_player" in body else "unknown", " is_server: ", multiplayer.is_server())
_pickup(body)
@@ -456,6 +487,61 @@ func _process_pickup_on_server(player: Node):
if sfx_key_collect and sfx_key_collect.playing:
await $SfxKeyCollect.finished
queue_free()
LootType.ITEM:
# Item instance pickup
if not item:
print("Loot: ERROR - Item loot has no item instance!")
queue_free()
return
if sfx_loot_collect:
sfx_loot_collect.play()
# Handle item pickup based on type
if item.item_type == Item.ItemType.Equippable:
# Equippable item - add to inventory
if player.character_stats:
player.character_stats.add_item(item)
print(name, " picked up item: ", item.item_name, " (added to inventory)")
elif item.item_type == Item.ItemType.Restoration:
# Consumable item - use immediately
if player.character_stats:
# Apply modifiers (hp, mp, etc.)
if item.modifiers.has("hp"):
var hp_heal = item.modifiers["hp"]
if player.has_method("heal"):
player.heal(hp_heal)
if item.modifiers.has("mp"):
var mana_amount = item.modifiers["mp"]
player.character_stats.restore_mana(mana_amount)
# TODO: Handle other modifiers (dodge_chance, res_all, etc.) - these would need duration tracking
print(name, " used item: ", item.item_name)
# Show floating text with item name
var items_texture = load(item.spritePath)
var display_text = item.item_name
var text_color = Color.WHITE
# Color code based on item type
if item.item_type == Item.ItemType.Equippable:
text_color = Color.CYAN # Cyan for equipment
elif item.item_type == Item.ItemType.Restoration:
text_color = Color.GREEN # Green for consumables
_show_floating_text(player, display_text, text_color, 0.5, 0.5, items_texture, item.spriteFrames.x, item.spriteFrames.y, item.spriteFrame)
# Sync floating text to client
if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
_sync_show_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, display_text, text_color, 0, item.spriteFrame, player.get_multiplayer_authority())
self.visible = false
# Wait for sound to finish before removing
if sfx_loot_collect and sfx_loot_collect.playing:
await sfx_loot_collect.finished
queue_free()
var processing_pickup: bool = false # Mutex to prevent concurrent pickup processing
@@ -526,7 +612,7 @@ func _sync_remove():
LootType.COIN:
if sfx_coin_collect:
sfx_coin_collect.play()
LootType.APPLE, LootType.BANANA, LootType.CHERRY:
LootType.APPLE, LootType.BANANA, LootType.CHERRY, LootType.ITEM:
if sfx_loot_collect:
sfx_loot_collect.play()
@@ -543,7 +629,7 @@ func _sync_remove():
if loot_type == LootType.COIN and sfx_coin_collect and sfx_coin_collect.playing:
_sound_playing = true
await sfx_coin_collect.finished
elif loot_type in [LootType.APPLE, LootType.BANANA, LootType.CHERRY] and sfx_loot_collect and sfx_loot_collect.playing:
elif loot_type in [LootType.APPLE, LootType.BANANA, LootType.CHERRY, LootType.ITEM] and sfx_loot_collect and sfx_loot_collect.playing:
_sound_playing = true
await sfx_loot_collect.finished
@@ -582,6 +668,13 @@ func _sync_show_floating_text(loot_type_value: int, text: String, color_value: C
item_texture = load("res://assets/gfx/pickups/items_n_shit.png")
sprite_hframes = 20
sprite_vframes = 14
LootType.ITEM:
# Item instance - use item's sprite path
# Note: item data is not available on client in this sync, so we use default
# The actual item sprite is set when the loot is created
item_texture = load("res://assets/gfx/pickups/items_n_shit.png")
sprite_hframes = 20
sprite_vframes = 14
# Show floating text on client
_show_floating_text(player, text, color_value, 0.5, 0.5, item_texture, sprite_hframes, sprite_vframes, sprite_frame_value)

View File

@@ -389,6 +389,11 @@ func _initialize_character_stats():
# Initialize health/mana from stats
character_stats.hp = character_stats.maxhp
character_stats.mp = character_stats.maxmp
# Connect signals
if character_stats:
character_stats.level_up_stats.connect(_on_level_up_stats)
character_stats.character_changed.connect(_on_character_changed)
func _randomize_stats():
# Randomize base stats within reasonable ranges
@@ -471,13 +476,33 @@ func _apply_appearance_to_sprites():
sprite_body.vframes = 8
sprite_body.modulate = Color.WHITE # Remove old color tint
# Boots - empty (bare)
# Boots
if sprite_boots:
sprite_boots.texture = null
var equipped_boots = character_stats.equipment["boots"]
if equipped_boots and equipped_boots.equipmentPath != "":
var boots_texture = load(equipped_boots.equipmentPath)
if boots_texture:
sprite_boots.texture = boots_texture
sprite_boots.hframes = 35
sprite_boots.vframes = 8
else:
sprite_boots.texture = null
else:
sprite_boots.texture = null
# Armour - empty (bare)
# Armour
if sprite_armour:
sprite_armour.texture = null
var equipped_armour = character_stats.equipment["armour"]
if equipped_armour and equipped_armour.equipmentPath != "":
var armour_texture = load(equipped_armour.equipmentPath)
if armour_texture:
sprite_armour.texture = armour_texture
sprite_armour.hframes = 35
sprite_armour.vframes = 8
else:
sprite_armour.texture = null
else:
sprite_armour.texture = null
# Facial Hair
if sprite_facial_hair:
@@ -546,19 +571,35 @@ func _apply_appearance_to_sprites():
else:
sprite_addons.texture = null
# Headgear - empty (bare)
# Headgear
if sprite_headgear:
sprite_headgear.texture = null
var equipped_headgear = character_stats.equipment["headgear"]
if equipped_headgear and equipped_headgear.equipmentPath != "":
var headgear_texture = load(equipped_headgear.equipmentPath)
if headgear_texture:
sprite_headgear.texture = headgear_texture
sprite_headgear.hframes = 35
sprite_headgear.vframes = 8
else:
sprite_headgear.texture = null
else:
sprite_headgear.texture = null
# Weapon - empty (bare)
# Weapon (Mainhand)
# NOTE: Weapons should NEVER use equipmentPath - they don't have character sprite sheets
# Weapons are only displayed as inventory icons (spritePath), not as character sprite layers
if sprite_weapon:
sprite_weapon.texture = null
sprite_weapon.texture = null # Weapons don't use character sprite layers
print(name, " appearance applied: skin=", character_stats.skin,
" hair=", character_stats.hairstyle,
" facial_hair=", character_stats.facial_hair,
" eyes=", character_stats.eyes)
func _on_character_changed(_char: CharacterStats):
# Update appearance when character stats change (e.g., equipment)
_apply_appearance_to_sprites()
func _get_player_color() -> Color:
# Legacy function - now returns white (no color tint)
return Color.WHITE
@@ -1582,15 +1623,40 @@ func _perform_attack():
# Delay before spawning sword slash
await get_tree().create_timer(0.15).timeout
# Calculate damage from character_stats with randomization
var base_damage = 20.0 # Default damage
if character_stats:
base_damage = character_stats.damage
# D&D style randomization: ±20% variance
var damage_variance = 0.2
var damage_multiplier = 1.0 + randf_range(-damage_variance, damage_variance)
var final_damage = base_damage * damage_multiplier
# Critical strike chance (based on LCK stat)
var crit_chance = 0.0
if character_stats:
crit_chance = (character_stats.baseStats.lck + character_stats.get_pass("lck")) * 0.01 # 1% per LCK point
var is_crit = randf() < crit_chance
if is_crit:
final_damage *= 2.0 # Critical strikes deal 2x damage
print(name, " CRITICAL STRIKE! (LCK: ", character_stats.baseStats.lck + character_stats.get_pass("lck"), ")")
# Round to 1 decimal place
final_damage = round(final_damage * 10.0) / 10.0
# Spawn sword projectile
if sword_projectile_scene:
var projectile = sword_projectile_scene.instantiate()
get_parent().add_child(projectile)
projectile.setup(attack_direction, self)
projectile.setup(attack_direction, self, final_damage)
# Store crit status for visual feedback
if is_crit:
projectile.set_meta("is_crit", true)
# Spawn projectile a bit in front of the player
var spawn_offset = attack_direction * 10.0 # 10 pixels in front
projectile.global_position = global_position + spawn_offset
print(name, " attacked with sword projectile!")
print(name, " attacked with sword projectile! Damage: ", final_damage, " (base: ", base_damage, ", crit: ", is_crit, ")")
# Sync attack over network
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
@@ -2093,13 +2159,37 @@ func take_damage(amount: float, attacker_position: Vector2):
if is_dead:
return
# Take damage using character_stats
# Check for dodge chance (based on DEX)
var _was_dodged = false
if character_stats:
character_stats.take_damage(amount, false) # false = not magical damage
print(name, " took ", amount, " damage! Health: ", character_stats.hp, "/", character_stats.maxhp)
var dodge_roll = randf()
var dodge_chance = character_stats.dodge_chance
if dodge_roll < dodge_chance:
_was_dodged = true
print(name, " DODGED the attack! (DEX: ", character_stats.baseStats.dex + character_stats.get_pass("dex"), ", dodge chance: ", dodge_chance * 100.0, "%)")
# Show "DODGED" text
_show_damage_number(0.0, attacker_position, false, false, true) # is_dodged = true
# Sync dodge visual to other clients
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
_sync_damage.rpc(0.0, attacker_position, false, false, true) # is_dodged = true
return # No damage taken, exit early
# If not dodged, apply damage with DEF reduction
var actual_damage = amount
if character_stats:
# Calculate damage after DEF reduction (critical hits pierce 80% of DEF)
actual_damage = character_stats.calculate_damage(amount, false, false) # false = not magical, false = not critical (enemy attacks don't crit yet)
# Apply the reduced damage using take_damage (which handles health modification and signals)
var _old_hp = character_stats.hp
character_stats.modify_health(-actual_damage)
if character_stats.hp <= 0:
character_stats.no_health.emit()
character_stats.character_changed.emit(character_stats)
print(name, " took ", actual_damage, " damage (", amount, " base - ", character_stats.defense, " DEF = ", actual_damage, ")! Health: ", character_stats.hp, "/", character_stats.maxhp)
else:
# Fallback for legacy
current_health -= amount
actual_damage = amount
print(name, " took ", amount, " damage! Health: ", current_health)
# Play damage sound effect
@@ -2129,11 +2219,11 @@ func take_damage(amount: float, attacker_position: Vector2):
tween.tween_property(sprite_body, "modulate", Color.WHITE, 0.1)
# Show damage number (red, using dmg_numbers.png font)
_show_damage_number(amount, attacker_position)
_show_damage_number(actual_damage, attacker_position)
# Sync damage visual effects to other clients (including damage numbers)
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
_sync_damage.rpc(amount, attacker_position)
_sync_damage.rpc(actual_damage, attacker_position)
# Check if dead - but wait for damage animation to play first
var health = character_stats.hp if character_stats else current_health
@@ -2493,11 +2583,9 @@ func use_key():
return false
@rpc("authority", "reliable")
func _show_damage_number(amount: float, from_position: Vector2):
func _show_damage_number(amount: float, from_position: Vector2, is_crit: bool = false, is_miss: bool = false, is_dodged: bool = false):
# Show damage number (red, using dmg_numbers.png font) above player
# Only show if damage > 0
if amount <= 0:
return
# Show even if amount is 0 for MISS/DODGED
var damage_number_scene = preload("res://scenes/damage_number.tscn")
if not damage_number_scene:
@@ -2507,9 +2595,16 @@ func _show_damage_number(amount: float, from_position: Vector2):
if not damage_label:
return
# Set damage text and red color
damage_label.label = str(int(amount))
damage_label.color = Color.RED
# Set text and color based on type
if is_dodged:
damage_label.label = "DODGED"
damage_label.color = Color.CYAN
elif is_miss:
damage_label.label = "MISS"
damage_label.color = Color.GRAY
else:
damage_label.label = str(int(amount))
damage_label.color = Color.ORANGE if is_crit else Color.RED
# Calculate direction from attacker (slight upward variation)
var direction_from_attacker = (global_position - from_position).normalized()
@@ -2531,12 +2626,81 @@ func _show_damage_number(amount: float, from_position: Vector2):
get_tree().current_scene.add_child(damage_label)
damage_label.global_position = global_position + Vector2(0, -16)
func _on_level_up_stats(stats_increased: Array):
# Show floating text for level up - "LEVEL UP!" and stat increases
# Use damage_number scene with damage_numbers font
if not character_stats:
return
# Stat name to display name mapping
var stat_display_names = {
"str": "STR",
"dex": "DEX",
"int": "INT",
"end": "END",
"wis": "WIS",
"lck": "LCK"
}
# Stat name to color mapping
var stat_colors = {
"str": Color.RED,
"dex": Color.GREEN,
"int": Color.BLUE,
"end": Color.WHITE,
"wis": Color(0.5, 0.0, 0.5), # Purple
"lck": Color.YELLOW
}
var damage_number_scene = preload("res://scenes/damage_number.tscn")
if not damage_number_scene:
return
# Get entities node for adding text
var game_world = get_tree().get_first_node_in_group("game_world")
var entities_node = null
if game_world:
entities_node = game_world.get_node_or_null("Entities")
if not entities_node:
entities_node = get_tree().current_scene
var base_y_offset = -32.0 # Start above player head
var y_spacing = 12.0 # Space between each text
# Show "LEVEL UP!" first (in white)
var level_up_text = damage_number_scene.instantiate()
if level_up_text:
level_up_text.label = "LEVEL UP!"
level_up_text.color = Color.WHITE
level_up_text.direction = Vector2(0, -1) # Straight up
entities_node.add_child(level_up_text)
level_up_text.global_position = global_position + Vector2(0, base_y_offset)
base_y_offset -= y_spacing
# Show each stat increase
for i in range(stats_increased.size()):
var stat_name = stats_increased[i]
var stat_text = damage_number_scene.instantiate()
if stat_text:
var display_name = stat_display_names.get(stat_name, stat_name.to_upper())
stat_text.label = "+1 " + display_name
stat_text.color = stat_colors.get(stat_name, Color.WHITE)
stat_text.direction = Vector2(randf_range(-0.2, 0.2), -1.0).normalized() # Slight random spread
entities_node.add_child(stat_text)
stat_text.global_position = global_position + Vector2(0, base_y_offset)
base_y_offset -= y_spacing
@rpc("any_peer", "reliable")
func _sync_damage(_amount: float, attacker_position: Vector2):
func _sync_damage(_amount: float, attacker_position: Vector2, is_crit: bool = false, is_miss: bool = false, is_dodged: bool = false):
# This RPC only syncs visual effects, not damage application
# (damage is already applied via rpc_take_damage)
if not is_multiplayer_authority():
# Play damage sound effect on clients
# If dodged, only show dodge text, no other effects
if is_dodged:
_show_damage_number(0.0, attacker_position, false, false, true)
return
# Play damage sound and effects
if sfx_take_damage:
sfx_take_damage.play()
@@ -2561,6 +2725,9 @@ func _sync_damage(_amount: float, attacker_position: Vector2):
var tween = create_tween()
tween.tween_property(sprite_body, "modulate", Color.RED, 0.1)
tween.tween_property(sprite_body, "modulate", Color.WHITE, 0.1)
# Show damage number
_show_damage_number(_amount, attacker_position, is_crit, is_miss, false)
func on_grabbed(by_player):
print(name, " grabbed by ", by_player.name)

View File

@@ -25,9 +25,10 @@ func _ready():
if hit_area:
hit_area.body_entered.connect(_on_body_entered)
func setup(direction: Vector2, owner_player: Node):
func setup(direction: Vector2, owner_player: Node, damage_value: float = 20.0):
travel_direction = direction.normalized()
player_owner = owner_player
damage = damage_value # Set damage from player
current_speed = initial_speed
# Rotate sprite to face travel direction
@@ -102,19 +103,37 @@ func _on_body_entered(body):
# Deal damage to enemies - only authority (creator) deals damage
elif body.is_in_group("enemy") and body.has_method("rpc_take_damage"):
$SfxImpact.play()
var attacker_pos = player_owner.global_position if player_owner else global_position
var is_crit = get_meta("is_crit") if has_meta("is_crit") else false
# Check hit chance (based on player's DEX stat)
var hit_roll = randf()
var hit_chance = 0.95 # Base hit chance
if player_owner and player_owner.character_stats:
hit_chance = player_owner.character_stats.hit_chance
var is_miss = hit_roll >= hit_chance
if is_miss:
# Attack missed - show MISS text, don't deal damage, don't play impact sound
print("Player MISSED enemy: ", body.name, "! (hit chance: ", hit_chance * 100.0, "%)")
# Show MISS text on the enemy
if body.has_method("_show_damage_number"):
body._show_damage_number(0.0, attacker_pos, false, true, false) # is_miss = true
return # Don't deal damage, don't play impact sound, don't cause knockback
# Hit successful - play impact sound and deal damage
$SfxImpact.play()
var enemy_peer_id = body.get_multiplayer_authority()
if enemy_peer_id != 0:
# If enemy is on the same peer (server), call directly
if multiplayer.is_server() and enemy_peer_id == multiplayer.get_unique_id():
body.rpc_take_damage(damage, attacker_pos)
body.rpc_take_damage(damage, attacker_pos, is_crit)
else:
# Send RPC to enemy's authority (server) - clients can do this!
body.rpc_take_damage.rpc_id(enemy_peer_id, damage, attacker_pos)
body.rpc_take_damage.rpc_id(enemy_peer_id, damage, attacker_pos, is_crit)
else:
# Fallback: broadcast if we can't get peer_id
body.rpc_take_damage.rpc(damage, attacker_pos)
body.rpc_take_damage.rpc(damage, attacker_pos, is_crit)
# Debug print - handle null player_owner safely
var owner_name: String = "none"
var is_authority: bool = false