Initial commit

This commit is contained in:
Ategon 2024-09-08 13:34:41 -04:00
commit 31c1d81f77
90 changed files with 2661 additions and 0 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

12
Main.tscn Normal file
View 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")

View 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"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

View 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

Binary file not shown.

View 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

Binary file not shown.

View 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

View 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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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

View 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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View 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
View 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 })

View 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"]

View 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")

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View 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

Binary file not shown.

View 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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View 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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View 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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View 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

View file

@ -0,0 +1,6 @@
name: Cursor
short: ???
description: ???
accent: #35b84b
log_category: CUR
icon: res://components/Cursor/mouse-pointer-2.svg

View 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

View 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

View 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

View 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

View 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"
})

Binary file not shown.

BIN
components/Cursor/saw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View 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

View 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")

View 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

View 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

View 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
View 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

View 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

View 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

View 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")

View file

View 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

View 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
View 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

View 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

View 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

View 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

View 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

View 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"]

View 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
}

View 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

View 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

View 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

View file

@ -0,0 +1,5 @@
func save_one(parent):
parent.change_save(1)
func save_two(parent):
parent.change_save(2)

View 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()

View 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

View 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")

View 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")

View 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

View 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

View 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")

View 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

View 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

View 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

View 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

View 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)

View 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)

View 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")

View file

View 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
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
[gd_resource type="Theme" format=3 uid="uid://d035h7upxrw3h"]
[resource]