Initial commit
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
12
Main.tscn
Normal file
|
@ -0,0 +1,12 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://5ske2hm55rce"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://d035h7upxrw3h" path="res://theme.tres" id="1_xbn5h"]
|
||||
|
||||
[node name="Main" type="Node2D"]
|
||||
|
||||
[node name="Control" type="Control" parent="."]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
theme = ExtResource("1_xbn5h")
|
164
components/Achievements/Achievements.tscn
Normal file
|
@ -0,0 +1,164 @@
|
|||
[gd_scene load_steps=11 format=3 uid="uid://c8nsgn4idu8ay"]
|
||||
|
||||
[ext_resource type="Script" path="res://components/Achievements/achievements.gd" id="1_fj2tg"]
|
||||
[ext_resource type="Texture2D" uid="uid://dat440dg86mjl" path="res://components/Achievements/back.png" id="2_1q85v"]
|
||||
[ext_resource type="Theme" uid="uid://d035h7upxrw3h" path="res://theme.tres" id="2_p5tbu"]
|
||||
[ext_resource type="PackedScene" uid="uid://dykc1mgg5uopw" path="res://components/Cursor/MouseHandler.tscn" id="2_vij8n"]
|
||||
[ext_resource type="Texture2D" uid="uid://bypm21lqwg7g0" path="res://components/Achievements/Village 6.png" id="4_1mson"]
|
||||
[ext_resource type="Texture2D" uid="uid://ur6oiwg6ksns" path="res://components/Achievements/award.svg" id="4_7grt2"]
|
||||
[ext_resource type="Texture2D" uid="uid://dcj0xniur7dk1" path="res://components/Achievements/thumbnail-mask.png" id="6_dwjjq"]
|
||||
[ext_resource type="AudioStream" uid="uid://dxac8e02mctgp" path="res://components/Achievements/achievement.wav" id="8_74gug"]
|
||||
[ext_resource type="AudioStream" uid="uid://4m4dwlrv78l" path="res://components/Achievements/achievement-back.ogg" id="9_c3xir"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_yajw4"]
|
||||
size = Vector2(180, 32)
|
||||
|
||||
[node name="Achievements" type="Node"]
|
||||
script = ExtResource("1_fj2tg")
|
||||
|
||||
[node name="CanvasLayer" type="CanvasLayer" parent="."]
|
||||
|
||||
[node name="Popup" type="Control" parent="CanvasLayer"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -140.0
|
||||
offset_top = -42.0
|
||||
offset_right = 40.0
|
||||
offset_bottom = -12.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="MouseHandler" parent="CanvasLayer/Popup" instance=ExtResource("2_vij8n")]
|
||||
position = Vector2(62, 15)
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="CanvasLayer/Popup/MouseHandler"]
|
||||
position = Vector2(29, 0)
|
||||
shape = SubResource("RectangleShape2D_yajw4")
|
||||
|
||||
[node name="Shadow" type="TextureRect" parent="CanvasLayer/Popup"]
|
||||
modulate = Color(0, 0, 0, 1)
|
||||
show_behind_parent = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -178.0
|
||||
offset_top = -29.0
|
||||
offset_right = 2.0
|
||||
offset_bottom = 2.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
size_flags_horizontal = 8
|
||||
size_flags_vertical = 8
|
||||
theme = ExtResource("2_p5tbu")
|
||||
texture = ExtResource("2_1q85v")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="Popup" type="TextureRect" parent="CanvasLayer/Popup"]
|
||||
clip_children = 2
|
||||
layout_mode = 1
|
||||
anchors_preset = 6
|
||||
anchor_left = 1.0
|
||||
anchor_top = 0.5
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -180.0
|
||||
offset_top = -15.0
|
||||
offset_bottom = 15.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 8
|
||||
size_flags_vertical = 8
|
||||
texture = ExtResource("2_1q85v")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="Title" type="RichTextLabel" parent="CanvasLayer/Popup/Popup"]
|
||||
modulate = Color(0.752941, 0.478431, 0.129412, 1)
|
||||
layout_mode = 1
|
||||
anchors_preset = 4
|
||||
anchor_top = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = 46.0
|
||||
offset_top = -11.0
|
||||
offset_right = 217.0
|
||||
offset_bottom = 7.0
|
||||
grow_vertical = 2
|
||||
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
|
||||
theme_override_font_sizes/normal_font_size = 7
|
||||
text = "Achievement Text"
|
||||
|
||||
[node name="Body" type="RichTextLabel" parent="CanvasLayer/Popup/Popup"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 4
|
||||
anchor_top = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = 37.0
|
||||
offset_top = 2.0
|
||||
offset_right = 223.0
|
||||
offset_bottom = 18.0
|
||||
grow_vertical = 2
|
||||
theme_override_colors/default_color = Color(0.607843, 0.607843, 0.607843, 1)
|
||||
theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
|
||||
theme_override_font_sizes/normal_font_size = 7
|
||||
text = "Achievement Body"
|
||||
|
||||
[node name="AwardShadow" type="TextureRect" parent="CanvasLayer/Popup/Popup"]
|
||||
modulate = Color(0, 0, 0, 1)
|
||||
layout_mode = 0
|
||||
offset_left = 36.0
|
||||
offset_top = 6.0
|
||||
offset_right = 45.0
|
||||
offset_bottom = 15.0
|
||||
texture = ExtResource("4_7grt2")
|
||||
expand_mode = 2
|
||||
|
||||
[node name="Award" type="TextureRect" parent="CanvasLayer/Popup/Popup"]
|
||||
modulate = Color(0.752941, 0.478431, 0.129412, 1)
|
||||
layout_mode = 0
|
||||
offset_left = 34.5
|
||||
offset_top = 4.5
|
||||
offset_right = 43.5
|
||||
offset_bottom = 13.5
|
||||
texture = ExtResource("4_7grt2")
|
||||
expand_mode = 2
|
||||
|
||||
[node name="ThumbnailMask" type="TextureRect" parent="CanvasLayer/Popup"]
|
||||
clip_children = 1
|
||||
layout_mode = 1
|
||||
anchors_preset = 4
|
||||
anchor_top = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = 3.0
|
||||
offset_top = -12.0
|
||||
offset_right = 32.0
|
||||
offset_bottom = 12.0
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("6_dwjjq")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="Thumbnail" type="TextureRect" parent="CanvasLayer/Popup/ThumbnailMask"]
|
||||
layout_mode = 0
|
||||
offset_right = 29.0
|
||||
offset_bottom = 24.0
|
||||
texture = ExtResource("4_1mson")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||
stream = ExtResource("8_74gug")
|
||||
volume_db = -9.043
|
||||
|
||||
[node name="AudioStreamPlayer2" type="AudioStreamPlayer" parent="."]
|
||||
stream = ExtResource("9_c3xir")
|
||||
volume_db = -17.441
|
||||
|
||||
[connection signal="clicked" from="CanvasLayer/Popup/MouseHandler" to="." method="_on_mouse_handler_clicked"]
|
||||
[connection signal="hovered" from="CanvasLayer/Popup/MouseHandler" to="." method="_on_mouse_handler_hovered"]
|
||||
[connection signal="unhovered" from="CanvasLayer/Popup/MouseHandler" to="." method="_on_mouse_handler_unhovered"]
|
||||
|
||||
[editable path="CanvasLayer/Popup/MouseHandler"]
|
BIN
components/Achievements/Village 6.png
Normal file
After Width: | Height: | Size: 2 MiB |
34
components/Achievements/Village 6.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bypm21lqwg7g0"
|
||||
path="res://.godot/imported/Village 6.png-38dba7a44eb73e6643b934b25a9dbbf9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Achievements/Village 6.png"
|
||||
dest_files=["res://.godot/imported/Village 6.png-38dba7a44eb73e6643b934b25a9dbbf9.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
BIN
components/Achievements/achievement-back.ogg
Normal file
19
components/Achievements/achievement-back.ogg.import
Normal file
|
@ -0,0 +1,19 @@
|
|||
[remap]
|
||||
|
||||
importer="oggvorbisstr"
|
||||
type="AudioStreamOggVorbis"
|
||||
uid="uid://4m4dwlrv78l"
|
||||
path="res://.godot/imported/achievement-back.ogg-e15f114c4307f4746154a747d47f126e.oggvorbisstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Achievements/achievement-back.ogg"
|
||||
dest_files=["res://.godot/imported/achievement-back.ogg-e15f114c4307f4746154a747d47f126e.oggvorbisstr"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
BIN
components/Achievements/achievement.wav
Normal file
24
components/Achievements/achievement.wav.import
Normal file
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dxac8e02mctgp"
|
||||
path="res://.godot/imported/achievement.wav-eb6c9e09c6c75764f5cf65335ad5f16a.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Achievements/achievement.wav"
|
||||
dest_files=["res://.godot/imported/achievement.wav-eb6c9e09c6c75764f5cf65335ad5f16a.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=0
|
263
components/Achievements/achievements.gd
Normal file
|
@ -0,0 +1,263 @@
|
|||
extends Base
|
||||
## Achievement handler to check and unlock achievements
|
||||
##
|
||||
## An achievement handler that will check the available achievements defined in
|
||||
## both the game files and the mods. When called with triggers will check to see
|
||||
## if that trigger value unlocks achievements.
|
||||
|
||||
signal achievement_unlocked(achievement: String)
|
||||
|
||||
const CACHE_TIME = 60000
|
||||
const POPUP_POP_TIME = 1000
|
||||
const POPUP_LINGER_TIME = 5000
|
||||
const POPUP_HIDE_TIME = 1000
|
||||
const POPUP_BREAK = 1000
|
||||
const POPUP_HOVER_TIME = 250
|
||||
|
||||
@onready var popup = $"CanvasLayer/Popup"
|
||||
@onready var popup_title = $"CanvasLayer/Popup/Popup/Title"
|
||||
@onready var popup_body = $"CanvasLayer/Popup/Popup/Body"
|
||||
@onready var audio = $"AudioStreamPlayer"
|
||||
@onready var audio2 = $"AudioStreamPlayer2"
|
||||
|
||||
enum AchievementState {
|
||||
ENTERING,
|
||||
SHOWN,
|
||||
HOVERED,
|
||||
EXITING,
|
||||
HIDDEN
|
||||
}
|
||||
|
||||
var queue = []
|
||||
var cached_percents = {}
|
||||
var last_percent_pull: int
|
||||
var showing_popup = false
|
||||
var starting_popup_pos
|
||||
var starting_popup_size
|
||||
var popup_tween
|
||||
var popup_timer = 0
|
||||
var popup_time = 6
|
||||
#var entering = false
|
||||
#var exiting = false
|
||||
#var hovered = false
|
||||
|
||||
var achievement_state = AchievementState.HIDDEN
|
||||
var hovered = false
|
||||
|
||||
|
||||
|
||||
func _ready():
|
||||
#_log_category = "ACH"
|
||||
#_log_icon = "res://components/Logger/scroll-text.svg"
|
||||
#_log_color = "#a166a1"
|
||||
super()
|
||||
_info("Achievements is active")
|
||||
starting_popup_pos = popup.position
|
||||
starting_popup_size = popup.size
|
||||
popup.position.x = starting_popup_pos.x + starting_popup_size.x
|
||||
popup.modulate = Color.TRANSPARENT
|
||||
popup.scale = Vector2(0.5, 0.5)
|
||||
if _triggerer:
|
||||
_triggerer.listen("any", _on_trigger)
|
||||
|
||||
|
||||
func _on_trigger(data: Dictionary) -> void:
|
||||
_check_trigger(data.trigger, "Trigger")
|
||||
|
||||
|
||||
func _process(delta):
|
||||
if Persister.get_value("paused") or not Persister.get_value("active"):
|
||||
return
|
||||
popup_timer += delta
|
||||
|
||||
if popup_timer > popup_time and achievement_state == AchievementState.SHOWN:
|
||||
_exit_popup()
|
||||
|
||||
|
||||
if not queue.is_empty() and not showing_popup:
|
||||
_show_popup(queue.pop_front())
|
||||
|
||||
if popup_tween and not popup_tween.is_running():
|
||||
if hovered and achievement_state == AchievementState.SHOWN:
|
||||
achievement_state = AchievementState.HOVERED
|
||||
|
||||
popup_tween = create_tween()
|
||||
popup_tween.set_parallel()
|
||||
popup_tween.set_trans(Tween.TRANS_QUAD)
|
||||
popup_tween.tween_property(popup, "scale", Vector2(1.05, 1.05), float(POPUP_HOVER_TIME) / 1000)
|
||||
popup_tween.tween_property(popup, "position:x", starting_popup_pos.x - 20, float(POPUP_HOVER_TIME) / 1000)
|
||||
if not hovered and achievement_state == AchievementState.HOVERED:
|
||||
achievement_state = AchievementState.SHOWN
|
||||
|
||||
popup_tween = create_tween()
|
||||
popup_tween.set_parallel()
|
||||
popup_tween.set_trans(Tween.TRANS_QUAD)
|
||||
popup_tween.tween_property(popup, "scale", Vector2(1, 1), float(POPUP_HOVER_TIME) / 1000)
|
||||
popup_tween.tween_property(popup, "position:x", starting_popup_pos.x, float(POPUP_HOVER_TIME) / 1000)
|
||||
|
||||
|
||||
func _exit_popup():
|
||||
achievement_state = AchievementState.EXITING
|
||||
audio2.play()
|
||||
|
||||
if popup_tween: popup_tween.kill()
|
||||
popup_tween = create_tween()
|
||||
|
||||
popup_tween.set_ease(Tween.EASE_IN)
|
||||
popup_tween.set_trans(Tween.TRANS_BACK)
|
||||
popup_tween.set_parallel()
|
||||
popup_tween.tween_property(popup, "modulate", Color.TRANSPARENT, POPUP_POP_TIME / 1000)
|
||||
popup_tween.tween_property(popup, "scale", Vector2(0.5, 0.5), POPUP_POP_TIME / 1000)
|
||||
popup_tween.tween_property(popup, "position:x", starting_popup_pos.x + starting_popup_size.x, POPUP_HIDE_TIME / 1000)
|
||||
popup_tween.tween_interval(POPUP_BREAK / 1000)
|
||||
popup_tween.chain()
|
||||
popup_tween.tween_callback(func(): showing_popup = false)
|
||||
|
||||
_info("Hiding achievement popup")
|
||||
|
||||
|
||||
func _on_data_persisted(key: String, value, category: PersisterEnums.Scope):
|
||||
_check_trigger(key, "Data", value)
|
||||
|
||||
|
||||
## Get all of the achievement info
|
||||
func get_achievements() -> Dictionary:
|
||||
if not _data:
|
||||
if (_logger): _logger.warn("Could not find data autoload when getting achievements")
|
||||
return {}
|
||||
|
||||
if not _data.data.has("achievements"):
|
||||
if (_logger): _logger.warn("Could not find achievement data")
|
||||
return {}
|
||||
|
||||
var local_ach_data = _data.data.achievements.duplicate()
|
||||
|
||||
var percents = _get_achievement_percents()
|
||||
|
||||
for achievement in _data.data.achievements:
|
||||
if percents.keys().has(achievement):
|
||||
local_ach_data.percent = percents[achievement]
|
||||
|
||||
return _data.data.achievements
|
||||
|
||||
|
||||
## Get achievement info given the achievement slug
|
||||
func get_achievement(achievement: String) -> Dictionary:
|
||||
if not _data:
|
||||
if _logger: _logger.warn("Could not find data autoload when getting achievement %s" % [achievement])
|
||||
return {}
|
||||
|
||||
if not _data.data.has("achievements"):
|
||||
if _logger: _logger.warn("Could not find achievement data")
|
||||
return {}
|
||||
|
||||
if not _data.data.achievements.has(achievement):
|
||||
if _logger: _logger.warn("Could not find achievement %s" % [achievement])
|
||||
return {}
|
||||
|
||||
var achievement_data = _data.data.achievements[achievement].duplicate()
|
||||
|
||||
# TODO: Add in achievement percent
|
||||
|
||||
return achievement_data
|
||||
|
||||
|
||||
func _show_popup(achievement: String) -> void:
|
||||
var achievement_data = get_achievement(achievement)
|
||||
achievement_state = AchievementState.ENTERING
|
||||
popup_timer = 0
|
||||
|
||||
if achievement_data.is_empty():
|
||||
if _logger: _logger.error("Could not get achievement %s info when trying to show" % [achievement])
|
||||
return
|
||||
|
||||
popup_title.text = achievement_data.name
|
||||
popup_body.text = achievement_data.description
|
||||
|
||||
showing_popup = true
|
||||
popup.position.x = starting_popup_pos.x + starting_popup_size.x
|
||||
popup.modulate = Color.TRANSPARENT
|
||||
popup.scale = Vector2(0.5, 0.5)
|
||||
#popup.position.y = starting_popup_pos.y + starting_popup_size.y
|
||||
|
||||
if popup_tween:
|
||||
popup_tween.kill()
|
||||
|
||||
audio.play()
|
||||
|
||||
popup_tween = create_tween()
|
||||
popup_tween.set_parallel()
|
||||
popup_tween.set_ease(Tween.EASE_OUT)
|
||||
popup_tween.set_trans(Tween.TRANS_BACK)
|
||||
popup_tween.tween_property(popup, "modulate", Color.WHITE, POPUP_POP_TIME / 1000)
|
||||
popup_tween.tween_property(popup, "scale", Vector2(1, 1), POPUP_POP_TIME / 1000)
|
||||
popup_tween.tween_property(popup, "position:x", starting_popup_pos.x, POPUP_POP_TIME / 1000)
|
||||
popup_tween.chain()
|
||||
popup_tween.tween_callback(func():
|
||||
achievement_state = AchievementState.SHOWN
|
||||
)
|
||||
|
||||
_info("Showing achievement %s popup" % [achievement])
|
||||
|
||||
|
||||
func _get_achievement_percents() -> Dictionary:
|
||||
if not last_percent_pull:
|
||||
return _get_percents_from_db()
|
||||
|
||||
if last_percent_pull + CACHE_TIME <= Time.get_ticks_msec():
|
||||
return cached_percents
|
||||
|
||||
return _get_percents_from_db()
|
||||
|
||||
|
||||
func _get_percents_from_db() -> Dictionary:
|
||||
last_percent_pull = Time.get_ticks_msec()
|
||||
return {}
|
||||
|
||||
|
||||
func _send_to_db(achievement: String) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _check_trigger(key: String, type: String, value = null):
|
||||
if not _data:
|
||||
if (_logger): _logger.warn("Could not find data autoload when checking trigger")
|
||||
return
|
||||
|
||||
if not _data.data.has("achievements"):
|
||||
return
|
||||
|
||||
for achievement in _data.data.achievements:
|
||||
# If the persister does not have the achievement already
|
||||
if not _persister.get_value(achievement):
|
||||
var ach_data = _data.data.achievements[achievement]
|
||||
|
||||
var valid = false
|
||||
|
||||
match type:
|
||||
"Data":
|
||||
if ach_data.has("key") and ach_data.key == key:
|
||||
valid = ach_data.value <= value
|
||||
"Trigger":
|
||||
if ach_data.has("trigger") and ach_data.trigger == key:
|
||||
valid = true
|
||||
|
||||
if valid:
|
||||
achievement_unlocked.emit(achievement)
|
||||
_persister.persist_data(achievement, true, PersisterEnums.Scope.PERMANENT)
|
||||
_info("Added achievement %s to queue" % [achievement])
|
||||
queue.push_back(achievement)
|
||||
_send_to_db(achievement)
|
||||
|
||||
|
||||
func _on_mouse_handler_hovered():
|
||||
hovered = true
|
||||
|
||||
|
||||
func _on_mouse_handler_clicked():
|
||||
if achievement_state != AchievementState.EXITING:
|
||||
_exit_popup()
|
||||
|
||||
|
||||
func _on_mouse_handler_unhovered():
|
||||
hovered = false
|
1
components/Achievements/award.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-award"><path d="m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526"/><circle cx="12" cy="8" r="6"/></svg>
|
After Width: | Height: | Size: 368 B |
37
components/Achievements/award.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ur6oiwg6ksns"
|
||||
path="res://.godot/imported/award.svg-6bd80af369d65193b7f216d026b9ff51.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Achievements/award.svg"
|
||||
dest_files=["res://.godot/imported/award.svg-6bd80af369d65193b7f216d026b9ff51.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
BIN
components/Achievements/back.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
34
components/Achievements/back.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dat440dg86mjl"
|
||||
path="res://.godot/imported/back.png-7865ce000d117e80cc752a23c0588205.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Achievements/back.png"
|
||||
dest_files=["res://.godot/imported/back.png-7865ce000d117e80cc752a23c0588205.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
8
components/Achievements/information.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: Achievements
|
||||
short: Achievement handler to check and unlock achievements
|
||||
description: An achievement handler that will check the available achievements
|
||||
defined in both the game files and the mods. When called with triggers will
|
||||
check to see if that trigger value unlocks achievements.
|
||||
accent: #a166a1
|
||||
log_category: ACH
|
||||
icon: res://components/Logger/scroll-text.svg
|
BIN
components/Achievements/particle.aseprite
Normal file
BIN
components/Achievements/particle.png
Normal file
After Width: | Height: | Size: 131 B |
34
components/Achievements/particle.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://brmlpo1rv8m47"
|
||||
path="res://.godot/imported/particle.png-659e7c73fd80f7940a2d61abac8f1984.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Achievements/particle.png"
|
||||
dest_files=["res://.godot/imported/particle.png-659e7c73fd80f7940a2d61abac8f1984.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
BIN
components/Achievements/popup-background.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
34
components/Achievements/popup-background.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dpmrungfff7jd"
|
||||
path="res://.godot/imported/popup-background.png-1f543826c612308bac959317015a3a98.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Achievements/popup-background.png"
|
||||
dest_files=["res://.godot/imported/popup-background.png-1f543826c612308bac959317015a3a98.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
BIN
components/Achievements/thumbnail-mask.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
34
components/Achievements/thumbnail-mask.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dcj0xniur7dk1"
|
||||
path="res://.godot/imported/thumbnail-mask.png-9e7311d6f1141413915356d4bb61948c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Achievements/thumbnail-mask.png"
|
||||
dest_files=["res://.godot/imported/thumbnail-mask.png-9e7311d6f1141413915356d4bb61948c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
90
components/Base/base.gd
Normal file
|
@ -0,0 +1,90 @@
|
|||
class_name Base
|
||||
extends Node
|
||||
## A base addon
|
||||
##
|
||||
## A base addon for shared functionality between all other addons
|
||||
|
||||
var _logger
|
||||
var _data
|
||||
var _persister
|
||||
var _triggerer
|
||||
var _information = {
|
||||
"log_category": "???",
|
||||
"accent": "#000000",
|
||||
"icon": "res://components/Cursor/mouse-pointer-2.svg"
|
||||
}
|
||||
|
||||
var CORE_NODES = {
|
||||
"Logger": {
|
||||
"property": "_logger"
|
||||
},
|
||||
"Data": {
|
||||
"property": "_data"
|
||||
},
|
||||
"Triggerer": {
|
||||
"property": "_triggerer",
|
||||
},
|
||||
"Persister": {
|
||||
"property": "_persister",
|
||||
"connections": {"data_persisted": "_on_data_persisted"}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func _ready():
|
||||
for node in CORE_NODES:
|
||||
# Prevent circular references
|
||||
if name == node:
|
||||
break
|
||||
|
||||
# Connect up core nodes
|
||||
var node_value = CORE_NODES[node]
|
||||
|
||||
if get_tree().root.has_node(node):
|
||||
set(node_value.property, get_tree().root.get_node(node))
|
||||
var property = get(node_value.property)
|
||||
|
||||
if node_value.has("connections"):
|
||||
var connections = node_value.connections
|
||||
|
||||
for connection in connections:
|
||||
var connection_callback = connections[connection]
|
||||
|
||||
property.get(connection).connect(get(connection_callback))
|
||||
|
||||
if _data and _data.components.has(name):
|
||||
_information = _data.components[name].information
|
||||
|
||||
if _information.has("triggers"):
|
||||
for trigger in _information.triggers:
|
||||
_triggerer.listen(trigger, Callable(self, _information.triggers[trigger]))
|
||||
|
||||
if has_method("_prespawned"):
|
||||
call("_prespawned")
|
||||
|
||||
_info("%s is active" % [name])
|
||||
|
||||
if has_method("_spawned"):
|
||||
call("_spawned")
|
||||
|
||||
_info("%s is ready" % [name])
|
||||
|
||||
|
||||
func _on_data_persisted(key: String, value, category: PersisterEnums.Scope):
|
||||
pass
|
||||
|
||||
|
||||
func _debug(message: String):
|
||||
if _logger: _logger.debug(message, { "category": _information.log_category, "image": _information.icon, "color": _information.accent })
|
||||
|
||||
|
||||
func _info(message: String):
|
||||
if _logger: _logger.info(message, { "category": _information.log_category, "image": _information.icon, "color": _information.accent })
|
||||
|
||||
|
||||
func _warn(message: String):
|
||||
if _logger: _logger.warn(message, { "category": _information.log_category, "image": _information.icon, "color": _information.accent })
|
||||
|
||||
|
||||
func _error(message: String):
|
||||
if _logger: _logger.error(message, { "category": _information.log_category, "image": _information.icon, "color": _information.accent })
|
22
components/Cursor/Cursor.tscn
Normal file
|
@ -0,0 +1,22 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://qecwga1b4yqn"]
|
||||
|
||||
[ext_resource type="Script" path="res://components/Cursor/cursor.gd" id="1_nmkwm"]
|
||||
[ext_resource type="Texture2D" uid="uid://q4wsbkhirtoh" path="res://components/Cursor/cursor.png" id="2_luk0m"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_4cq27"]
|
||||
|
||||
[node name="Cursor" type="CanvasLayer"]
|
||||
layer = 10
|
||||
script = ExtResource("1_nmkwm")
|
||||
|
||||
[node name="MouseControl" type="Area2D" parent="."]
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="MouseControl"]
|
||||
shape = SubResource("RectangleShape2D_4cq27")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="MouseControl"]
|
||||
scale = Vector2(0.15625, 0.15625)
|
||||
texture = ExtResource("2_luk0m")
|
||||
|
||||
[connection signal="area_entered" from="MouseControl" to="." method="_on_mouse_control_area_entered"]
|
||||
[connection signal="area_exited" from="MouseControl" to="." method="_on_mouse_control_area_exited"]
|
6
components/Cursor/MouseHandler.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://dykc1mgg5uopw"]
|
||||
|
||||
[ext_resource type="Script" path="res://components/Cursor/mouse_handler.gd" id="1_n30mb"]
|
||||
|
||||
[node name="MouseHandler" type="Area2D"]
|
||||
script = ExtResource("1_n30mb")
|
BIN
components/Cursor/cursor-ui.aseprite
Normal file
BIN
components/Cursor/cursor-ui.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
34
components/Cursor/cursor-ui.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cg140nu46vm2p"
|
||||
path="res://.godot/imported/cursor-ui.png-342ebc343c2c58c466eef67d0e8042ef.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Cursor/cursor-ui.png"
|
||||
dest_files=["res://.godot/imported/cursor-ui.png-342ebc343c2c58c466eef67d0e8042ef.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
BIN
components/Cursor/cursor.aseprite
Normal file
87
components/Cursor/cursor.gd
Normal file
|
@ -0,0 +1,87 @@
|
|||
@icon("res://components/Cursor/mouse-pointer-2.svg")
|
||||
extends Base
|
||||
|
||||
@onready var mouse_control = $"MouseControl"
|
||||
@onready var sprite_2d: Sprite2D = $MouseControl/Sprite2D
|
||||
|
||||
const CURSOR_UI = preload("res://components/Cursor/cursor-ui.png")
|
||||
const CURSOR = preload("res://components/Cursor/cursor.png")
|
||||
var cursor_state = "normal"
|
||||
var rotation_tween
|
||||
|
||||
func _ready() -> void:
|
||||
Persister.data_persisted.connect(_on_data_persisted)
|
||||
Triggerer.listen("fire", _on_fire)
|
||||
|
||||
|
||||
func _on_fire(_data):
|
||||
if cursor_state == "ui":
|
||||
return
|
||||
|
||||
sprite_2d.scale = Vector2.ONE * 1.25 * 0.156
|
||||
|
||||
|
||||
func _on_click():
|
||||
var overlapping_areas = mouse_control.get_overlapping_areas()
|
||||
overlapping_areas.sort_custom(func(a, b):
|
||||
return a.z_index > b.z_index
|
||||
)
|
||||
|
||||
for area in overlapping_areas:
|
||||
if area.has_method("_on_clicked"):
|
||||
var current_node = area
|
||||
var hidden = false
|
||||
while current_node:
|
||||
if "modulate" in current_node:
|
||||
if current_node.modulate.a < 1:
|
||||
hidden = true
|
||||
break
|
||||
current_node = current_node.get_parent()
|
||||
|
||||
if hidden:
|
||||
continue
|
||||
|
||||
if not area._on_clicked():
|
||||
break
|
||||
|
||||
func _on_rclick():
|
||||
var overlapping_areas = mouse_control.get_overlapping_areas()
|
||||
overlapping_areas.sort_custom(func(a, b):
|
||||
return a.z_index > b.z_index
|
||||
)
|
||||
|
||||
for area in overlapping_areas:
|
||||
if area.has_method("_on_rclicked"):
|
||||
var current_node = area
|
||||
var hidden = false
|
||||
while current_node:
|
||||
if "modulate" in current_node:
|
||||
if current_node.modulate.a < 1:
|
||||
hidden = true
|
||||
break
|
||||
current_node = current_node.get_parent()
|
||||
|
||||
if hidden:
|
||||
continue
|
||||
|
||||
if not area._on_rclicked():
|
||||
break
|
||||
|
||||
|
||||
func _process(delta):
|
||||
if Input.is_action_just_pressed("lclick"):
|
||||
_on_click()
|
||||
if Input.is_action_just_pressed("rclick"):
|
||||
_on_rclick()
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
|
||||
mouse_control.position = mouse_control.get_global_mouse_position()
|
||||
|
||||
|
||||
func _on_mouse_control_area_entered(area):
|
||||
if area.has_method("_on_hovered"):
|
||||
area._on_hovered()
|
||||
|
||||
|
||||
func _on_mouse_control_area_exited(area):
|
||||
if area.has_method("_on_unhovered"):
|
||||
area._on_unhovered()
|
BIN
components/Cursor/cursor.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
34
components/Cursor/cursor.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://q4wsbkhirtoh"
|
||||
path="res://.godot/imported/cursor.png-ab5b6c93c7e19595ea2eadb14e17d081.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Cursor/cursor.png"
|
||||
dest_files=["res://.godot/imported/cursor.png-ab5b6c93c7e19595ea2eadb14e17d081.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
BIN
components/Cursor/grinder-mini-icon.aseprite
Normal file
BIN
components/Cursor/grinder-mini-icon.png
Normal file
After Width: | Height: | Size: 1 KiB |
34
components/Cursor/grinder-mini-icon.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://lrvl2uddu856"
|
||||
path="res://.godot/imported/grinder-mini-icon.png-1f6177a95ae870b905ce582bb42a531b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Cursor/grinder-mini-icon.png"
|
||||
dest_files=["res://.godot/imported/grinder-mini-icon.png-1f6177a95ae870b905ce582bb42a531b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
BIN
components/Cursor/grinder.aseprite
Normal file
BIN
components/Cursor/grinder.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
34
components/Cursor/grinder.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cygdk25d83qp5"
|
||||
path="res://.godot/imported/grinder.png-138a6003a032d52389ff94554d883d54.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Cursor/grinder.png"
|
||||
dest_files=["res://.godot/imported/grinder.png-138a6003a032d52389ff94554d883d54.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
6
components/Cursor/information.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: Cursor
|
||||
short: ???
|
||||
description: ???
|
||||
accent: #35b84b
|
||||
log_category: CUR
|
||||
icon: res://components/Cursor/mouse-pointer-2.svg
|
1
components/Cursor/mouse-pointer-2.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mouse-pointer-2"><path d="m4 4 7.07 17 2.51-7.39L21 11.07z"/></svg>
|
After Width: | Height: | Size: 262 B |
37
components/Cursor/mouse-pointer-2.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cdu6b7pclsrrg"
|
||||
path="res://.godot/imported/mouse-pointer-2.svg-528a80312e1a4aed351ba060d183df97.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Cursor/mouse-pointer-2.svg"
|
||||
dest_files=["res://.godot/imported/mouse-pointer-2.svg-528a80312e1a4aed351ba060d183df97.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
components/Cursor/mouse-pointer-click.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mouse-pointer-click"><path d="m9 9 5 12 1.8-5.2L21 14Z"/><path d="M7.2 2.2 8 5.1"/><path d="m5.1 8-2.9-.8"/><path d="M14 4.1 12 6"/><path d="m6 12-1.9 2"/></svg>
|
After Width: | Height: | Size: 356 B |
37
components/Cursor/mouse-pointer-click.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ddkp18the486t"
|
||||
path="res://.godot/imported/mouse-pointer-click.svg-f0b06ba161b377fd039b4f40e477ff4d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Cursor/mouse-pointer-click.svg"
|
||||
dest_files=["res://.godot/imported/mouse-pointer-click.svg-f0b06ba161b377fd039b4f40e477ff4d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
39
components/Cursor/mouse_handler.gd
Normal file
|
@ -0,0 +1,39 @@
|
|||
@icon("res://components/Cursor/mouse-pointer-click.svg")
|
||||
extends Area2D
|
||||
|
||||
signal clicked
|
||||
signal hovered
|
||||
signal unhovered
|
||||
signal rclicked
|
||||
|
||||
@export var passthrough = false
|
||||
|
||||
var _logger
|
||||
|
||||
func _ready():
|
||||
if get_tree().root.has_node("Logger"):
|
||||
_logger = get_tree().root.get_node("Logger")
|
||||
|
||||
func _on_clicked():
|
||||
clicked.emit()
|
||||
return passthrough
|
||||
|
||||
func _on_rclicked():
|
||||
rclicked.emit()
|
||||
return passthrough
|
||||
|
||||
func _on_hovered():
|
||||
hovered.emit()
|
||||
_logger.info("Hovered over mouse handler for object →%s←" % [get_parent().name], {
|
||||
"color": "#919191",
|
||||
"image": "res://components/Cursor/mouse-pointer-click.svg",
|
||||
"category": "MOU"
|
||||
})
|
||||
|
||||
func _on_unhovered():
|
||||
unhovered.emit()
|
||||
_logger.info("Unhovered mouse handler for object →%s←" % [get_parent().name], {
|
||||
"color": "#919191",
|
||||
"image": "res://components/Cursor/mouse-pointer-click.svg",
|
||||
"category": "MOU"
|
||||
})
|
BIN
components/Cursor/saw.aseprite
Normal file
BIN
components/Cursor/saw.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
34
components/Cursor/saw.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://mnwnajp2e87v"
|
||||
path="res://.godot/imported/saw.png-36e8ff0c640bc68b02eebb4f43290f75.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Cursor/saw.png"
|
||||
dest_files=["res://.godot/imported/saw.png-36e8ff0c640bc68b02eebb4f43290f75.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
6
components/Data/Data.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://b4o7cekdsf4pu"]
|
||||
|
||||
[ext_resource type="Script" path="res://components/Data/data.gd" id="1_xutp1"]
|
||||
|
||||
[node name="Data" type="Node"]
|
||||
script = ExtResource("1_xutp1")
|
1
components/Data/book-marked.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-marked"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/><polyline points="10 2 10 10 13 7 16 10 16 2"/></svg>
|
After Width: | Height: | Size: 335 B |
37
components/Data/book-marked.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://05aeafxrb5ph"
|
||||
path="res://.godot/imported/book-marked.svg-4d90597eaf7440ba51cd915ad6f20255.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Data/book-marked.svg"
|
||||
dest_files=["res://.godot/imported/book-marked.svg-4d90597eaf7440ba51cd915ad6f20255.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
18
components/Data/data-toolbar.gd
Normal file
|
@ -0,0 +1,18 @@
|
|||
const LOG_CATEGORY = "DATAT"
|
||||
const NAME = "Data"
|
||||
|
||||
|
||||
func send_log(parent):
|
||||
parent.tools[NAME].node.info("Test Debug!", LOG_CATEGORY)
|
||||
|
||||
|
||||
func send_debug(parent):
|
||||
parent.tools[NAME].node.debug("Test Log!", LOG_CATEGORY)
|
||||
|
||||
|
||||
func send_warning(parent):
|
||||
parent.tools[NAME].node.warn("Test Warning!", LOG_CATEGORY)
|
||||
|
||||
|
||||
func send_error(parent):
|
||||
parent.tools[NAME].node.error("Test Error!", LOG_CATEGORY)
|
241
components/Data/data.gd
Normal file
|
@ -0,0 +1,241 @@
|
|||
@icon("res://components/Data/book-marked.svg")
|
||||
class_name DataType
|
||||
extends Base
|
||||
## Data handler to read in data from files
|
||||
##
|
||||
## A data handler that will read in data from txt files and other sources for
|
||||
## use in various parts of the game. Reads both from the parts folder in the
|
||||
## main game directory and the mods folder in the user folder to allow users to
|
||||
## easily mod the game.
|
||||
|
||||
var _FILE_FUNCTIONS = {
|
||||
"*.txt": _read_txt,
|
||||
"*.png": _read_resource,
|
||||
"*.png.import": _read_png_import,
|
||||
"*.ogg": _read_resource,
|
||||
"*.mp3": _read_resource,
|
||||
"*.wav": _read_resource,
|
||||
"*.gd": _read_script_resource,
|
||||
}
|
||||
|
||||
var _FILE_LOCATION_OVERRIDES = {
|
||||
"*.gd": "scripts",
|
||||
"*.png.import": "images",
|
||||
"*.png": "images",
|
||||
"*.ogg": "audio",
|
||||
"*.wav": "audio"
|
||||
}
|
||||
|
||||
var data = {}
|
||||
var components = {}
|
||||
var location_overrides = {}
|
||||
|
||||
|
||||
func _prespawned():
|
||||
_information = {
|
||||
"accent": "#a27155",
|
||||
"log_category": "DATA",
|
||||
"icon": "res://components/Data/book-marked.svg"
|
||||
}
|
||||
|
||||
|
||||
func _spawned():
|
||||
reload_data()
|
||||
|
||||
|
||||
## Reload the data object by pulling from available sources
|
||||
func reload_data() -> void:
|
||||
data = {} # Remove all old mod data
|
||||
|
||||
# Open part folder from project (Ignore if none) and if exists set mod data to it
|
||||
var res_dir = DirAccess.open("res://")
|
||||
if res_dir.dir_exists("parts"):
|
||||
data = _read_directory("res://parts")
|
||||
data = _merge_objects(location_overrides, data)
|
||||
|
||||
if res_dir.dir_exists("components"):
|
||||
location_overrides = {}
|
||||
components = _read_directory("res://components")
|
||||
components = _merge_objects(location_overrides, components)
|
||||
|
||||
|
||||
func _merge_objects(object1, object2):
|
||||
var newObject = {}
|
||||
for key in object1:
|
||||
newObject[key] = object1[key]
|
||||
for key in object2:
|
||||
if(newObject.has(key)):
|
||||
if(typeof(newObject[key]) == TYPE_STRING):
|
||||
newObject[key] = object1[key]
|
||||
elif(typeof(newObject[key]) == TYPE_OBJECT):
|
||||
if newObject[key] is Texture:
|
||||
newObject[key] = object1[key]
|
||||
else:
|
||||
newObject[key] = _merge_objects(object1[key], object2[key])
|
||||
elif(typeof(newObject[key]) == TYPE_DICTIONARY):
|
||||
newObject[key] = _merge_objects(object1[key], object2[key])
|
||||
elif(typeof(newObject[key]) == TYPE_ARRAY):
|
||||
newObject[key] = object1[key] + object2[key]
|
||||
else:
|
||||
newObject[key] = object2[key]
|
||||
return newObject
|
||||
|
||||
|
||||
func _read_directory(path) -> Dictionary:
|
||||
var dir = DirAccess.open(path)
|
||||
var local_data = {}
|
||||
|
||||
if not dir:
|
||||
_warn("Could not read directory [%s]" % [path])
|
||||
return {}
|
||||
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
while file_name != "":
|
||||
if dir.current_is_dir():
|
||||
var micro_data = _read_directory("%s/%s" % [path, file_name])
|
||||
if micro_data:
|
||||
local_data[file_name] = micro_data
|
||||
else:
|
||||
var file_handling = _get_file_handling(file_name)
|
||||
|
||||
if (file_handling):
|
||||
var location_override = _get_location_override(file_name)
|
||||
|
||||
if location_override:
|
||||
var micro_data = file_handling.call("%s/%s" % [path, file_name])
|
||||
if not location_overrides.has(location_override):
|
||||
location_overrides[location_override] = {}
|
||||
location_overrides[location_override][file_name.split(".")[0]] = micro_data
|
||||
else:
|
||||
var micro_data = file_handling.call("%s/%s" % [path, file_name])
|
||||
if micro_data:
|
||||
local_data[file_name.split(".")[0]] = micro_data
|
||||
|
||||
file_name = dir.get_next()
|
||||
|
||||
_info("Read directory ♢%s♢" % [path])
|
||||
return local_data
|
||||
|
||||
|
||||
func _get_file_handling(file):
|
||||
var split_file = file.split(".", true, 1)
|
||||
|
||||
for file_function in _FILE_FUNCTIONS:
|
||||
if(file_function == file):
|
||||
return _FILE_FUNCTIONS[file_function]
|
||||
|
||||
var split_file_function = file_function.split(".", true, 1)
|
||||
|
||||
if(split_file_function[0] == "*" and split_file_function[1] == split_file[1]):
|
||||
return _FILE_FUNCTIONS[file_function]
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func _read_script_resource(path):
|
||||
return load(path)
|
||||
|
||||
|
||||
func _read_resource(path):
|
||||
var song = load(path)
|
||||
return song
|
||||
|
||||
|
||||
func _read_png_import(path):
|
||||
var image = load(path.rsplit(".", true, 1)[0])
|
||||
return image
|
||||
|
||||
|
||||
func _get_location_override(file):
|
||||
var split_file = file.split(".", true, 1)
|
||||
|
||||
for location_override in _FILE_LOCATION_OVERRIDES:
|
||||
if location_override == file:
|
||||
return _FILE_LOCATION_OVERRIDES[location_override]
|
||||
|
||||
var split_file_function = location_override.split(".", true, 1)
|
||||
|
||||
if(split_file_function[0] == "*" and split_file_function[1] == split_file[1]):
|
||||
return _FILE_LOCATION_OVERRIDES[location_override]
|
||||
|
||||
|
||||
func _read_txt(path) -> Dictionary:
|
||||
# Open file for reading
|
||||
var file = FileAccess.open(path, FileAccess.READ)
|
||||
var content = file.get_as_text()
|
||||
var local_data = {}
|
||||
|
||||
# Separate into lines
|
||||
var split_content = content.split("\n", false)
|
||||
|
||||
# Indentation
|
||||
var indentation = 0
|
||||
var indentation_levels = []
|
||||
|
||||
# Iterate over everything
|
||||
for content_piece in split_content:
|
||||
# Fix Indentation to actual content level
|
||||
var actual_indentation = _count_indentation(content_piece)
|
||||
|
||||
while(actual_indentation < indentation):
|
||||
indentation -= 1
|
||||
indentation_levels.pop_back()
|
||||
|
||||
# Navigate to current indentation data
|
||||
var micro_data = local_data
|
||||
var previous_data = null
|
||||
var i = 0
|
||||
while i < indentation:
|
||||
previous_data = micro_data
|
||||
micro_data = micro_data[indentation_levels[i]]
|
||||
i += 1
|
||||
|
||||
# Reading
|
||||
if(content_piece.ends_with("[]")):
|
||||
# Array handling
|
||||
var trimmed_content = content_piece.strip_edges().trim_suffix("[]")
|
||||
|
||||
indentation += 1
|
||||
indentation_levels.append(trimmed_content)
|
||||
micro_data[trimmed_content] = {}
|
||||
else:
|
||||
var split_args = content_piece.split(":", true, 1)
|
||||
|
||||
if split_args.size() == 2:
|
||||
# Dict Handling
|
||||
var key = split_args[0].strip_edges()
|
||||
var value = split_args[1].strip_edges()
|
||||
|
||||
if value.is_valid_int():
|
||||
value = value.to_int()
|
||||
|
||||
micro_data[key] = value
|
||||
elif split_args.size() == 1:
|
||||
# Value Handling
|
||||
var value = split_args[0].strip_edges()
|
||||
|
||||
if typeof(micro_data) == TYPE_DICTIONARY:
|
||||
# TODO: FIX BELOW
|
||||
if indentation_levels.size() == 0:
|
||||
continue
|
||||
|
||||
if previous_data[indentation_levels[indentation_levels.size()-1]].keys().size() == 0:
|
||||
previous_data[indentation_levels[indentation_levels.size()-1]] = [split_args[0].strip_edges()]
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
previous_data[indentation_levels[indentation_levels.size()-1]] += [split_args[0].strip_edges()]
|
||||
return local_data
|
||||
|
||||
|
||||
func _count_indentation(string: String):
|
||||
var tabs = 0
|
||||
|
||||
for character in string:
|
||||
if(character == "\t"):
|
||||
tabs += 1
|
||||
else:
|
||||
return tabs
|
||||
|
||||
return tabs
|
10
components/Data/information.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: Data
|
||||
short: Data handler to read in data from files
|
||||
description: |
|
||||
A data handler that will read in data from txt files and other
|
||||
sources for use in various parts of the game. Reads both from the
|
||||
parts folder in the main game directory and the mods folder in the
|
||||
user folder to allow users to easily mod the game.
|
||||
accent: #a27155
|
||||
log_category: DATA
|
||||
icon: res://components/Data/book-marked.svg
|
20
components/Data/toolbar.txt
Normal file
|
@ -0,0 +1,20 @@
|
|||
debug[]
|
||||
type: button
|
||||
name: Send Debug
|
||||
accent: #11a1a1
|
||||
function: send_debug
|
||||
log[]
|
||||
type: button
|
||||
name: Send Log
|
||||
accent: #11a11a
|
||||
function: send_log
|
||||
warning[]
|
||||
type: button
|
||||
name: Send Warning
|
||||
accent: #a1a111
|
||||
function: send_warning
|
||||
error[]
|
||||
type: button
|
||||
name: Send Error
|
||||
accent: #f13333
|
||||
function: send_error
|
6
components/Logger/Logger.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://7p8ecy62n2h8"]
|
||||
|
||||
[ext_resource type="Script" path="res://components/Logger/logger.gd" id="1_gse6q"]
|
||||
|
||||
[node name="Logger" type="Node"]
|
||||
script = ExtResource("1_gse6q")
|
0
components/Logger/commands.gd
Normal file
9
components/Logger/information.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
name: Logger
|
||||
short: A logger to log data to relevant locations
|
||||
description: A logger that logs various forms of data at different log levels to
|
||||
both built in locations in Godot as well as other components that listen to
|
||||
the provided log created signal.
|
||||
accent: #71a255
|
||||
log_category: LOG
|
||||
icon: res://components/Logger/scroll-text.svg
|
||||
version: v1.0.0
|
34
components/Logger/logger-toolbar.gd
Normal file
|
@ -0,0 +1,34 @@
|
|||
const LOG_CATEGORY = "TOOLT"
|
||||
const NAME = "Logger"
|
||||
|
||||
|
||||
func send_log(parent):
|
||||
parent.info("Test Log!", {
|
||||
"image": "res://components/Toolbar/hammer.svg",
|
||||
"color": "#818181",
|
||||
"category": "TST"
|
||||
})
|
||||
|
||||
|
||||
func send_debug(parent):
|
||||
parent.debug("Test Debug!", {
|
||||
"image": "res://components/Toolbar/hammer.svg",
|
||||
"color": "#818181",
|
||||
"category": "TST"
|
||||
})
|
||||
|
||||
|
||||
func send_warning(parent):
|
||||
parent.warn("Test Warn!", {
|
||||
"image": "res://components/Toolbar/hammer.svg",
|
||||
"color": "#818181",
|
||||
"category": "TST"
|
||||
})
|
||||
|
||||
|
||||
func send_error(parent):
|
||||
parent.error("Test Error!", {
|
||||
"image": "res://components/Toolbar/hammer.svg",
|
||||
"color": "#818181",
|
||||
"category": "TST"
|
||||
})
|
138
components/Logger/logger.gd
Normal file
|
@ -0,0 +1,138 @@
|
|||
extends Node
|
||||
## A logger to log data to relevant locations
|
||||
##
|
||||
## A logger that logs various forms of data at different log levels to both
|
||||
## built in locations in Godot as well as other components that listen to the
|
||||
## provided log created signal.
|
||||
|
||||
signal log_created(message: String, level: LogLevel)
|
||||
|
||||
enum LogLevel {
|
||||
DEBUG = 0,
|
||||
INFO = 1,
|
||||
WARN = 2,
|
||||
ERROR = 3
|
||||
}
|
||||
|
||||
## The log level that should be outputted as a minimum.
|
||||
@export var log_level: LogLevel
|
||||
|
||||
|
||||
## Log a message at log level debug (meant for troubleshooting).
|
||||
func debug(message: String, arguments: Dictionary = {}) -> void:
|
||||
if log_level > LogLevel.DEBUG:
|
||||
return
|
||||
|
||||
_log(message, LogLevel.DEBUG, arguments)
|
||||
|
||||
|
||||
## Log a message at log level info (log to indicate something happened).
|
||||
func info(message: String, arguments: Dictionary = {}) -> void:
|
||||
if log_level > LogLevel.INFO:
|
||||
return
|
||||
|
||||
_log(message, LogLevel.INFO, arguments)
|
||||
|
||||
|
||||
## Log a warning at log level warning (something unexpected happened but it can
|
||||
## continue).
|
||||
func warn(message: String, arguments: Dictionary = {}) -> void:
|
||||
_log(message, LogLevel.WARN, arguments)
|
||||
|
||||
|
||||
## Log an error at log level error (an issue that prevents something from
|
||||
## functioning).
|
||||
func error(message: String, arguments: Dictionary = {}) -> void:
|
||||
_log(message, LogLevel.ERROR, arguments)
|
||||
|
||||
|
||||
func _log(message: String, level: LogLevel, arguments: Dictionary = {}) -> void:
|
||||
var category = arguments.category if arguments.has("category") and arguments.category else "???"
|
||||
var color = arguments.color if arguments.has("color") else "olive"
|
||||
var image = arguments.image if arguments.has("image") and arguments.image else "res://components/Logger/scroll-text.svg"
|
||||
|
||||
var adjusted_message = _clean_message(message)
|
||||
|
||||
var constructed_message = "[color=%s][%s][/color] [img= width=12 height=12 valign=center]%s[/img] %s" % [color, category, image, adjusted_message]
|
||||
print_rich(constructed_message)
|
||||
log_created.emit(constructed_message, level)
|
||||
|
||||
|
||||
func _clean_message(message: String) -> String:
|
||||
var cleans = [
|
||||
{
|
||||
"type": "button",
|
||||
"regex": "μ(.*)μ",
|
||||
"color": Color.html("#a4bf37")
|
||||
},
|
||||
{
|
||||
"type": "key",
|
||||
"regex": "<(.*)>",
|
||||
"color": Color.html("#42ad24")
|
||||
},
|
||||
{
|
||||
"type": "tool",
|
||||
"regex": "λ(.*)λ",
|
||||
"color": Color.html("#bf9d37")
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"regex": "→(.*)←",
|
||||
"color": Color.html("#854322")
|
||||
},
|
||||
{
|
||||
"type": "path",
|
||||
"regex": "♢(.*)♢",
|
||||
"color": Color.html("#22852e")
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"regex": "∨(.*)∨",
|
||||
"color": Color.html("#ad2452")
|
||||
},
|
||||
{
|
||||
"type": "trigger",
|
||||
"regex": "∧(.*)∧",
|
||||
"color": Color.html("#ad2d24")
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"regex": "\\{(.*)\\}",
|
||||
"color": Color.html("#ad9b24")
|
||||
},
|
||||
{
|
||||
"type": "value",
|
||||
"regex": "\\|(.*)\\|",
|
||||
"color": Color.TEAL
|
||||
}
|
||||
]
|
||||
|
||||
var adjusted_message = message
|
||||
|
||||
for clean in cleans:
|
||||
adjusted_message = _replace_regex_color(adjusted_message, clean.regex, clean.color)
|
||||
|
||||
return adjusted_message
|
||||
|
||||
|
||||
func _replace_regex_color(message: String, regex_string: String, color: Color) -> String:
|
||||
return _replace_regex(message, regex_string, "[color=" + color.to_html() + "]%s[/color]")
|
||||
|
||||
|
||||
func _replace_regex(message: String, regex_string: String, new_content: String) -> String:
|
||||
var adjusted_message = message
|
||||
var stripped_message = message
|
||||
|
||||
var regex = RegEx.new()
|
||||
regex.compile(regex_string)
|
||||
var result = regex.search(adjusted_message)
|
||||
|
||||
while result:
|
||||
var before_content = adjusted_message.substr(0, result.get_start())
|
||||
var after_content = adjusted_message.substr(result.get_end(), adjusted_message.length())
|
||||
var template_string = "%s" + new_content + "%s"
|
||||
adjusted_message = template_string % [before_content, result.get_string(1), after_content]
|
||||
|
||||
result = regex.search(adjusted_message)
|
||||
|
||||
return adjusted_message
|
1
components/Logger/scroll-text.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scroll-text"><path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/></svg>
|
After Width: | Height: | Size: 409 B |
37
components/Logger/scroll-text.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bt1irjjewldcq"
|
||||
path="res://.godot/imported/scroll-text.svg-3edd1135d80784e9e14ee4871d671a9a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Logger/scroll-text.svg"
|
||||
dest_files=["res://.godot/imported/scroll-text.svg-3edd1135d80784e9e14ee4871d671a9a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
11
components/Logger/settings.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
log_level[]
|
||||
name: Log Level
|
||||
description: The level of info you want as a minimum to be logged to
|
||||
relevant locations.
|
||||
category: misc
|
||||
default: LogLevel.WARN
|
||||
values[]
|
||||
LogLevel.DEBUG
|
||||
LogLevel.INFO
|
||||
LogLevel.WARN
|
||||
LogLevel.ERROR
|
24
components/Logger/toolbar.txt
Normal file
|
@ -0,0 +1,24 @@
|
|||
debug[]
|
||||
type: button
|
||||
name: Send Debug
|
||||
accent: #11a1a1
|
||||
function: send_debug
|
||||
test[]
|
||||
type: label
|
||||
name: Testing Label
|
||||
accent: #11a1cc
|
||||
log[]
|
||||
type: button
|
||||
name: Send Log
|
||||
accent: #11a11a
|
||||
function: send_log
|
||||
warning[]
|
||||
type: button
|
||||
name: Send Warning
|
||||
accent: #a1a111
|
||||
function: send_warning
|
||||
error[]
|
||||
type: button
|
||||
name: Send Error
|
||||
accent: #f13333
|
||||
function: send_error
|
12
components/Persister/Persister.tscn
Normal file
|
@ -0,0 +1,12 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://pht0rn54n3t8"]
|
||||
|
||||
[ext_resource type="Script" path="res://components/Persister/persister.gd" id="1_taxaa"]
|
||||
|
||||
[node name="Persister" type="Node"]
|
||||
script = ExtResource("1_taxaa")
|
||||
|
||||
[node name="Timer" type="Timer" parent="."]
|
||||
wait_time = 20.0
|
||||
autostart = true
|
||||
|
||||
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
|
11
components/Persister/data_scope.gd
Normal file
|
@ -0,0 +1,11 @@
|
|||
class_name PersisterEnums
|
||||
|
||||
enum Scope {
|
||||
PERMANENT, # Stays forever
|
||||
SAVE, # Persists through a save
|
||||
GAME, # Persists through runs
|
||||
RUN, # Persists through an entire run of the game
|
||||
ROUND, # Persists through a round (if applicable, e.g. between ifa round is 2 mins between shops)
|
||||
ROOM, # Persists within a game room (if applicable)
|
||||
UNKNOWN
|
||||
}
|
1
components/Persister/hard-drive.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hard-drive"><line x1="22" x2="2" y1="12" y2="12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/><line x1="6" x2="6.01" y1="16" y2="16"/><line x1="10" x2="10.01" y1="16" y2="16"/></svg>
|
After Width: | Height: | Size: 451 B |
37
components/Persister/hard-drive.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bru35jf108ajj"
|
||||
path="res://.godot/imported/hard-drive.svg-f9677d2ad3a8226891a78779d376d996.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Persister/hard-drive.svg"
|
||||
dest_files=["res://.godot/imported/hard-drive.svg-f9677d2ad3a8226891a78779d376d996.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
10
components/Persister/information.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: Persister
|
||||
short: Data persister to persist data through locations
|
||||
description: A data persister that is used to persist data through the game,
|
||||
run, round, or room. A function is called to set data with a key in one
|
||||
of the categories and then other functions can be called to clear
|
||||
the data for a key or all data in a category.
|
||||
accent: #a1a166
|
||||
log_category: PER
|
||||
icon: res://components/Persister/hard-drive.svg
|
||||
version: v1.0.0
|
5
components/Persister/persister-toolbar.gd
Normal file
|
@ -0,0 +1,5 @@
|
|||
func save_one(parent):
|
||||
parent.change_save(1)
|
||||
|
||||
func save_two(parent):
|
||||
parent.change_save(2)
|
384
components/Persister/persister.gd
Normal file
|
@ -0,0 +1,384 @@
|
|||
@icon("res://components/Persister/hard-drive.svg")
|
||||
extends Base
|
||||
## Data persister to persist data through locations
|
||||
##
|
||||
## A data persister that is used to persist data through the game, run, round,
|
||||
## or room. A function is called to set data with a key in one of the categories
|
||||
## and then other functions can be called to clear the data for a key or all
|
||||
## data in a category.
|
||||
|
||||
signal data_persisted(key: String, value, category: PersisterEnums.Scope)
|
||||
|
||||
var _persisted = {}
|
||||
|
||||
|
||||
func _spawned():
|
||||
if _triggerer:
|
||||
_info("Connecting to data scope triggers")
|
||||
_triggerer.listen("game", _on_game_triggered)
|
||||
_triggerer.listen("run", _on_run_triggered)
|
||||
_triggerer.listen("round", _on_round_triggered)
|
||||
_triggerer.listen("room", _on_room_triggered)
|
||||
_load()
|
||||
|
||||
|
||||
## Store data in a category
|
||||
func persist_data(key: String, value, category := PersisterEnums.Scope.RUN) -> void:
|
||||
# Only allow ints, bools or strings to be persisted and as Strings
|
||||
var original_pass = value
|
||||
|
||||
if value is int:
|
||||
value = str(value)
|
||||
|
||||
if value is bool:
|
||||
value = str(value)
|
||||
|
||||
if not value is String:
|
||||
_warn("Attempted to persist data for key <%s> with an invalid data type |%s|" % [key, type_string(typeof(value))])
|
||||
return
|
||||
|
||||
# Create category if does not exist
|
||||
if not _persisted.has(category):
|
||||
_info("Created new persister category {%s}" % [_get_category_name(category)])
|
||||
_persisted[category] = {}
|
||||
|
||||
# If key is already set to value exit early
|
||||
if _persisted[category].has(key) and _persisted[category][key] == value:
|
||||
return
|
||||
|
||||
_info("Set key <%s> in category {%s} to value |%s|" % [key, _get_category_name(category), value])
|
||||
_persisted[category][key] = value
|
||||
|
||||
if _triggerer:
|
||||
_triggerer.trigger(key, {"value": original_pass})
|
||||
|
||||
_emit_change(key, value, category)
|
||||
|
||||
|
||||
## Get the value associated with a key in the highest priority or specified category
|
||||
func get_value(key: String, category: PersisterEnums.Scope = PersisterEnums.Scope.UNKNOWN, default = null):
|
||||
if category == PersisterEnums.Scope.UNKNOWN:
|
||||
category = _get_key_category(key)
|
||||
|
||||
if category == PersisterEnums.Scope.UNKNOWN:
|
||||
return default
|
||||
|
||||
if _persisted[category][key].is_valid_int():
|
||||
return int(_persisted[category][key])
|
||||
|
||||
if _persisted[category][key] == "true" || _persisted[category][key] == "false":
|
||||
return _persisted[category][key] == "true"
|
||||
|
||||
return _persisted[category][key]
|
||||
|
||||
|
||||
## Delete data associated with a key from a certain category
|
||||
func clear_data(key: String, category := PersisterEnums.Scope.RUN) -> void:
|
||||
if not _persisted.has(category):
|
||||
_info("Attempted to clear key <%s> in category {%s} but the category did not exist" % [key, _get_category_name(category)])
|
||||
return
|
||||
|
||||
if not _persisted[category].has(key):
|
||||
_info("Attempted to clear key <%s> in category {%s} but it did not exist" % [key, _get_category_name(category)])
|
||||
return
|
||||
|
||||
_info("Cleared key <%s> in category {%s}" % [key, _get_category_name(category)])
|
||||
_persisted[category].erase(key)
|
||||
|
||||
_emit_removal(key, category)
|
||||
|
||||
|
||||
## Delete a category including all data within it
|
||||
func clear_category(category := PersisterEnums.Scope.RUN) -> void:
|
||||
if not _persisted.has(category):
|
||||
_error("Attempted to clear category {%s} but it did not exist" % [_get_category_name(category)])
|
||||
return
|
||||
|
||||
var keys = _persisted[category].keys()
|
||||
|
||||
_info("Cleared category {%s}" % [_get_category_name(category)])
|
||||
_persisted.erase(category)
|
||||
|
||||
for key in keys:
|
||||
_emit_removal(key, category)
|
||||
|
||||
|
||||
## Add a number to a number value with the given key from a certain category
|
||||
func change_value(key: String, value: int, category := PersisterEnums.Scope.RUN) -> void:
|
||||
if not _persisted.has(category):
|
||||
_persisted[category] = {}
|
||||
|
||||
if not _persisted[category].has(key):
|
||||
persist_data(key, value, category)
|
||||
return
|
||||
|
||||
if is_nan(int(_persisted[category][key])):
|
||||
_error("Attempted to add number |%d| to key <%s> in category {%s} that is not a number (value: |%s|)" % [value, key, category, _persisted[category][key]])
|
||||
return
|
||||
|
||||
var old_value = int(_persisted[category][key])
|
||||
_info("Added number |%d| to key <%s> in category {%s} (old: |%d|) (new: |%d|)" % [value, key, _get_category_name(category), old_value, old_value + value])
|
||||
persist_data(key, value + old_value, category)
|
||||
|
||||
|
||||
func change_value_clamp_min(key: String, value: int, min: int, category := PersisterEnums.Scope.RUN) -> void:
|
||||
change_value_clamp(key, value, min, INF, category)
|
||||
|
||||
|
||||
func change_value_clamp_max(key: String, value: int, max: int, category := PersisterEnums.Scope.RUN) -> void:
|
||||
change_value_clamp(key, value, -INF, max, category)
|
||||
|
||||
|
||||
func change_value_clamp(key: String, value: int, min: int, max: int, category := PersisterEnums.Scope.RUN) -> void:
|
||||
if min > max and not min == -9223372036854775808 and not max == -9223372036854775808:
|
||||
_warn("Attempted to change clamp value %s with higher min %s than max %s" % [key, min, max])
|
||||
return
|
||||
|
||||
if not _persisted.has(category):
|
||||
_persisted[category] = {}
|
||||
|
||||
if not _persisted[category].has(key):
|
||||
if value < min and not min == -9223372036854775808:
|
||||
value = min
|
||||
|
||||
if value > max and not max == -9223372036854775808:
|
||||
value = max
|
||||
|
||||
persist_data(key, value, category)
|
||||
return
|
||||
|
||||
if is_nan(int(_persisted[category][key])):
|
||||
_error("Attempted to add number |%d| to key <%s> in category {%s} that is not a number (value: |%s|)" % [value, key, category, _persisted[category][key]])
|
||||
return
|
||||
|
||||
var old_value = int(_persisted[category][key])
|
||||
var new_value = value + old_value
|
||||
|
||||
if new_value < min and not min == -9223372036854775808:
|
||||
new_value = min
|
||||
|
||||
if new_value > max and not max == -9223372036854775808:
|
||||
new_value = max
|
||||
|
||||
if old_value == new_value:
|
||||
return
|
||||
|
||||
_info("Added clamped number |%d| to key <%s> in category {%s} (old: |%d|) (new: |%d|)" % [value, key, _get_category_name(category), old_value, new_value])
|
||||
persist_data(key, new_value, category)
|
||||
|
||||
func change_save(index: int):
|
||||
_save()
|
||||
persist_data("save", index)
|
||||
_load()
|
||||
|
||||
|
||||
func save():
|
||||
_save()
|
||||
|
||||
|
||||
func _emit_change(key: String, value: String, category: PersisterEnums.Scope) -> void:
|
||||
var key_category = _get_key_category(key)
|
||||
|
||||
# Only emit change if the category set has the highest priority
|
||||
if key_category == category:
|
||||
if is_nan(int(value)):
|
||||
data_persisted.emit(key, value, category)
|
||||
else:
|
||||
data_persisted.emit(key, int(value), category)
|
||||
|
||||
|
||||
func _emit_removal(key: String, category: PersisterEnums.Scope) -> void:
|
||||
var key_category = _get_key_category(key)
|
||||
|
||||
if key_category == PersisterEnums.Scope.UNKNOWN:
|
||||
return
|
||||
|
||||
var category_priority = _get_category_priority(category)
|
||||
var key_category_priority = _get_category_priority(key_category)
|
||||
|
||||
if category_priority > key_category_priority:
|
||||
var value = _persister[key_category][key]
|
||||
|
||||
if is_nan(int(value)):
|
||||
data_persisted.emit(key, value, key_category)
|
||||
else:
|
||||
data_persisted.emit(key, int(value), key_category)
|
||||
|
||||
|
||||
func _get_category_name(category: PersisterEnums.Scope) -> String:
|
||||
match category:
|
||||
PersisterEnums.Scope.PERMANENT:
|
||||
return "permanent"
|
||||
PersisterEnums.Scope.SAVE:
|
||||
return "save"
|
||||
PersisterEnums.Scope.GAME:
|
||||
return "game"
|
||||
PersisterEnums.Scope.RUN:
|
||||
return "run"
|
||||
PersisterEnums.Scope.ROUND:
|
||||
return "round"
|
||||
PersisterEnums.Scope.ROOM:
|
||||
return "room"
|
||||
_:
|
||||
return "unknown"
|
||||
|
||||
|
||||
func _get_category_priority(category: PersisterEnums.Scope) -> int:
|
||||
match category:
|
||||
PersisterEnums.Scope.PERMANENT:
|
||||
return 1
|
||||
PersisterEnums.Scope.SAVE:
|
||||
return 2
|
||||
PersisterEnums.Scope.GAME:
|
||||
return 3
|
||||
PersisterEnums.Scope.RUN:
|
||||
return 4
|
||||
PersisterEnums.Scope.ROUND:
|
||||
return 5
|
||||
PersisterEnums.Scope.ROOM:
|
||||
return 6
|
||||
_:
|
||||
return 0
|
||||
|
||||
|
||||
func _get_key_category(key: String) -> PersisterEnums.Scope:
|
||||
var category_order = []
|
||||
|
||||
for category in PersisterEnums.Scope:
|
||||
var category_value = PersisterEnums.Scope[category]
|
||||
|
||||
category_order.push_back(
|
||||
{
|
||||
"priority": _get_category_priority(category_value),
|
||||
"category": category_value
|
||||
}
|
||||
)
|
||||
|
||||
category_order.sort_custom(func(a, b):
|
||||
return a.priority > b.priority
|
||||
)
|
||||
|
||||
for value in category_order:
|
||||
var category = value.category
|
||||
|
||||
if _persisted.has(category):
|
||||
if _persisted[category].has(key):
|
||||
return category
|
||||
|
||||
return PersisterEnums.Scope.UNKNOWN
|
||||
|
||||
|
||||
func _get_all_category_data(category: PersisterEnums.Scope) -> Dictionary:
|
||||
if not _persisted.has(category):
|
||||
return {}
|
||||
|
||||
return _persisted[category]
|
||||
|
||||
|
||||
func _on_game_triggered(data: Dictionary) -> void:
|
||||
clear_category(PersisterEnums.Scope.GAME)
|
||||
|
||||
|
||||
func _on_run_triggered(data: Dictionary) -> void:
|
||||
clear_category(PersisterEnums.Scope.RUN)
|
||||
|
||||
|
||||
func _on_round_triggered(data: Dictionary) -> void:
|
||||
clear_category(PersisterEnums.Scope.ROUND)
|
||||
|
||||
|
||||
func _on_room_triggered(data: Dictionary) -> void:
|
||||
clear_category(PersisterEnums.Scope.ROOM)
|
||||
|
||||
|
||||
func _save():
|
||||
_info("Saving")
|
||||
_save_permanent_data()
|
||||
_save_save_data()
|
||||
|
||||
|
||||
func _load():
|
||||
_info("Loading")
|
||||
_load_permanent_data()
|
||||
|
||||
if not get_value("save"):
|
||||
_info("Creating starting save")
|
||||
persist_data("save", 1, PersisterEnums.Scope.PERMANENT)
|
||||
|
||||
clear_category(PersisterEnums.Scope.SAVE)
|
||||
_load_save_data()
|
||||
|
||||
|
||||
func _save_permanent_data():
|
||||
var save_file = FileAccess.open("user://data.json", FileAccess.WRITE)
|
||||
|
||||
var data = _get_all_category_data(PersisterEnums.Scope.PERMANENT)
|
||||
var data_string = JSON.stringify(data)
|
||||
|
||||
_info("Saving permanent save data")
|
||||
|
||||
save_file.store_line(data_string)
|
||||
|
||||
|
||||
func _save_save_data():
|
||||
if not FileAccess.file_exists("user://saves"):
|
||||
DirAccess.make_dir_absolute("user://saves")
|
||||
|
||||
var save = get_value("save")
|
||||
var save_file = FileAccess.open("user://saves/save-%d.json" % [save], FileAccess.WRITE)
|
||||
|
||||
var data = _get_all_category_data(PersisterEnums.Scope.SAVE)
|
||||
var data_string = JSON.stringify(data)
|
||||
|
||||
_info("Saving save save data")
|
||||
|
||||
save_file.store_line(data_string)
|
||||
|
||||
|
||||
func _load_permanent_data():
|
||||
var save_file = FileAccess.open("user://data.json", FileAccess.READ)
|
||||
|
||||
if not save_file:
|
||||
return
|
||||
|
||||
_info("Loading permanent save data")
|
||||
var json_string = ""
|
||||
|
||||
while save_file.get_position() < save_file.get_length():
|
||||
json_string += save_file.get_line()
|
||||
|
||||
var json = JSON.new()
|
||||
|
||||
var result = json.parse(json_string)
|
||||
var data = json.get_data()
|
||||
|
||||
for key in data:
|
||||
Persister.persist_data(key, data[key], PersisterEnums.Scope.PERMANENT)
|
||||
|
||||
|
||||
func _load_save_data():
|
||||
var save = get_value("save")
|
||||
var save_file = FileAccess.open("user://saves/save-%s.json" % [save], FileAccess.READ)
|
||||
|
||||
if not save_file:
|
||||
return
|
||||
|
||||
_info("Loading save save data")
|
||||
var json_string = ""
|
||||
|
||||
while save_file.get_position() < save_file.get_length():
|
||||
json_string += save_file.get_line()
|
||||
|
||||
var json = JSON.new()
|
||||
|
||||
var result = json.parse(json_string)
|
||||
var data = json.get_data()
|
||||
|
||||
Persister.clear_category(PersisterEnums.Scope.SAVE)
|
||||
|
||||
for key in data:
|
||||
Persister.persist_data(key, data[key], PersisterEnums.Scope.SAVE)
|
||||
|
||||
|
||||
func _on_timer_timeout():
|
||||
_save()
|
10
components/Persister/toolbar.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
save[]
|
||||
type: button
|
||||
name: Swap Save 1
|
||||
accent: #11a1a1
|
||||
function: save_one
|
||||
save2[]
|
||||
type: button
|
||||
name: Swap Save 2
|
||||
accent: #11a1a1
|
||||
function: save_two
|
6
components/Triggerer/TriggerReceiver.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://cgivlj3yp8nsy"]
|
||||
|
||||
[ext_resource type="Script" path="res://components/Triggerer/trigger_receiver.gd" id="1_rb2wc"]
|
||||
|
||||
[node name="TriggerReceiver" type="Node"]
|
||||
script = ExtResource("1_rb2wc")
|
6
components/Triggerer/Triggerer.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://rjec7f6cseh"]
|
||||
|
||||
[ext_resource type="Script" path="res://components/Triggerer/triggerer.gd" id="1_e3bhf"]
|
||||
|
||||
[node name="Triggerer" type="Node"]
|
||||
script = ExtResource("1_e3bhf")
|
1
components/Triggerer/antenna.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-antenna"><path d="M2 12 7 2"/><path d="m7 12 5-10"/><path d="m12 12 5-10"/><path d="m17 12 5-10"/><path d="M4.5 7h15"/><path d="M12 16v6"/></svg>
|
After Width: | Height: | Size: 340 B |
37
components/Triggerer/antenna.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://hnqq5c8erlns"
|
||||
path="res://.godot/imported/antenna.svg-9e84cc3637193460d89c85029a059bef.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Triggerer/antenna.svg"
|
||||
dest_files=["res://.godot/imported/antenna.svg-9e84cc3637193460d89c85029a059bef.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
8
components/Triggerer/editor.gd
Normal file
|
@ -0,0 +1,8 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
func _enter_tree():
|
||||
add_custom_type("TriggerReceiver", "Node", preload("res://components/Triggerer/trigger_receiver.gd"), preload("res://components/Triggerer/antenna.svg"))
|
||||
|
||||
func _exit_tree():
|
||||
remove_custom_type("TriggerReceiver")
|
11
components/Triggerer/information.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
name: Triggerer
|
||||
short: A trigger object to trigger things and read in triggers
|
||||
description: An object used for other objects to connect to for triggers.
|
||||
Objects can call the read function with a callback function to
|
||||
read in future triggers of a key to that callback. Objects can
|
||||
then call the trigger function to trigger everything listening
|
||||
to a key.
|
||||
accent: #dada00
|
||||
log_category: TRI
|
||||
icon: res://components/Triggerer/radio.svg
|
||||
version: v1.0.0
|
1
components/Triggerer/radio.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-radio"><path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9"/><path d="M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5"/><circle cx="12" cy="12" r="2"/><path d="M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5"/><path d="M19.1 4.9C23 8.8 23 15.1 19.1 19"/></svg>
|
After Width: | Height: | Size: 412 B |
37
components/Triggerer/radio.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cxpp5fn5c75wj"
|
||||
path="res://.godot/imported/radio.svg-17a71d0d8df639a42271899eb9c43d1e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://components/Triggerer/radio.svg"
|
||||
dest_files=["res://.godot/imported/radio.svg-17a71d0d8df639a42271899eb9c43d1e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
20
components/Triggerer/toolbar.txt
Normal file
|
@ -0,0 +1,20 @@
|
|||
trigger-event[]
|
||||
type: modal
|
||||
name: Trigger Event
|
||||
accent: #11a1a1
|
||||
modal[]
|
||||
name[]
|
||||
type: input
|
||||
name: Event Name
|
||||
|
||||
trigger-event-data[]
|
||||
type: modal
|
||||
name: Trigger Event w/ Data
|
||||
accent: #2181c1
|
||||
modal[]
|
||||
name[]
|
||||
type: input
|
||||
name: Event Name
|
||||
data[]
|
||||
type: dictionary
|
||||
name: Event Data
|
22
components/Triggerer/trigger_receiver.gd
Normal file
|
@ -0,0 +1,22 @@
|
|||
@icon("res://components/Triggerer/antenna.svg")
|
||||
class_name TriggerReceiver
|
||||
extends Node
|
||||
|
||||
signal received(data: Dictionary)
|
||||
|
||||
@export var keys: Array[String]
|
||||
|
||||
var _triggerer
|
||||
|
||||
|
||||
func _ready():
|
||||
if get_tree().root.has_node("Triggerer"):
|
||||
_triggerer = get_tree().root.get_node("Triggerer")
|
||||
|
||||
if _triggerer:
|
||||
for key in keys:
|
||||
_triggerer.listen(key, _on_received)
|
||||
|
||||
|
||||
func _on_received(data: Dictionary):
|
||||
received.emit(data)
|
40
components/Triggerer/triggerer.gd
Normal file
|
@ -0,0 +1,40 @@
|
|||
@icon("res://components/Triggerer/radio.svg")
|
||||
extends Base
|
||||
## A trigger object to trigger things and read in triggers
|
||||
##
|
||||
## An object used for other objects to connect to for triggers.
|
||||
## Objects can call the read function with a callback function to
|
||||
## read in future triggers of a key to that callback. Objects can
|
||||
## then call the trigger function to trigger everything listening
|
||||
## to a key.
|
||||
|
||||
var _connections = {}
|
||||
|
||||
|
||||
## Trigger an event to be read in by other objects
|
||||
func trigger(key: String, data: Dictionary = {}) -> void:
|
||||
_info("Triggered key ∧%s∧" % [key])
|
||||
|
||||
data.trigger = key
|
||||
|
||||
if _connections.has(key):
|
||||
_connections[key] = _connections[key].filter(func(x): return x.is_valid())
|
||||
|
||||
for callback in _connections[key]:
|
||||
callback.call(data)
|
||||
|
||||
if _connections.has("any"):
|
||||
_connections["any"] = _connections["any"].filter(func(x): return x.is_valid())
|
||||
|
||||
for callback in _connections["any"]:
|
||||
callback.call(data)
|
||||
|
||||
|
||||
## Read in future triggers of a key to a callback
|
||||
func listen(key: String, callback: Callable) -> void:
|
||||
if not _connections.has(key):
|
||||
_info("Created new connection key ∧%s∧" % [key])
|
||||
_connections[key] = []
|
||||
|
||||
_info("Callback ∨%s∨ on →%s← added to key ∧%s∧" % [callback.get_method(), callback.get_object().name, key])
|
||||
_connections[key].push_back(callback)
|
6
components/Unlocks/Unlocks.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://cwcttvh7s42ca"]
|
||||
|
||||
[ext_resource type="Script" path="res://components/Unlocks/unlocks.gd" id="1_ege4w"]
|
||||
|
||||
[node name="Unlocks" type="Node"]
|
||||
script = ExtResource("1_ege4w")
|
0
components/Unlocks/information.txt
Normal file
127
components/Unlocks/unlocks.gd
Normal file
|
@ -0,0 +1,127 @@
|
|||
class_name UnlocksType
|
||||
extends Base
|
||||
|
||||
# Signals
|
||||
|
||||
# Constants
|
||||
const LOG_CATEGORY = "LOCK"
|
||||
|
||||
var unlocks = {}
|
||||
|
||||
func unlock_item(key: String, category: String):
|
||||
if not unlocks.has(category):
|
||||
if _logger: _logger.error("Attempted to unlock item %s in invalid category %s" % [key, category], LOG_CATEGORY)
|
||||
return
|
||||
|
||||
if not unlocks[category].has(key):
|
||||
if _logger: _logger.error("Attempted to unlock invalid item %s in category %s" % [key, category], LOG_CATEGORY)
|
||||
return
|
||||
|
||||
if unlocks[category][key]:
|
||||
if _logger: _logger.warn("Unlocked already unlocked item %s in category %s" % [key, category], LOG_CATEGORY)
|
||||
|
||||
unlocks[category][key].status = true
|
||||
if _logger: _logger.info("Unlocked item %s in category %s" % [key, category], LOG_CATEGORY)
|
||||
|
||||
|
||||
func lock_item(key: String, category: String):
|
||||
if not unlocks.has(category):
|
||||
if _logger: _logger.error("Attempted to lock item %s in invalid category %s" % [key, category], LOG_CATEGORY)
|
||||
return
|
||||
|
||||
if not unlocks[category].has(key):
|
||||
if _logger: _logger.error("Attempted to lock invalid item %s in category %s" % [key, category], LOG_CATEGORY)
|
||||
return
|
||||
|
||||
if not unlocks[category][key]:
|
||||
if _logger: _logger.warn("Locked already locked item %s in category %s" % [key, category], LOG_CATEGORY)
|
||||
|
||||
unlocks[category][key].status = false
|
||||
if _logger: _logger.info("Locked item %s in category %s" % [key, category], LOG_CATEGORY)
|
||||
|
||||
|
||||
func reset_unlocks():
|
||||
if (_data):
|
||||
unlocks = {}
|
||||
|
||||
for category in _data.data:
|
||||
var category_data = _data.data[category]
|
||||
|
||||
if category_data.has("!_config"):
|
||||
if category_data["!_config"].has("unlock"):
|
||||
if category_data["!_config"]["unlock"] == "true":
|
||||
unlocks[category] = {}
|
||||
|
||||
for key in category_data:
|
||||
if key == "!_config":
|
||||
continue
|
||||
|
||||
var element = category_data[key]
|
||||
|
||||
if element.has("locked"):
|
||||
if element["locked"] == "true":
|
||||
if not element.has("unlock_trigger"):
|
||||
if _logger: _logger.warn("Locked element %s in category %s has no unlock trigger" % [key, category], LOG_CATEGORY)
|
||||
unlocks[category][key] = {
|
||||
"status": false
|
||||
}
|
||||
continue
|
||||
|
||||
if not element.has("unlock_amount"):
|
||||
if _logger: _logger.warn("Locked element %s in category %s has no unlock amount" % [key, category], LOG_CATEGORY)
|
||||
unlocks[category][key] = {
|
||||
"status": false
|
||||
}
|
||||
continue
|
||||
|
||||
if is_nan(int(element["unlock_amount"])):
|
||||
if _logger: _logger.warn("Locked element %s in category %s unlock amount %s is not a number" % [key, category, element["unlock_amount"]], LOG_CATEGORY)
|
||||
unlocks[category][key] = {
|
||||
"status": false
|
||||
}
|
||||
continue
|
||||
|
||||
unlocks[category][key] = {
|
||||
"status": false,
|
||||
"trigger": element["unlock_trigger"],
|
||||
"amount": int(element["unlock_amount"])
|
||||
}
|
||||
else:
|
||||
unlocks[category][key] = {
|
||||
"status": true
|
||||
}
|
||||
else:
|
||||
unlocks[category][key] = {
|
||||
"status": true
|
||||
}
|
||||
if _logger: _logger.info("Reset Unlocks", LOG_CATEGORY)
|
||||
|
||||
func _ready():
|
||||
super()
|
||||
|
||||
reset_unlocks()
|
||||
|
||||
|
||||
#func _on_number_persisted(key: String, value, category: PersisterType.DataCategory):
|
||||
#_check_trigger(key, value)
|
||||
|
||||
|
||||
func _check_trigger(key: String, value: int):
|
||||
for category in unlocks:
|
||||
var category_data = unlocks[category]
|
||||
|
||||
for element in category_data:
|
||||
var element_data = category_data[element]
|
||||
|
||||
if not element_data.has("trigger"):
|
||||
continue
|
||||
|
||||
if element_data["trigger"] != key:
|
||||
continue
|
||||
|
||||
if element_data["status"]:
|
||||
continue
|
||||
|
||||
if _logger: _logger.debug("Checking trigger %s for item %s in category %s: %d (required) vs %d (incoming)" % [key, element, category, element_data["amount"], value], LOG_CATEGORY)
|
||||
if value > element_data["amount"]:
|
||||
unlock_item(element, category)
|
1
icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
After Width: | Height: | Size: 994 B |
37
icon.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bi03hh1ousovx"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
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/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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
36
project.godot
Normal file
|
@ -0,0 +1,36 @@
|
|||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="brackeys-12"
|
||||
run/main_scene="res://Main.tscn"
|
||||
config/features=PackedStringArray("4.3", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
Logger="*res://components/Logger/Logger.tscn"
|
||||
Data="*res://components/Data/Data.tscn"
|
||||
Triggerer="*res://components/Triggerer/Triggerer.tscn"
|
||||
Persister="*res://components/Persister/Persister.tscn"
|
||||
Achievements="*res://components/Achievements/Achievements.tscn"
|
||||
Cursor="*res://components/Cursor/Cursor.tscn"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=640
|
||||
window/size/viewport_height=360
|
||||
window/stretch/mode="canvas_items"
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
3
theme.tres
Normal file
|
@ -0,0 +1,3 @@
|
|||
[gd_resource type="Theme" format=3 uid="uid://d035h7upxrw3h"]
|
||||
|
||||
[resource]
|