Turns the sandbox into a survival roguelike loop: outfit your gladiator with banked gold, enter the arena, earn gold from kills and wave clears, die, keep 10% of your total value (floored at the 500 fresh-start). Economy (GameState autoload): - banked_gold, owned items, and loadout persist to user://save.cfg - Kill gold (basic 10g, armed 25g) credited server-side to the killer - Wave-clear bonus (25 + 5/wave) for all players; wave synced to peers - Death settlement liquidates gear, guarded against double-fire Character creation / shop: - Menu reframed: armory shop + live Character Sheet loadout preview - Character Sheet gains preview mode (renders from loadout, no player) - Buy/Equip with hand rules; two-handers and off-hands displace each other symmetrically (shop and in-game pickups) - Chosen loadout auto-equips on spawn via existing RPC path Combat feel: - Attacks snap to camera facing and lock direction for the swing - Dash commits to its direction for the full duration - Weapon slots show name text when no icon is set - Hitbox/hurtbox debug meshes hidden by default (H toggles) Death is final: players no longer respawn; a results screen shows waves, time, kills, and the settlement, then returns to camp. Armed enemies no longer respawn (were an infinite gold farm and respawned invisible).
135 lines
3.9 KiB
GDScript
135 lines
3.9 KiB
GDScript
extends Control
|
|
class_name WeaponSlot
|
|
## Weapon slot that displays weapon icon and shows tooltip on hover
|
|
|
|
# References
|
|
@onready var icon_rect: TextureRect = $SlotPanel/MarginContainer/IconRect
|
|
var tooltip: PanelContainer = null
|
|
# Fallback name display for weapons without an icon texture
|
|
var _name_label: Label = null
|
|
|
|
# Weapon data
|
|
var weapon_data: WeaponData = null
|
|
var slot_name: String = "Main Hand"
|
|
|
|
func _ready():
|
|
# Create tooltip as a top-level CanvasLayer child so it's not clipped
|
|
_create_tooltip()
|
|
|
|
# Connect mouse events
|
|
mouse_entered.connect(_on_mouse_entered)
|
|
mouse_exited.connect(_on_mouse_exited)
|
|
|
|
## Create tooltip as a top-level node to avoid clipping
|
|
func _create_tooltip():
|
|
# Load tooltip scene template
|
|
var tooltip_scene = load("res://level/ui/scenes/weapon_tooltip.tscn")
|
|
if tooltip_scene:
|
|
tooltip = tooltip_scene.instantiate()
|
|
# Add to the root CanvasLayer (HUD) so it's not clipped
|
|
var hud = get_node_or_null("/root/HUD")
|
|
if hud:
|
|
hud.add_child(tooltip)
|
|
tooltip.hide()
|
|
else:
|
|
push_error("[WeaponSlot] Could not find HUD node")
|
|
else:
|
|
push_error("[WeaponSlot] Failed to load weapon_tooltip.tscn")
|
|
|
|
## Set weapon data and update display
|
|
func set_weapon(data: WeaponData, name: String = "Main Hand"):
|
|
weapon_data = data
|
|
slot_name = name
|
|
_update_display()
|
|
|
|
## Clear the weapon slot
|
|
func clear_weapon():
|
|
weapon_data = null
|
|
_update_display()
|
|
|
|
## Update the icon display
|
|
func _update_display():
|
|
if not icon_rect:
|
|
return
|
|
|
|
if weapon_data and weapon_data.icon:
|
|
icon_rect.texture = weapon_data.icon
|
|
icon_rect.modulate = Color.WHITE
|
|
_set_name_fallback("")
|
|
elif weapon_data:
|
|
# No icon on this weapon - show its name in the slot instead
|
|
icon_rect.texture = null
|
|
icon_rect.modulate = Color.WHITE
|
|
_set_name_fallback(weapon_data.weapon_name)
|
|
else:
|
|
icon_rect.texture = null
|
|
icon_rect.modulate = Color(0.3, 0.3, 0.3, 0.5) # Dim empty slot
|
|
_set_name_fallback("")
|
|
|
|
## Show/hide the fallback name label ("" hides it)
|
|
func _set_name_fallback(text: String):
|
|
if text == "":
|
|
if _name_label:
|
|
_name_label.hide()
|
|
return
|
|
|
|
if not _name_label:
|
|
_name_label = Label.new()
|
|
_name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
_name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
_name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
_name_label.add_theme_font_size_override("font_size", 12)
|
|
_name_label.add_theme_color_override("font_color", Color(1.0, 0.85, 0.4))
|
|
_name_label.add_theme_color_override("font_outline_color", Color.BLACK)
|
|
_name_label.add_theme_constant_override("outline_size", 2)
|
|
_name_label.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
_name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
var container = get_node_or_null("SlotPanel/MarginContainer")
|
|
if container:
|
|
container.add_child(_name_label)
|
|
else:
|
|
add_child(_name_label)
|
|
|
|
_name_label.text = text
|
|
_name_label.show()
|
|
|
|
## Show tooltip on mouse enter
|
|
func _on_mouse_entered():
|
|
if weapon_data and tooltip:
|
|
_update_tooltip()
|
|
# Position tooltip to the right of the slot
|
|
var global_pos = global_position
|
|
tooltip.position = Vector2(global_pos.x + size.x + 10, global_pos.y)
|
|
tooltip.show()
|
|
|
|
## Hide tooltip on mouse exit
|
|
func _on_mouse_exited():
|
|
if tooltip:
|
|
tooltip.hide()
|
|
|
|
## Update tooltip content
|
|
func _update_tooltip():
|
|
if not tooltip or not weapon_data:
|
|
return
|
|
|
|
var tooltip_label = tooltip.get_node_or_null("MarginContainer/VBoxContainer/StatsLabel")
|
|
if not tooltip_label:
|
|
return
|
|
|
|
var text = ""
|
|
text += slot_name.to_upper() + "\n"
|
|
text += weapon_data.weapon_name + "\n"
|
|
text += "---\n"
|
|
text += "Damage: " + str(weapon_data.damage) + "\n"
|
|
text += "Range: " + str(weapon_data.attack_range) + "\n"
|
|
text += "Cooldown: " + str(weapon_data.attack_cooldown) + "s\n"
|
|
text += "Knockback: " + str(weapon_data.knockback_force) + "\n"
|
|
|
|
if weapon_data.can_block:
|
|
text += "Block: " + str(int(weapon_data.block_reduction * 100)) + "%\n"
|
|
|
|
if weapon_data.description:
|
|
text += "\n" + weapon_data.description
|
|
|
|
tooltip_label.text = text
|