Compare commits
5 Commits
856aaefb38
...
e13368ab17
| Author | SHA1 | Date |
|---|---|---|
|
|
e13368ab17 | |
|
|
1883760b6e | |
|
|
b71ea39155 | |
|
|
7c3d95aa9f | |
|
|
8dde32b9d2 |
Binary file not shown.
|
|
@ -0,0 +1,24 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="wav"
|
||||||
|
type="AudioStreamWAV"
|
||||||
|
uid="uid://c2xs6x6q70sr1"
|
||||||
|
path="res://.godot/imported/achievement_unlock.wav-9a9f9e013cd33e691b8d6734dd2cf61a.sample"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/music&sfx/sfx/achievement_unlock.wav"
|
||||||
|
dest_files=["res://.godot/imported/achievement_unlock.wav-9a9f9e013cd33e691b8d6734dd2cf61a.sample"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
force/8_bit=false
|
||||||
|
force/mono=false
|
||||||
|
force/max_rate=false
|
||||||
|
force/max_rate_hz=44100
|
||||||
|
edit/trim=false
|
||||||
|
edit/normalize=false
|
||||||
|
edit/loop_mode=0
|
||||||
|
edit/loop_begin=0
|
||||||
|
edit/loop_end=-1
|
||||||
|
compress/mode=2
|
||||||
|
|
@ -23,6 +23,8 @@ buses/default_bus_layout="uid://5oswo22yvmtg"
|
||||||
|
|
||||||
MusicManager="*uid://dmu7041wi1upt"
|
MusicManager="*uid://dmu7041wi1upt"
|
||||||
SpellLibrary="*res://scripts/SpellLibrary.gd"
|
SpellLibrary="*res://scripts/SpellLibrary.gd"
|
||||||
|
StatsManager="*res://scripts/stats_manager.gd"
|
||||||
|
AchievementManager="*res://scripts/achievement_manager.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/achievements_screen.gd" id="1_ach"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://2ddggeetjj1t" path="res://assets/images/screenshot_game.png" id="2_ach"]
|
||||||
|
[ext_resource type="FontFile" uid="uid://8v71dcws4q6o" path="res://assets/fonts/slkscre.ttf" id="3_ach"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_info"]
|
||||||
|
bg_color = Color(0.04, 0.04, 0.1, 0.9)
|
||||||
|
border_width_left = 1
|
||||||
|
border_width_top = 1
|
||||||
|
border_width_right = 1
|
||||||
|
border_width_bottom = 1
|
||||||
|
border_color = Color(0.4, 0.3, 0.1, 0.8)
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 8.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 8.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_normal"]
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hover"]
|
||||||
|
bg_color = Color(0.45, 0.1, 0.7, 0.35)
|
||||||
|
border_width_left = 2
|
||||||
|
border_width_top = 2
|
||||||
|
border_width_right = 2
|
||||||
|
border_width_bottom = 2
|
||||||
|
border_color = Color(0.75, 0.45, 1.0, 0.75)
|
||||||
|
corner_radius_top_left = 3
|
||||||
|
corner_radius_top_right = 3
|
||||||
|
corner_radius_bottom_right = 3
|
||||||
|
corner_radius_bottom_left = 3
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pressed"]
|
||||||
|
bg_color = Color(0.3, 0.05, 0.5, 0.55)
|
||||||
|
border_width_left = 2
|
||||||
|
border_width_top = 2
|
||||||
|
border_width_right = 2
|
||||||
|
border_width_bottom = 2
|
||||||
|
border_color = Color(0.9, 0.6, 1.0, 1.0)
|
||||||
|
corner_radius_top_left = 3
|
||||||
|
corner_radius_top_right = 3
|
||||||
|
corner_radius_bottom_right = 3
|
||||||
|
corner_radius_bottom_left = 3
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_focus"]
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[node name="AchievementsScreen" type="CanvasLayer"]
|
||||||
|
script = ExtResource("1_ach")
|
||||||
|
|
||||||
|
[node name="BackgroundSprite" type="TextureRect" parent="."]
|
||||||
|
z_index = -2
|
||||||
|
offset_left = 4.0
|
||||||
|
offset_top = 2.0
|
||||||
|
offset_right = 1277.0
|
||||||
|
offset_bottom = 718.0
|
||||||
|
texture = ExtResource("2_ach")
|
||||||
|
expand_mode = 2
|
||||||
|
|
||||||
|
[node name="BackgroundRect" type="ColorRect" parent="."]
|
||||||
|
z_index = -1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
color = Color(0, 0, 0, 0.6)
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
z_index = 1
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -308.5
|
||||||
|
offset_top = -300.0
|
||||||
|
offset_right = 308.5
|
||||||
|
offset_bottom = 300.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="TitleLabel" type="Label" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 0.9, 0.6, 1)
|
||||||
|
theme_override_colors/font_outline_color = Color(0.4, 0.05, 0.65, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_fonts/font = ExtResource("3_ach")
|
||||||
|
theme_override_font_sizes/font_size = 56
|
||||||
|
text = "Achievements"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="CounterLabel" type="Label" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.9, 0.85, 0.75, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("3_ach")
|
||||||
|
theme_override_font_sizes/font_size = 20
|
||||||
|
text = "Unlocked: 0 of 10"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Spacer1" type="Control" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
custom_minimum_size = Vector2(0, 8)
|
||||||
|
|
||||||
|
[node name="AchievementGrid" type="GridContainer" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/h_separation = 6
|
||||||
|
theme_override_constants/v_separation = 6
|
||||||
|
columns = 5
|
||||||
|
|
||||||
|
[node name="Spacer2" type="Control" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
custom_minimum_size = Vector2(0, 8)
|
||||||
|
|
||||||
|
[node name="InfoPanel" type="PanelContainer" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(0, 72)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_info")
|
||||||
|
|
||||||
|
[node name="InfoVBox" type="VBoxContainer" parent="VBoxContainer/InfoPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 2
|
||||||
|
|
||||||
|
[node name="InfoTitle" type="Label" parent="VBoxContainer/InfoPanel/InfoVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("3_ach")
|
||||||
|
theme_override_font_sizes/font_size = 20
|
||||||
|
theme_override_colors/font_color = Color(1, 1, 0.55, 1)
|
||||||
|
|
||||||
|
[node name="InfoDesc" type="Label" parent="VBoxContainer/InfoPanel/InfoVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("3_ach")
|
||||||
|
theme_override_font_sizes/font_size = 16
|
||||||
|
theme_override_colors/font_color = Color(0.9, 0.85, 0.75, 1)
|
||||||
|
text = "Hover over an achievement to see details."
|
||||||
|
|
||||||
|
[node name="InfoReward" type="Label" parent="VBoxContainer/InfoPanel/InfoVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("3_ach")
|
||||||
|
theme_override_font_sizes/font_size = 16
|
||||||
|
theme_override_colors/font_color = Color(0.75, 0.45, 1.0, 1)
|
||||||
|
|
||||||
|
[node name="Spacer3" type="Control" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
custom_minimum_size = Vector2(0, 8)
|
||||||
|
|
||||||
|
[node name="BackButton" type="Button" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(250, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 0.95, 0.8, 1)
|
||||||
|
theme_override_colors/font_hover_color = Color(1, 1, 0.55, 1)
|
||||||
|
theme_override_colors/font_pressed_color = Color(1, 1, 0.75, 1)
|
||||||
|
theme_override_colors/font_focus_color = Color(1, 0.95, 0.8, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("3_ach")
|
||||||
|
theme_override_font_sizes/font_size = 32
|
||||||
|
theme_override_styles/normal = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover = SubResource("StyleBoxFlat_hover")
|
||||||
|
theme_override_styles/pressed = SubResource("StyleBoxFlat_pressed")
|
||||||
|
theme_override_styles/focus = SubResource("StyleBoxEmpty_focus")
|
||||||
|
theme_override_styles/disabled = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_pressed")
|
||||||
|
text = "Back"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="VBoxContainer/BackButton" to="." method="_on_back_button_pressed"]
|
||||||
|
|
@ -10,12 +10,15 @@ func _ready():
|
||||||
MusicManager.play(preload("res://assets/music&sfx/music/ashveil-355633.mp3"))
|
MusicManager.play(preload("res://assets/music&sfx/music/ashveil-355633.mp3"))
|
||||||
add_child(_debug_menu_script.new())
|
add_child(_debug_menu_script.new())
|
||||||
$World/Witch.died.connect(_on_witch_died)
|
$World/Witch.died.connect(_on_witch_died)
|
||||||
|
StatsManager.start_run()
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
var t = int(_spawn_control.elapsed_time)
|
var t = int(_spawn_control.elapsed_time)
|
||||||
_timer_label.text = "%02d:%02d" % [t / 60, t % 60]
|
_timer_label.text = "%02d:%02d" % [t / 60, t % 60]
|
||||||
|
|
||||||
func _on_witch_died(killer) -> void:
|
func _on_witch_died(killer) -> void:
|
||||||
|
StatsManager.end_run(true)
|
||||||
|
AchievementManager.check_all()
|
||||||
var witch = $World/Witch
|
var witch = $World/Witch
|
||||||
var player = $World/Player
|
var player = $World/Player
|
||||||
$PauseMenu.process_mode = Node.PROCESS_MODE_DISABLED
|
$PauseMenu.process_mode = Node.PROCESS_MODE_DISABLED
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,40 @@ theme_override_styles/disabled = SubResource("StyleBoxEmpty_normal")
|
||||||
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_pressed")
|
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_pressed")
|
||||||
text = "Play"
|
text = "Play"
|
||||||
|
|
||||||
|
[node name="AchievementsButton" type="Button" parent="VBoxContainer" unique_id=444444444]
|
||||||
|
custom_minimum_size = Vector2(250, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 0.95, 0.8, 1)
|
||||||
|
theme_override_colors/font_hover_color = Color(1, 1, 0.55, 1)
|
||||||
|
theme_override_colors/font_pressed_color = Color(1, 1, 0.75, 1)
|
||||||
|
theme_override_colors/font_focus_color = Color(1, 0.95, 0.8, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("4_3vvtq")
|
||||||
|
theme_override_font_sizes/font_size = 32
|
||||||
|
theme_override_styles/normal = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover = SubResource("StyleBoxFlat_hover")
|
||||||
|
theme_override_styles/pressed = SubResource("StyleBoxFlat_pressed")
|
||||||
|
theme_override_styles/focus = SubResource("StyleBoxEmpty_focus")
|
||||||
|
theme_override_styles/disabled = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_pressed")
|
||||||
|
text = "Achievements"
|
||||||
|
|
||||||
|
[node name="StatsButton" type="Button" parent="VBoxContainer" unique_id=333333333]
|
||||||
|
custom_minimum_size = Vector2(250, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 0.95, 0.8, 1)
|
||||||
|
theme_override_colors/font_hover_color = Color(1, 1, 0.55, 1)
|
||||||
|
theme_override_colors/font_pressed_color = Color(1, 1, 0.75, 1)
|
||||||
|
theme_override_colors/font_focus_color = Color(1, 0.95, 0.8, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("4_3vvtq")
|
||||||
|
theme_override_font_sizes/font_size = 32
|
||||||
|
theme_override_styles/normal = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover = SubResource("StyleBoxFlat_hover")
|
||||||
|
theme_override_styles/pressed = SubResource("StyleBoxFlat_pressed")
|
||||||
|
theme_override_styles/focus = SubResource("StyleBoxEmpty_focus")
|
||||||
|
theme_override_styles/disabled = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_pressed")
|
||||||
|
text = "Stats"
|
||||||
|
|
||||||
[node name="OptionsButton" type="Button" parent="VBoxContainer" unique_id=1460263213]
|
[node name="OptionsButton" type="Button" parent="VBoxContainer" unique_id=1460263213]
|
||||||
custom_minimum_size = Vector2(250, 50)
|
custom_minimum_size = Vector2(250, 50)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
@ -170,4 +204,6 @@ text = "Quit"
|
||||||
[connection signal="pressed" from="VBoxContainer/PlayButton" to="." method="_on_play_button_pressed"]
|
[connection signal="pressed" from="VBoxContainer/PlayButton" to="." method="_on_play_button_pressed"]
|
||||||
[connection signal="pressed" from="VBoxContainer/OptionsButton" to="." method="_on_options_button_pressed"]
|
[connection signal="pressed" from="VBoxContainer/OptionsButton" to="." method="_on_options_button_pressed"]
|
||||||
[connection signal="pressed" from="VBoxContainer/CreditsButton" to="." method="_on_credits_button_pressed"]
|
[connection signal="pressed" from="VBoxContainer/CreditsButton" to="." method="_on_credits_button_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/AchievementsButton" to="." method="_on_achievements_button_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/StatsButton" to="." method="_on_stats_button_pressed"]
|
||||||
[connection signal="pressed" from="VBoxContainer/QuitButton" to="." method="_on_quit_button_pressed"]
|
[connection signal="pressed" from="VBoxContainer/QuitButton" to="." method="_on_quit_button_pressed"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/stats_screen.gd" id="1_stats"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://2ddggeetjj1t" path="res://assets/images/screenshot_game.png" id="2_stats"]
|
||||||
|
[ext_resource type="FontFile" uid="uid://8v71dcws4q6o" path="res://assets/fonts/slkscre.ttf" id="3_stats"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_normal"]
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hover"]
|
||||||
|
bg_color = Color(0.45, 0.1, 0.7, 0.35)
|
||||||
|
border_width_left = 2
|
||||||
|
border_width_top = 2
|
||||||
|
border_width_right = 2
|
||||||
|
border_width_bottom = 2
|
||||||
|
border_color = Color(0.75, 0.45, 1.0, 0.75)
|
||||||
|
corner_radius_top_left = 3
|
||||||
|
corner_radius_top_right = 3
|
||||||
|
corner_radius_bottom_right = 3
|
||||||
|
corner_radius_bottom_left = 3
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pressed"]
|
||||||
|
bg_color = Color(0.3, 0.05, 0.5, 0.55)
|
||||||
|
border_width_left = 2
|
||||||
|
border_width_top = 2
|
||||||
|
border_width_right = 2
|
||||||
|
border_width_bottom = 2
|
||||||
|
border_color = Color(0.9, 0.6, 1.0, 1.0)
|
||||||
|
corner_radius_top_left = 3
|
||||||
|
corner_radius_top_right = 3
|
||||||
|
corner_radius_bottom_right = 3
|
||||||
|
corner_radius_bottom_left = 3
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_focus"]
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_reset_hover"]
|
||||||
|
bg_color = Color(0.5, 0.04, 0.04, 0.4)
|
||||||
|
border_width_left = 2
|
||||||
|
border_width_top = 2
|
||||||
|
border_width_right = 2
|
||||||
|
border_width_bottom = 2
|
||||||
|
border_color = Color(1.0, 0.3, 0.3, 0.75)
|
||||||
|
corner_radius_top_left = 3
|
||||||
|
corner_radius_top_right = 3
|
||||||
|
corner_radius_bottom_right = 3
|
||||||
|
corner_radius_bottom_left = 3
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_reset_pressed"]
|
||||||
|
bg_color = Color(0.35, 0.02, 0.02, 0.6)
|
||||||
|
border_width_left = 2
|
||||||
|
border_width_top = 2
|
||||||
|
border_width_right = 2
|
||||||
|
border_width_bottom = 2
|
||||||
|
border_color = Color(1.0, 0.5, 0.5, 1.0)
|
||||||
|
corner_radius_top_left = 3
|
||||||
|
corner_radius_top_right = 3
|
||||||
|
corner_radius_bottom_right = 3
|
||||||
|
corner_radius_bottom_left = 3
|
||||||
|
content_margin_left = 10.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 10.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[node name="StatsScreen" type="CanvasLayer"]
|
||||||
|
script = ExtResource("1_stats")
|
||||||
|
|
||||||
|
[node name="BackgroundSprite" type="TextureRect" parent="."]
|
||||||
|
z_index = -2
|
||||||
|
offset_left = 4.0
|
||||||
|
offset_top = 2.0
|
||||||
|
offset_right = 1277.0
|
||||||
|
offset_bottom = 718.0
|
||||||
|
texture = ExtResource("2_stats")
|
||||||
|
expand_mode = 2
|
||||||
|
|
||||||
|
[node name="BackgroundRect" type="ColorRect" parent="."]
|
||||||
|
z_index = -1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
color = Color(0, 0, 0, 0.6)
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
|
z_index = 1
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -308.5
|
||||||
|
offset_top = -330.0
|
||||||
|
offset_right = 308.5
|
||||||
|
offset_bottom = 330.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="TitleLabel" type="Label" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 0.9, 0.6, 1)
|
||||||
|
theme_override_colors/font_outline_color = Color(0.4, 0.05, 0.65, 1)
|
||||||
|
theme_override_constants/outline_size = 3
|
||||||
|
theme_override_fonts/font = ExtResource("3_stats")
|
||||||
|
theme_override_font_sizes/font_size = 56
|
||||||
|
text = "Statistics"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Spacer" type="Control" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
custom_minimum_size = Vector2(0, 8)
|
||||||
|
|
||||||
|
[node name="StatsContainer" type="VBoxContainer" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Spacer2" type="Control" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
custom_minimum_size = Vector2(0, 12)
|
||||||
|
|
||||||
|
[node name="ResetButton" type="Button" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(250, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.85, 0.5, 0.5, 1)
|
||||||
|
theme_override_colors/font_hover_color = Color(1, 0.35, 0.35, 1)
|
||||||
|
theme_override_colors/font_pressed_color = Color(1, 0.55, 0.55, 1)
|
||||||
|
theme_override_colors/font_focus_color = Color(0.85, 0.5, 0.5, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("3_stats")
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
theme_override_styles/normal = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover = SubResource("StyleBoxFlat_reset_hover")
|
||||||
|
theme_override_styles/pressed = SubResource("StyleBoxFlat_reset_pressed")
|
||||||
|
theme_override_styles/focus = SubResource("StyleBoxEmpty_focus")
|
||||||
|
theme_override_styles/disabled = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_reset_pressed")
|
||||||
|
text = "Reset Stats & Achievements"
|
||||||
|
|
||||||
|
[node name="BackButton" type="Button" parent="VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(250, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 0.95, 0.8, 1)
|
||||||
|
theme_override_colors/font_hover_color = Color(1, 1, 0.55, 1)
|
||||||
|
theme_override_colors/font_pressed_color = Color(1, 1, 0.75, 1)
|
||||||
|
theme_override_colors/font_focus_color = Color(1, 0.95, 0.8, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("3_stats")
|
||||||
|
theme_override_font_sizes/font_size = 32
|
||||||
|
theme_override_styles/normal = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover = SubResource("StyleBoxFlat_hover")
|
||||||
|
theme_override_styles/pressed = SubResource("StyleBoxFlat_pressed")
|
||||||
|
theme_override_styles/focus = SubResource("StyleBoxEmpty_focus")
|
||||||
|
theme_override_styles/disabled = SubResource("StyleBoxEmpty_normal")
|
||||||
|
theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_pressed")
|
||||||
|
text = "Back"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="VBoxContainer/ResetButton" to="." method="_on_reset_button_pressed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/BackButton" to="." method="_on_back_button_pressed"]
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
const SAVE_PATH = "user://achievements.json"
|
||||||
|
const _POPUP_SCRIPT = preload("res://scripts/achievement_popup.gd")
|
||||||
|
var achievements: Array = []
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_define()
|
||||||
|
_load()
|
||||||
|
check_all()
|
||||||
|
|
||||||
|
func _define() -> void:
|
||||||
|
achievements = [
|
||||||
|
{
|
||||||
|
"id": "first_blood",
|
||||||
|
"title": "First Blood",
|
||||||
|
"description": "Kill your very first enemy.",
|
||||||
|
"condition_type": "kills_total",
|
||||||
|
"threshold": 1,
|
||||||
|
"reward": "+10 Max HP at the start of every run",
|
||||||
|
"color": Color(0.55, 0.08, 0.08),
|
||||||
|
"icon_char": "I",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "slime_slayer",
|
||||||
|
"title": "Slime Slayer",
|
||||||
|
"description": "Kill 25 Blue Slimes.",
|
||||||
|
"condition_type": "kills_blue_slime",
|
||||||
|
"threshold": 25,
|
||||||
|
"reward": "Shurikens pierce 1 additional enemy",
|
||||||
|
"color": Color(0.15, 0.2, 0.65),
|
||||||
|
"icon_char": "B",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fire_bane",
|
||||||
|
"title": "Fire Bane",
|
||||||
|
"description": "Kill 50 Fire Slimes.",
|
||||||
|
"condition_type": "kills_fire_slime",
|
||||||
|
"threshold": 50,
|
||||||
|
"reward": "Fireball permanently deals +20% more damage",
|
||||||
|
"color": Color(0.75, 0.28, 0.05),
|
||||||
|
"icon_char": "F",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "apprentice",
|
||||||
|
"title": "Apprentice",
|
||||||
|
"description": "Cast 50 spells in total.",
|
||||||
|
"condition_type": "spells_total",
|
||||||
|
"threshold": 50,
|
||||||
|
"reward": "Cauldron brews with only 2 fruits instead of 3",
|
||||||
|
"color": Color(0.45, 0.1, 0.7),
|
||||||
|
"icon_char": "A",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "slime_hunter",
|
||||||
|
"title": "Slime Hunter",
|
||||||
|
"description": "Kill 100 enemies in total.",
|
||||||
|
"condition_type": "kills_total",
|
||||||
|
"threshold": 100,
|
||||||
|
"reward": "Each run starts with 1 random fruit already in the cauldron",
|
||||||
|
"color": Color(0.15, 0.5, 0.15),
|
||||||
|
"icon_char": "H",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "survivor",
|
||||||
|
"title": "Survivor",
|
||||||
|
"description": "Survive 3 minutes in a single run.",
|
||||||
|
"condition_type": "survive_best",
|
||||||
|
"threshold": 180,
|
||||||
|
"reward": "Regenerate 5 HP every 10 seconds",
|
||||||
|
"color": Color(0.1, 0.4, 0.65),
|
||||||
|
"icon_char": "S",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "veteran",
|
||||||
|
"title": "Veteran",
|
||||||
|
"description": "Play 15 runs.",
|
||||||
|
"condition_type": "runs_played",
|
||||||
|
"threshold": 15,
|
||||||
|
"reward": "Start each run with 1 random perk already applied",
|
||||||
|
"color": Color(0.6, 0.45, 0.05),
|
||||||
|
"icon_char": "V",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "archmage",
|
||||||
|
"title": "Archmage",
|
||||||
|
"description": "Cast 300 spells in total.",
|
||||||
|
"condition_type": "spells_total",
|
||||||
|
"threshold": 300,
|
||||||
|
"reward": "All spells permanently deal +15% more damage",
|
||||||
|
"color": Color(0.25, 0.05, 0.5),
|
||||||
|
"icon_char": "M",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "iron_witch",
|
||||||
|
"title": "Iron Witch",
|
||||||
|
"description": "Survive 7 minutes in a single run.",
|
||||||
|
"condition_type": "survive_best",
|
||||||
|
"threshold": 420,
|
||||||
|
"reward": "+25 Max HP permanently",
|
||||||
|
"color": Color(0.25, 0.35, 0.5),
|
||||||
|
"icon_char": "W",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "exterminator",
|
||||||
|
"title": "Exterminator",
|
||||||
|
"description": "Kill 500 enemies in total.",
|
||||||
|
"condition_type": "kills_total",
|
||||||
|
"threshold": 500,
|
||||||
|
"reward": "Level-up screen shows 4 perk cards instead of 3",
|
||||||
|
"color": Color(0.65, 0.05, 0.05),
|
||||||
|
"icon_char": "X",
|
||||||
|
"unlocked": false
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
func check_all() -> void:
|
||||||
|
var newly_unlocked: Array = []
|
||||||
|
for ach in achievements:
|
||||||
|
if ach.unlocked:
|
||||||
|
continue
|
||||||
|
if _meets(ach):
|
||||||
|
ach.unlocked = true
|
||||||
|
newly_unlocked.append(ach)
|
||||||
|
if not newly_unlocked.is_empty():
|
||||||
|
_save()
|
||||||
|
for ach in newly_unlocked:
|
||||||
|
_show_popup(ach)
|
||||||
|
|
||||||
|
func _show_popup(ach: Dictionary) -> void:
|
||||||
|
var popup = _POPUP_SCRIPT.new()
|
||||||
|
get_tree().root.add_child(popup)
|
||||||
|
popup.show_unlock(ach)
|
||||||
|
|
||||||
|
func _meets(ach: Dictionary) -> bool:
|
||||||
|
var d = StatsManager.data
|
||||||
|
var t: int = ach.threshold
|
||||||
|
match ach.condition_type:
|
||||||
|
"kills_total":
|
||||||
|
var total = 0
|
||||||
|
for v in d["kills"].values():
|
||||||
|
total += v
|
||||||
|
return total >= t
|
||||||
|
"kills_blue_slime":
|
||||||
|
return d["kills"]["blue_slime"] >= t
|
||||||
|
"kills_fire_slime":
|
||||||
|
return d["kills"]["fire_slime"] >= t
|
||||||
|
"spells_total":
|
||||||
|
var total = 0
|
||||||
|
for v in d["spells_cast"].values():
|
||||||
|
total += v
|
||||||
|
return total >= t
|
||||||
|
"survive_best":
|
||||||
|
return d["longest_run_seconds"] >= t
|
||||||
|
"runs_played":
|
||||||
|
return d["runs_played"] >= t
|
||||||
|
return false
|
||||||
|
|
||||||
|
func reset() -> void:
|
||||||
|
for ach in achievements:
|
||||||
|
ach.unlocked = false
|
||||||
|
_save()
|
||||||
|
|
||||||
|
func get_unlocked_count() -> int:
|
||||||
|
var n = 0
|
||||||
|
for ach in achievements:
|
||||||
|
if ach.unlocked:
|
||||||
|
n += 1
|
||||||
|
return n
|
||||||
|
|
||||||
|
func _save() -> void:
|
||||||
|
var ids: Array = []
|
||||||
|
for ach in achievements:
|
||||||
|
if ach.unlocked:
|
||||||
|
ids.append(ach.id)
|
||||||
|
var f = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
|
||||||
|
if f == null:
|
||||||
|
return
|
||||||
|
f.store_string(JSON.stringify(ids))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
func _load() -> void:
|
||||||
|
if not FileAccess.file_exists(SAVE_PATH):
|
||||||
|
return
|
||||||
|
var f = FileAccess.open(SAVE_PATH, FileAccess.READ)
|
||||||
|
if f == null:
|
||||||
|
return
|
||||||
|
var j = JSON.new()
|
||||||
|
var text = f.get_as_text()
|
||||||
|
f.close()
|
||||||
|
if j.parse(text) != OK:
|
||||||
|
return
|
||||||
|
var ids = j.get_data()
|
||||||
|
if typeof(ids) != TYPE_ARRAY:
|
||||||
|
return
|
||||||
|
for ach in achievements:
|
||||||
|
ach.unlocked = ach.id in ids
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
uid://doym1fo4mfdxp
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
const _VP_W := 1280.0
|
||||||
|
const _VP_H := 720.0
|
||||||
|
const _MARGIN := 16.0
|
||||||
|
const _SFX_PATH := "res://assets/music&sfx/sfx/achievement_unlock.wav"
|
||||||
|
|
||||||
|
var _font = preload("res://assets/fonts/slkscre.ttf")
|
||||||
|
var _panel: PanelContainer
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
layer = 100
|
||||||
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||||
|
|
||||||
|
_panel = PanelContainer.new()
|
||||||
|
_panel.custom_minimum_size = Vector2(310.0, 76.0)
|
||||||
|
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
|
||||||
|
var style := StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color(0.04, 0.04, 0.12, 0.96)
|
||||||
|
style.border_width_left = 2
|
||||||
|
style.border_width_top = 2
|
||||||
|
style.border_width_right = 2
|
||||||
|
style.border_width_bottom = 2
|
||||||
|
style.border_color = Color(0.78, 0.62, 0.18, 1.0)
|
||||||
|
style.corner_radius_top_left = 3
|
||||||
|
style.corner_radius_top_right = 3
|
||||||
|
style.corner_radius_bottom_right = 3
|
||||||
|
style.corner_radius_bottom_left = 3
|
||||||
|
style.content_margin_left = 8.0
|
||||||
|
style.content_margin_top = 8.0
|
||||||
|
style.content_margin_right = 12.0
|
||||||
|
style.content_margin_bottom = 8.0
|
||||||
|
_panel.add_theme_stylebox_override("panel", style)
|
||||||
|
|
||||||
|
_panel.position = Vector2(_VP_W + 8.0, _VP_H - 76.0 - _MARGIN)
|
||||||
|
add_child(_panel)
|
||||||
|
|
||||||
|
|
||||||
|
func show_unlock(ach: Dictionary) -> void:
|
||||||
|
var hbox := HBoxContainer.new()
|
||||||
|
hbox.add_theme_constant_override("separation", 10)
|
||||||
|
hbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
_panel.add_child(hbox)
|
||||||
|
|
||||||
|
# Icon square
|
||||||
|
var icon_wrap := Control.new()
|
||||||
|
icon_wrap.custom_minimum_size = Vector2(52, 52)
|
||||||
|
icon_wrap.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
hbox.add_child(icon_wrap)
|
||||||
|
|
||||||
|
var icon_bg := ColorRect.new()
|
||||||
|
icon_bg.color = ach.color
|
||||||
|
icon_bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
icon_bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
icon_wrap.add_child(icon_bg)
|
||||||
|
|
||||||
|
var icon_lbl := Label.new()
|
||||||
|
icon_lbl.text = ach.icon_char
|
||||||
|
icon_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
icon_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
icon_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
icon_lbl.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
icon_lbl.add_theme_font_override("font", _font)
|
||||||
|
icon_lbl.add_theme_font_size_override("font_size", 22)
|
||||||
|
icon_lbl.add_theme_color_override("font_color", Color(1.0, 1.0, 0.88))
|
||||||
|
icon_lbl.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.8))
|
||||||
|
icon_lbl.add_theme_constant_override("outline_size", 2)
|
||||||
|
icon_wrap.add_child(icon_lbl)
|
||||||
|
|
||||||
|
# Text block
|
||||||
|
var vbox := VBoxContainer.new()
|
||||||
|
vbox.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||||
|
vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
hbox.add_child(vbox)
|
||||||
|
|
||||||
|
var header := Label.new()
|
||||||
|
header.text = "Achievement Unlocked!"
|
||||||
|
header.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
header.add_theme_font_override("font", _font)
|
||||||
|
header.add_theme_font_size_override("font_size", 13)
|
||||||
|
header.add_theme_color_override("font_color", Color(0.78, 0.62, 0.18, 1.0))
|
||||||
|
vbox.add_child(header)
|
||||||
|
|
||||||
|
var title_lbl := Label.new()
|
||||||
|
title_lbl.text = ach.title
|
||||||
|
title_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
title_lbl.add_theme_font_override("font", _font)
|
||||||
|
title_lbl.add_theme_font_size_override("font_size", 20)
|
||||||
|
title_lbl.add_theme_color_override("font_color", Color(1.0, 0.95, 0.8, 1.0))
|
||||||
|
vbox.add_child(title_lbl)
|
||||||
|
|
||||||
|
_play_sound()
|
||||||
|
# Wait one frame so the panel has its final size before animating
|
||||||
|
await get_tree().process_frame
|
||||||
|
_animate()
|
||||||
|
|
||||||
|
|
||||||
|
func _play_sound() -> void:
|
||||||
|
if not ResourceLoader.exists(_SFX_PATH):
|
||||||
|
return
|
||||||
|
var sfx := AudioStreamPlayer.new()
|
||||||
|
sfx.stream = load(_SFX_PATH)
|
||||||
|
sfx.volume_db = -2.0
|
||||||
|
sfx.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||||
|
add_child(sfx)
|
||||||
|
sfx.play()
|
||||||
|
sfx.finished.connect(sfx.queue_free)
|
||||||
|
|
||||||
|
func _animate() -> void:
|
||||||
|
var pw := _panel.size.x
|
||||||
|
var target_x := _VP_W - pw - _MARGIN
|
||||||
|
_panel.position.x = _VP_W + 8.0
|
||||||
|
|
||||||
|
var tween := create_tween()
|
||||||
|
tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS)
|
||||||
|
tween.tween_property(_panel, "position:x", target_x, 0.4)\
|
||||||
|
.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_QUART)
|
||||||
|
tween.tween_interval(3.5)
|
||||||
|
tween.tween_property(_panel, "position:x", _VP_W + 8.0, 0.3)\
|
||||||
|
.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_QUART)
|
||||||
|
tween.tween_callback(queue_free)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dsitid33q7lcx
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
@onready var _grid: GridContainer = $VBoxContainer/AchievementGrid
|
||||||
|
@onready var _counter: Label = $VBoxContainer/CounterLabel
|
||||||
|
@onready var _info_title: Label = $VBoxContainer/InfoPanel/InfoVBox/InfoTitle
|
||||||
|
@onready var _info_desc: Label = $VBoxContainer/InfoPanel/InfoVBox/InfoDesc
|
||||||
|
@onready var _info_reward: Label = $VBoxContainer/InfoPanel/InfoVBox/InfoReward
|
||||||
|
|
||||||
|
var _font = preload("res://assets/fonts/slkscre.ttf")
|
||||||
|
var _frame_tex = preload("res://assets/Tiny RPG Mana Soul GUI v1.0/20250420manaSoul9SlicesA-Sheet.png")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
AchievementManager.check_all()
|
||||||
|
_update_counter()
|
||||||
|
_build_grid()
|
||||||
|
_clear_info()
|
||||||
|
|
||||||
|
func _build_grid() -> void:
|
||||||
|
for ach in AchievementManager.achievements:
|
||||||
|
_grid.add_child(_make_slot(ach))
|
||||||
|
|
||||||
|
func _make_slot(ach: Dictionary) -> Control:
|
||||||
|
var slot := Control.new()
|
||||||
|
slot.custom_minimum_size = Vector2(80, 80)
|
||||||
|
slot.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
|
|
||||||
|
var bg := ColorRect.new()
|
||||||
|
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
bg.color = ach.color if ach.unlocked else Color(0.06, 0.06, 0.1, 1.0)
|
||||||
|
slot.add_child(bg)
|
||||||
|
|
||||||
|
var frame := NinePatchRect.new()
|
||||||
|
frame.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
frame.texture = _frame_tex
|
||||||
|
frame.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
frame.patch_margin_left = 8
|
||||||
|
frame.patch_margin_right = 8
|
||||||
|
frame.patch_margin_top = 8
|
||||||
|
frame.patch_margin_bottom = 8
|
||||||
|
if not ach.unlocked:
|
||||||
|
frame.modulate = Color(0.4, 0.4, 0.4, 1.0)
|
||||||
|
slot.add_child(frame)
|
||||||
|
|
||||||
|
var lbl := Label.new()
|
||||||
|
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
lbl.text = ach.icon_char
|
||||||
|
lbl.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
lbl.add_theme_font_override("font", _font)
|
||||||
|
lbl.add_theme_font_size_override("font_size", 26)
|
||||||
|
if ach.unlocked:
|
||||||
|
lbl.add_theme_color_override("font_color", Color(1.0, 1.0, 0.9, 1.0))
|
||||||
|
lbl.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.9))
|
||||||
|
lbl.add_theme_constant_override("outline_size", 2)
|
||||||
|
else:
|
||||||
|
lbl.add_theme_color_override("font_color", Color(0.22, 0.22, 0.22, 1.0))
|
||||||
|
slot.add_child(lbl)
|
||||||
|
|
||||||
|
slot.mouse_entered.connect(_on_slot_enter.bind(ach))
|
||||||
|
slot.mouse_exited.connect(_on_slot_exit)
|
||||||
|
return slot
|
||||||
|
|
||||||
|
func _on_slot_enter(ach: Dictionary) -> void:
|
||||||
|
if ach.unlocked:
|
||||||
|
_info_title.text = ach.title + " [UNLOCKED]"
|
||||||
|
_info_title.add_theme_color_override("font_color", Color(1.0, 1.0, 0.55, 1.0))
|
||||||
|
else:
|
||||||
|
_info_title.text = ach.title + " [LOCKED]"
|
||||||
|
_info_title.add_theme_color_override("font_color", Color(0.75, 0.45, 1.0, 1.0))
|
||||||
|
_info_desc.text = ach.description
|
||||||
|
_info_reward.text = "Reward: " + ach.reward
|
||||||
|
|
||||||
|
func _on_slot_exit() -> void:
|
||||||
|
_clear_info()
|
||||||
|
|
||||||
|
func _clear_info() -> void:
|
||||||
|
_info_title.text = ""
|
||||||
|
_info_desc.text = "Hover over an achievement to see details."
|
||||||
|
_info_reward.text = ""
|
||||||
|
|
||||||
|
func _update_counter() -> void:
|
||||||
|
var n := AchievementManager.get_unlocked_count()
|
||||||
|
var total := AchievementManager.achievements.size()
|
||||||
|
_counter.text = "Unlocked: %d of %d" % [n, total]
|
||||||
|
|
||||||
|
func _on_back_button_pressed() -> void:
|
||||||
|
get_tree().change_scene_to_file("res://scenes/mainmenu.tscn")
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
uid://yv14n6gesnt5
|
||||||
|
|
@ -2,6 +2,7 @@ extends EnemyBase
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
super()
|
super()
|
||||||
|
enemy_type = "blue_slime"
|
||||||
speed = 15.0
|
speed = 15.0
|
||||||
max_hp = 20
|
max_hp = 20
|
||||||
hp = max_hp
|
hp = max_hp
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ extends CharacterBody2D
|
||||||
|
|
||||||
@export var max_hp: int = 10
|
@export var max_hp: int = 10
|
||||||
|
|
||||||
|
var enemy_type: String = "slime"
|
||||||
var is_dying = false
|
var is_dying = false
|
||||||
var is_hurt = false
|
var is_hurt = false
|
||||||
var hp: int
|
var hp: int
|
||||||
|
|
@ -49,6 +50,7 @@ func die():
|
||||||
animated_sprite_2d.sprite_frames.set_animation_loop(death_anim, false)
|
animated_sprite_2d.sprite_frames.set_animation_loop(death_anim, false)
|
||||||
animated_sprite_2d.play(death_anim)
|
animated_sprite_2d.play(death_anim)
|
||||||
_play_hit_sound()
|
_play_hit_sound()
|
||||||
|
StatsManager.on_kill(enemy_type)
|
||||||
died.emit(self)
|
died.emit(self)
|
||||||
await animated_sprite_2d.animation_finished
|
await animated_sprite_2d.animation_finished
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ extends EnemyBase
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
super()
|
super()
|
||||||
|
enemy_type = "fire_slime"
|
||||||
speed = 15.0
|
speed = 15.0
|
||||||
max_hp = 25
|
max_hp = 25
|
||||||
hp = max_hp
|
hp = max_hp
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,11 @@ func _on_options_button_pressed() -> void:
|
||||||
func _on_credits_button_pressed() -> void:
|
func _on_credits_button_pressed() -> void:
|
||||||
get_tree().change_scene_to_file("res://scenes/credits.tscn")
|
get_tree().change_scene_to_file("res://scenes/credits.tscn")
|
||||||
|
|
||||||
|
func _on_achievements_button_pressed() -> void:
|
||||||
|
get_tree().change_scene_to_file("res://scenes/achievements_screen.tscn")
|
||||||
|
|
||||||
|
func _on_stats_button_pressed() -> void:
|
||||||
|
get_tree().change_scene_to_file("res://scenes/stats_screen.tscn")
|
||||||
|
|
||||||
func _on_quit_button_pressed() -> void:
|
func _on_quit_button_pressed() -> void:
|
||||||
get_tree().quit()
|
get_tree().quit()
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,10 @@ func _on_continue_button_pressed() -> void:
|
||||||
toggle_pause()
|
toggle_pause()
|
||||||
|
|
||||||
func _on_quit_button_pressed() -> void:
|
func _on_quit_button_pressed() -> void:
|
||||||
get_tree().quit()
|
StatsManager.end_run(false)
|
||||||
|
AchievementManager.check_all()
|
||||||
|
get_tree().paused = false
|
||||||
|
get_tree().change_scene_to_file("res://scenes/mainmenu.tscn")
|
||||||
|
|
||||||
|
|
||||||
func _on_options_button_pressed() -> void:
|
func _on_options_button_pressed() -> void:
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,7 @@ func knife_cooldown_upgrade():
|
||||||
available_perks.append(upg)
|
available_perks.append(upg)
|
||||||
|
|
||||||
func _shoot_throwing_knife() -> void:
|
func _shoot_throwing_knife() -> void:
|
||||||
|
StatsManager.on_spell_cast("throwing_knife")
|
||||||
for i in range(throwing_knife_count):
|
for i in range(throwing_knife_count):
|
||||||
var knife = throwing_knife.instantiate()
|
var knife = throwing_knife.instantiate()
|
||||||
knife.global_position = witch.global_position
|
knife.global_position = witch.global_position
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
const SAVE_PATH = "user://stats.json"
|
||||||
|
|
||||||
|
var _session_start_ms: int = 0
|
||||||
|
var _run_active: bool = false
|
||||||
|
|
||||||
|
var data: Dictionary = {
|
||||||
|
"runs_played": 0,
|
||||||
|
"total_deaths": 0,
|
||||||
|
"longest_run_seconds": 0.0,
|
||||||
|
"total_survive_seconds": 0.0,
|
||||||
|
"kills": {
|
||||||
|
"slime": 0,
|
||||||
|
"blue_slime": 0,
|
||||||
|
"fire_slime": 0
|
||||||
|
},
|
||||||
|
"spells_cast": {
|
||||||
|
"fireball": 0,
|
||||||
|
"shuriken": 0,
|
||||||
|
"laser": 0,
|
||||||
|
"fire_swirl": 0,
|
||||||
|
"tornado": 0,
|
||||||
|
"purge": 0,
|
||||||
|
"throwing_knife": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_load()
|
||||||
|
|
||||||
|
func start_run() -> void:
|
||||||
|
_session_start_ms = Time.get_ticks_msec()
|
||||||
|
_run_active = true
|
||||||
|
data["runs_played"] += 1
|
||||||
|
_save()
|
||||||
|
|
||||||
|
func end_run(died: bool) -> void:
|
||||||
|
if not _run_active:
|
||||||
|
return
|
||||||
|
_run_active = false
|
||||||
|
var survived_sec = (Time.get_ticks_msec() - _session_start_ms) / 1000.0
|
||||||
|
if died:
|
||||||
|
data["total_deaths"] += 1
|
||||||
|
data["total_survive_seconds"] += survived_sec
|
||||||
|
if survived_sec > data["longest_run_seconds"]:
|
||||||
|
data["longest_run_seconds"] = survived_sec
|
||||||
|
_save()
|
||||||
|
|
||||||
|
func on_kill(enemy_type: String) -> void:
|
||||||
|
if data["kills"].has(enemy_type):
|
||||||
|
data["kills"][enemy_type] += 1
|
||||||
|
AchievementManager.call_deferred("check_all")
|
||||||
|
|
||||||
|
func on_spell_cast(spell_type: String) -> void:
|
||||||
|
if data["spells_cast"].has(spell_type):
|
||||||
|
data["spells_cast"][spell_type] += 1
|
||||||
|
AchievementManager.call_deferred("check_all")
|
||||||
|
|
||||||
|
func reset() -> void:
|
||||||
|
data["runs_played"] = 0
|
||||||
|
data["total_deaths"] = 0
|
||||||
|
data["longest_run_seconds"] = 0.0
|
||||||
|
data["total_survive_seconds"] = 0.0
|
||||||
|
for key in data["kills"]:
|
||||||
|
data["kills"][key] = 0
|
||||||
|
for key in data["spells_cast"]:
|
||||||
|
data["spells_cast"][key] = 0
|
||||||
|
_save()
|
||||||
|
|
||||||
|
func _save() -> void:
|
||||||
|
var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
|
||||||
|
if file == null:
|
||||||
|
return
|
||||||
|
file.store_string(JSON.stringify(data, "\t"))
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
func _load() -> void:
|
||||||
|
if not FileAccess.file_exists(SAVE_PATH):
|
||||||
|
return
|
||||||
|
var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
|
||||||
|
if file == null:
|
||||||
|
return
|
||||||
|
var json = JSON.new()
|
||||||
|
var err = json.parse(file.get_as_text())
|
||||||
|
file.close()
|
||||||
|
if err != OK:
|
||||||
|
return
|
||||||
|
var loaded = json.get_data()
|
||||||
|
if typeof(loaded) != TYPE_DICTIONARY:
|
||||||
|
return
|
||||||
|
for key in loaded:
|
||||||
|
if not data.has(key):
|
||||||
|
continue
|
||||||
|
if typeof(data[key]) == TYPE_DICTIONARY and typeof(loaded[key]) == TYPE_DICTIONARY:
|
||||||
|
for sub in loaded[key]:
|
||||||
|
if data[key].has(sub):
|
||||||
|
data[key][sub] = loaded[key][sub]
|
||||||
|
else:
|
||||||
|
data[key] = loaded[key]
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
uid://vrgjw34wuhnj
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
@onready var _container: VBoxContainer = $VBoxContainer/StatsContainer
|
||||||
|
var _font = preload("res://assets/fonts/slkscre.ttf")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_build_stats()
|
||||||
|
|
||||||
|
func _build_stats() -> void:
|
||||||
|
var d = StatsManager.data
|
||||||
|
|
||||||
|
_add_section("General")
|
||||||
|
_add_row("Runs Played", str(d["runs_played"]))
|
||||||
|
_add_row("Deaths", str(d["total_deaths"]))
|
||||||
|
_add_row("Total Time Survived", _fmt_time(d["total_survive_seconds"]))
|
||||||
|
_add_row("Longest Run", _fmt_time(d["longest_run_seconds"]))
|
||||||
|
|
||||||
|
_add_gap()
|
||||||
|
_add_section("Enemies Killed")
|
||||||
|
var kill_names = {"slime": "Slime", "blue_slime": "Blue Slime", "fire_slime": "Fire Slime"}
|
||||||
|
var total_kills = 0
|
||||||
|
for key in kill_names:
|
||||||
|
var n: int = d["kills"][key]
|
||||||
|
_add_row(kill_names[key], str(n))
|
||||||
|
total_kills += n
|
||||||
|
_add_row("Total", str(total_kills))
|
||||||
|
|
||||||
|
_add_gap()
|
||||||
|
_add_section("Spells Cast")
|
||||||
|
var spell_names = {
|
||||||
|
"fireball": "Fireball",
|
||||||
|
"shuriken": "Shuriken",
|
||||||
|
"laser": "Laser",
|
||||||
|
"fire_swirl": "Fire Swirl",
|
||||||
|
"tornado": "Tornado",
|
||||||
|
"purge": "Purge",
|
||||||
|
"throwing_knife": "Throwing Knife"
|
||||||
|
}
|
||||||
|
var total_spells = 0
|
||||||
|
for key in spell_names:
|
||||||
|
var n: int = d["spells_cast"][key]
|
||||||
|
_add_row(spell_names[key], str(n))
|
||||||
|
total_spells += n
|
||||||
|
_add_row("Total", str(total_spells))
|
||||||
|
|
||||||
|
func _add_section(title: String) -> void:
|
||||||
|
var lbl := Label.new()
|
||||||
|
lbl.text = "- " + title + " -"
|
||||||
|
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
lbl.add_theme_font_override("font", _font)
|
||||||
|
lbl.add_theme_font_size_override("font_size", 20)
|
||||||
|
lbl.add_theme_color_override("font_color", Color(0.75, 0.45, 1.0, 1.0))
|
||||||
|
_container.add_child(lbl)
|
||||||
|
|
||||||
|
func _add_row(label: String, value: String) -> void:
|
||||||
|
var hbox := HBoxContainer.new()
|
||||||
|
hbox.add_theme_constant_override("separation", 8)
|
||||||
|
|
||||||
|
var lbl := Label.new()
|
||||||
|
lbl.text = label
|
||||||
|
lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
lbl.add_theme_font_override("font", _font)
|
||||||
|
lbl.add_theme_font_size_override("font_size", 18)
|
||||||
|
lbl.add_theme_color_override("font_color", Color(1.0, 0.95, 0.8, 1.0))
|
||||||
|
hbox.add_child(lbl)
|
||||||
|
|
||||||
|
var val := Label.new()
|
||||||
|
val.text = value
|
||||||
|
val.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
|
val.custom_minimum_size = Vector2(80, 0)
|
||||||
|
val.add_theme_font_override("font", _font)
|
||||||
|
val.add_theme_font_size_override("font_size", 18)
|
||||||
|
val.add_theme_color_override("font_color", Color(1.0, 1.0, 0.55, 1.0))
|
||||||
|
hbox.add_child(val)
|
||||||
|
|
||||||
|
_container.add_child(hbox)
|
||||||
|
|
||||||
|
func _add_gap() -> void:
|
||||||
|
var spacer := Control.new()
|
||||||
|
spacer.custom_minimum_size = Vector2(0, 8)
|
||||||
|
_container.add_child(spacer)
|
||||||
|
|
||||||
|
func _fmt_time(seconds: float) -> String:
|
||||||
|
var s := int(seconds)
|
||||||
|
var h := s / 3600
|
||||||
|
var m := (s % 3600) / 60
|
||||||
|
var sec := s % 60
|
||||||
|
if h > 0:
|
||||||
|
return "%dh %02dm %02ds" % [h, m, sec]
|
||||||
|
return "%dm %02ds" % [m, sec]
|
||||||
|
|
||||||
|
func _on_reset_button_pressed() -> void:
|
||||||
|
StatsManager.reset()
|
||||||
|
AchievementManager.reset()
|
||||||
|
get_tree().change_scene_to_file("res://scenes/stats_screen.tscn")
|
||||||
|
|
||||||
|
func _on_back_button_pressed() -> void:
|
||||||
|
get_tree().change_scene_to_file("res://scenes/mainmenu.tscn")
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
uid://ddgqwj6630ocf
|
||||||
|
|
@ -59,6 +59,7 @@ func _on_collect(DropsBase):
|
||||||
|
|
||||||
|
|
||||||
func shoot_fireballs():
|
func shoot_fireballs():
|
||||||
|
StatsManager.on_spell_cast("fireball")
|
||||||
match perk_effects.get_mode("fireball.cast"):
|
match perk_effects.get_mode("fireball.cast"):
|
||||||
"shotgun":
|
"shotgun":
|
||||||
await _cast_fireball_shotgun()
|
await _cast_fireball_shotgun()
|
||||||
|
|
@ -133,12 +134,14 @@ func _play_fire_sfx() -> void:
|
||||||
asp.finished.connect(asp.queue_free)
|
asp.finished.connect(asp.queue_free)
|
||||||
|
|
||||||
func shoot_fire_swirl():
|
func shoot_fire_swirl():
|
||||||
|
StatsManager.on_spell_cast("fire_swirl")
|
||||||
var fs = fire_swirl.instantiate()
|
var fs = fire_swirl.instantiate()
|
||||||
fs.global_position = global_position
|
fs.global_position = global_position
|
||||||
get_parent().add_child(fs)
|
get_parent().add_child(fs)
|
||||||
camera.shake(0.3, 0.8)
|
camera.shake(0.3, 0.8)
|
||||||
|
|
||||||
func shoot_shuriken():
|
func shoot_shuriken():
|
||||||
|
StatsManager.on_spell_cast("shuriken")
|
||||||
for i in range(shuriken_count):
|
for i in range(shuriken_count):
|
||||||
var sh = shuriken.instantiate()
|
var sh = shuriken.instantiate()
|
||||||
sh.global_position = global_position
|
sh.global_position = global_position
|
||||||
|
|
@ -146,6 +149,7 @@ func shoot_shuriken():
|
||||||
await get_tree().create_timer(0.2).timeout
|
await get_tree().create_timer(0.2).timeout
|
||||||
|
|
||||||
func shoot_tornado():
|
func shoot_tornado():
|
||||||
|
StatsManager.on_spell_cast("tornado")
|
||||||
var target = get_nearest_enemy(global_position)
|
var target = get_nearest_enemy(global_position)
|
||||||
var tw = tornado.instantiate()
|
var tw = tornado.instantiate()
|
||||||
tw.global_position = target.global_position if target != null else global_position
|
tw.global_position = target.global_position if target != null else global_position
|
||||||
|
|
@ -153,6 +157,7 @@ func shoot_tornado():
|
||||||
camera.shake(0.3, 0.8)
|
camera.shake(0.3, 0.8)
|
||||||
|
|
||||||
func shoot_laser():
|
func shoot_laser():
|
||||||
|
StatsManager.on_spell_cast("laser")
|
||||||
var ls = laser.instantiate()
|
var ls = laser.instantiate()
|
||||||
ls.global_position = global_position
|
ls.global_position = global_position
|
||||||
get_parent().add_child(ls)
|
get_parent().add_child(ls)
|
||||||
|
|
@ -166,6 +171,7 @@ func shoot_laser():
|
||||||
asp.finished.connect(asp.queue_free)
|
asp.finished.connect(asp.queue_free)
|
||||||
|
|
||||||
func shoot_purge():
|
func shoot_purge():
|
||||||
|
StatsManager.on_spell_cast("purge")
|
||||||
var pg = purge.instantiate()
|
var pg = purge.instantiate()
|
||||||
pg.global_position = global_position
|
pg.global_position = global_position
|
||||||
get_parent().add_child(pg)
|
get_parent().add_child(pg)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue