Add final progression to tiny sword, fixes and tweeks required

This commit is contained in:
2026-05-15 00:47:38 +02:00
parent 66666defcb
commit 738381a16b
43 changed files with 1127 additions and 339 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://rpedoq4102to"
path="res://.godot/imported/Castle.png-abe84e074a7668619903091d591ffce7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/gyms/tiny_sword/buildings/devil_idol/Castle.png"
dest_files=["res://.godot/imported/Castle.png-abe84e074a7668619903091d591ffce7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@@ -0,0 +1,135 @@
class_name DevilIdol
extends Node2D
const UNLOCK_GOAL_ID: StringName = &"gold_50M_food_5M_wood_5M"
const SAVE_KEY: String = "devil_idol_state"
const HP_KEY: String = "current_hp"
## Emitted when the idol takes damage.
signal idol_damaged(current_hp: BigNumber, max_hp: BigNumber)
## Emitted when the idol is destroyed (one-time win condition).
signal idol_destroyed()
## Maximum HP mantissa.
@export var max_hp_mantissa: float = 1.0
## Maximum HP exponent (default 1e12 = 1 trillion).
@export var max_hp_exponent: int = 12
@onready var game_state: LevelGameState = find_parent("LevelGameState")
@onready var _sprite: Sprite2D = $Sprite2D
@onready var _area: Area2D = $Area2D
@onready var _panel: PanelContainer = $IdolPanel
@onready var _hp_value: Label = $IdolPanel/MarginContainer/VBoxContainer/HPValue
@onready var _damage_button: Button = $IdolPanel/MarginContainer/VBoxContainer/DamageButton
@onready var _victory_label: Label = $IdolPanel/MarginContainer/VBoxContainer/VictoryLabel
var _max_hp: BigNumber
var _current_hp: BigNumber
var _is_targetable: bool = false
var _is_destroyed: bool = false
func _ready() -> void:
assert(game_state != null, "DevilIdol: LevelGameState parent not found")
_max_hp = BigNumber.new(max_hp_mantissa, max_hp_exponent)
_current_hp = BigNumber.new(max_hp_mantissa, max_hp_exponent)
# Load persisted HP from save data
_load_state()
# Panel hidden by default; shown on hover if unlocked
_panel.visible = false
_victory_label.visible = false
_damage_button.pressed.connect(_on_damage_button_pressed)
# Connect to goal completion for unlocking
game_state.goal_completed.connect(_on_goal_completed)
# If goal was already completed in a previous session, unlock immediately
if game_state.is_goal_completed(UNLOCK_GOAL_ID):
_unlock_idol()
_update_ui()
func _on_area_2d_mouse_entered() -> void:
if _is_targetable and not _is_destroyed:
_panel.visible = true
func _on_area_2d_mouse_exited() -> void:
_panel.visible = false
func _on_goal_completed(goal_id: StringName) -> void:
if goal_id == UNLOCK_GOAL_ID:
_unlock_idol()
func _unlock_idol() -> void:
_is_targetable = true
func _on_damage_button_pressed() -> void:
if _is_destroyed:
return
var unit_balance: BigNumber = game_state.get_currency_amount_by_id(&"unit")
if unit_balance.mantissa <= 0.0:
return
# Spend all available units as damage
var damage: BigNumber = BigNumber.new(unit_balance.mantissa, unit_balance.exponent)
if game_state.spend_currency_by_id(&"unit", damage):
_current_hp = _current_hp.subtract(damage)
if _current_hp.mantissa <= 0.0:
_current_hp = BigNumber.new(0.0, 0)
_is_destroyed = true
idol_destroyed.emit()
_victory_label.visible = true
_panel.visible = true
idol_damaged.emit(_current_hp, _max_hp)
_save_state()
_update_ui()
func _update_ui() -> void:
if _is_destroyed:
_hp_value.text = "0 / %s — DESTROYED" % _max_hp.to_string_suffix(2)
_damage_button.disabled = true
_damage_button.text = "VICTORY!"
return
var progress_pct: float = 0.0
if _max_hp.mantissa > 0.0:
progress_pct = (1.0 - (_current_hp.mantissa / _max_hp.mantissa)) * pow(10.0, float(_current_hp.exponent - _max_hp.exponent))
progress_pct = clampf(progress_pct * 100.0, 0.0, 100.0)
_hp_value.text = "%s / %s (%.1f%%)" % [_current_hp.to_string_suffix(2), _max_hp.to_string_suffix(2), progress_pct]
var unit_balance: BigNumber = game_state.get_currency_amount_by_id(&"unit")
_damage_button.text = "Send Army (%s units)" % unit_balance.to_string_suffix(2)
_damage_button.disabled = unit_balance.mantissa <= 0.0
func _save_state() -> void:
if game_state == null:
return
var payload: Dictionary = {HP_KEY: _current_hp.serialize()}
game_state.set_external_save_data(SAVE_KEY, payload)
func _load_state() -> void:
if game_state == null:
return
var saved: Dictionary = game_state.get_external_save_data(SAVE_KEY)
if saved.has(HP_KEY):
var raw_hp: Variant = saved.get(HP_KEY)
if raw_hp is Dictionary:
_current_hp = BigNumber.deserialize(raw_hp)
if _current_hp.mantissa <= 0.0:
_current_hp = BigNumber.new(0.0, 0)
_is_destroyed = true
_victory_label.visible = true

View File

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

View File

@@ -0,0 +1,57 @@
[gd_scene format=3 uid="uid://d1cvlnd5vwjol"]
[ext_resource type="Script" uid="uid://blfof77kjp7x0" path="res://docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd" id="1_script"]
[ext_resource type="Texture2D" uid="uid://rpedoq4102to" path="res://docs/gyms/tiny_sword/buildings/devil_idol/Castle.png" id="2_g7pcm"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_idol"]
size = Vector2(312, 198)
[node name="DevilIdol" type="Node2D" unique_id=797117647]
script = ExtResource("1_script")
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=661703335]
texture = ExtResource("2_g7pcm")
[node name="Area2D" type="Area2D" parent="." unique_id=1812195052]
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D" unique_id=1492517397]
position = Vector2(1, 22)
shape = SubResource("RectangleShape2D_idol")
[node name="IdolPanel" type="PanelContainer" parent="." unique_id=906038530]
offset_left = -200.0
offset_top = -120.0
offset_right = 200.0
offset_bottom = -10.0
[node name="MarginContainer" type="MarginContainer" parent="IdolPanel" unique_id=1952977282]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="IdolPanel/MarginContainer" unique_id=1214756176]
layout_mode = 2
theme_override_constants/separation = 8
[node name="TitleLabel" type="Label" parent="IdolPanel/MarginContainer/VBoxContainer" unique_id=1268978638]
layout_mode = 2
text = "Devil Idol"
[node name="HPValue" type="Label" parent="IdolPanel/MarginContainer/VBoxContainer" unique_id=529436307]
layout_mode = 2
text = "??? / ???"
[node name="DamageButton" type="Button" parent="IdolPanel/MarginContainer/VBoxContainer" unique_id=1565608485]
layout_mode = 2
disabled = true
text = "Send Army"
[node name="VictoryLabel" type="Label" parent="IdolPanel/MarginContainer/VBoxContainer" unique_id=169309796]
layout_mode = 2
text = "THE DEVIL IDOL HAS BEEN DESTROYED!"
horizontal_alignment = 1
[connection signal="mouse_entered" from="Area2D" to="." method="_on_area_2d_mouse_entered"]
[connection signal="mouse_exited" from="Area2D" to="." method="_on_area_2d_mouse_exited"]