diff --git a/assets/audio/OGG/SFX/Chopping and Mining/chop 1.ogg.import b/assets/audio/OGG/SFX/Chopping and Mining/chop 1.ogg.import
deleted file mode 100644
index c64d022..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/chop 1.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://dk4uuqs15ke73"
-path="res://.godot/imported/chop 1.ogg-b69af87df03a0c5fe070f85eb271d215.oggvorbisstr"
-
-[deps]
-
-source_file="res://assets/audio/OGG/SFX/Chopping and Mining/chop 1.ogg"
-dest_files=["res://.godot/imported/chop 1.ogg-b69af87df03a0c5fe070f85eb271d215.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/chop 2.ogg.import b/assets/audio/OGG/SFX/Chopping and Mining/chop 2.ogg.import
deleted file mode 100644
index bed697e..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/chop 2.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://cu0or87uh4tnx"
-path="res://.godot/imported/chop 2.ogg-3c1aa9e3d1bef67928f70428ba3baf67.oggvorbisstr"
-
-[deps]
-
-source_file="res://assets/audio/OGG/SFX/Chopping and Mining/chop 2.ogg"
-dest_files=["res://.godot/imported/chop 2.ogg-3c1aa9e3d1bef67928f70428ba3baf67.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/chop 3.ogg.import b/assets/audio/OGG/SFX/Chopping and Mining/chop 3.ogg.import
deleted file mode 100644
index 88cc4fa..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/chop 3.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://dyeihn6rpcexy"
-path="res://.godot/imported/chop 3.ogg-cbdf52dc00aee6e6fc66cc6cdf1d505c.oggvorbisstr"
-
-[deps]
-
-source_file="res://assets/audio/OGG/SFX/Chopping and Mining/chop 3.ogg"
-dest_files=["res://.godot/imported/chop 3.ogg-cbdf52dc00aee6e6fc66cc6cdf1d505c.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/chop 4.ogg.import b/assets/audio/OGG/SFX/Chopping and Mining/chop 4.ogg.import
deleted file mode 100644
index 7b485b1..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/chop 4.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://dtv1te54cfra1"
-path="res://.godot/imported/chop 4.ogg-3b75c4df4d131e29f8d9237341d5fdc8.oggvorbisstr"
-
-[deps]
-
-source_file="res://assets/audio/OGG/SFX/Chopping and Mining/chop 4.ogg"
-dest_files=["res://.godot/imported/chop 4.ogg-3b75c4df4d131e29f8d9237341d5fdc8.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 1.ogg b/assets/audio/OGG/SFX/Chopping and Mining/mine 1.ogg
deleted file mode 100644
index af0347c..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 1.ogg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:7a482bc0dd0336b678e7bb2ee0485df6dc2cdd649ea6136f015984ee686b69b9
-size 33072
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 1.ogg.import b/assets/audio/OGG/SFX/Chopping and Mining/mine 1.ogg.import
deleted file mode 100644
index 5c7afd5..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 1.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://ciigu6xv4een2"
-path="res://.godot/imported/mine 1.ogg-7d18662074fe361b38d431c8b05adfef.oggvorbisstr"
-
-[deps]
-
-source_file="res://assets/audio/OGG/SFX/Chopping and Mining/mine 1.ogg"
-dest_files=["res://.godot/imported/mine 1.ogg-7d18662074fe361b38d431c8b05adfef.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 2.ogg b/assets/audio/OGG/SFX/Chopping and Mining/mine 2.ogg
deleted file mode 100644
index 8c7d489..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 2.ogg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:efb291c33b90172234cb2c96b9cb0733d712f3663998a394ce653017ece6a0b2
-size 30937
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 2.ogg.import b/assets/audio/OGG/SFX/Chopping and Mining/mine 2.ogg.import
deleted file mode 100644
index 2b591f0..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 2.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://dvymfro1mgu26"
-path="res://.godot/imported/mine 2.ogg-ddfea9d88f0eaa481725560e89c016d4.oggvorbisstr"
-
-[deps]
-
-source_file="res://assets/audio/OGG/SFX/Chopping and Mining/mine 2.ogg"
-dest_files=["res://.godot/imported/mine 2.ogg-ddfea9d88f0eaa481725560e89c016d4.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 3.ogg b/assets/audio/OGG/SFX/Chopping and Mining/mine 3.ogg
deleted file mode 100644
index c73fb16..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 3.ogg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:4d0181feb17087d62da7cb2eba216cc1362567c48e086ef7e8757561b20350fd
-size 26080
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 3.ogg.import b/assets/audio/OGG/SFX/Chopping and Mining/mine 3.ogg.import
deleted file mode 100644
index 8cbcf32..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 3.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://df271xfcx0a7b"
-path="res://.godot/imported/mine 3.ogg-217d1add25379185f600351d973c4748.oggvorbisstr"
-
-[deps]
-
-source_file="res://assets/audio/OGG/SFX/Chopping and Mining/mine 3.ogg"
-dest_files=["res://.godot/imported/mine 3.ogg-217d1add25379185f600351d973c4748.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 4.ogg b/assets/audio/OGG/SFX/Chopping and Mining/mine 4.ogg
deleted file mode 100644
index 44a511e..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 4.ogg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:e9f7d0b76e8fc9d0ff70e819208763698fca099a657f147ad33cbc48abfb15cb
-size 32489
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 4.ogg.import b/assets/audio/OGG/SFX/Chopping and Mining/mine 4.ogg.import
deleted file mode 100644
index 27d769d..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 4.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://bqdbaorkkkg8o"
-path="res://.godot/imported/mine 4.ogg-9f72de7d0c734b4128ba92bef9b7b50f.oggvorbisstr"
-
-[deps]
-
-source_file="res://assets/audio/OGG/SFX/Chopping and Mining/mine 4.ogg"
-dest_files=["res://.godot/imported/mine 4.ogg-9f72de7d0c734b4128ba92bef9b7b50f.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 5.ogg b/assets/audio/OGG/SFX/Chopping and Mining/mine 5.ogg
deleted file mode 100644
index f652787..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 5.ogg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:1a0f02a72aafb81b01eb971391bb3d348d0a8676334b8926f477f2b8873425a2
-size 31572
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/mine 5.ogg.import b/assets/audio/OGG/SFX/Chopping and Mining/mine 5.ogg.import
deleted file mode 100644
index 6e155fc..0000000
--- a/assets/audio/OGG/SFX/Chopping and Mining/mine 5.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://dpbdn7vgrkbvg"
-path="res://.godot/imported/mine 5.ogg-fefc870733e0b64c3ccf27b6e4d44e3f.oggvorbisstr"
-
-[deps]
-
-source_file="res://assets/audio/OGG/SFX/Chopping and Mining/mine 5.ogg"
-dest_files=["res://.godot/imported/mine 5.ogg-fefc870733e0b64c3ccf27b6e4d44e3f.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/.DS_Store b/assets/audio/OGG/SFX/chopping/.DS_Store
similarity index 100%
rename from assets/audio/OGG/SFX/Chopping and Mining/.DS_Store
rename to assets/audio/OGG/SFX/chopping/.DS_Store
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/chop 1.ogg b/assets/audio/OGG/SFX/chopping/chop1.ogg
similarity index 100%
rename from assets/audio/OGG/SFX/Chopping and Mining/chop 1.ogg
rename to assets/audio/OGG/SFX/chopping/chop1.ogg
diff --git a/assets/audio/OGG/SFX/chopping/chop1.ogg.import b/assets/audio/OGG/SFX/chopping/chop1.ogg.import
new file mode 100644
index 0000000..841d951
--- /dev/null
+++ b/assets/audio/OGG/SFX/chopping/chop1.ogg.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="oggvorbisstr"
+type="AudioStreamOggVorbis"
+uid="uid://dk4uuqs15ke73"
+path="res://.godot/imported/chop1.ogg-f4598c93b23f97c2d4f238891e3ff73f.oggvorbisstr"
+
+[deps]
+
+source_file="res://assets/audio/OGG/SFX/chopping/chop1.ogg"
+dest_files=["res://.godot/imported/chop1.ogg-f4598c93b23f97c2d4f238891e3ff73f.oggvorbisstr"]
+
+[params]
+
+loop=false
+loop_offset=0
+bpm=0
+beat_count=0
+bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/chop 2.ogg b/assets/audio/OGG/SFX/chopping/chop2.ogg
similarity index 100%
rename from assets/audio/OGG/SFX/Chopping and Mining/chop 2.ogg
rename to assets/audio/OGG/SFX/chopping/chop2.ogg
diff --git a/assets/audio/OGG/SFX/chopping/chop2.ogg.import b/assets/audio/OGG/SFX/chopping/chop2.ogg.import
new file mode 100644
index 0000000..ebd1268
--- /dev/null
+++ b/assets/audio/OGG/SFX/chopping/chop2.ogg.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="oggvorbisstr"
+type="AudioStreamOggVorbis"
+uid="uid://cu0or87uh4tnx"
+path="res://.godot/imported/chop2.ogg-c9974775002e292c3a0954ff6ec0cf49.oggvorbisstr"
+
+[deps]
+
+source_file="res://assets/audio/OGG/SFX/chopping/chop2.ogg"
+dest_files=["res://.godot/imported/chop2.ogg-c9974775002e292c3a0954ff6ec0cf49.oggvorbisstr"]
+
+[params]
+
+loop=false
+loop_offset=0
+bpm=0
+beat_count=0
+bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/chop 3.ogg b/assets/audio/OGG/SFX/chopping/chop3.ogg
similarity index 100%
rename from assets/audio/OGG/SFX/Chopping and Mining/chop 3.ogg
rename to assets/audio/OGG/SFX/chopping/chop3.ogg
diff --git a/assets/audio/OGG/SFX/chopping/chop3.ogg.import b/assets/audio/OGG/SFX/chopping/chop3.ogg.import
new file mode 100644
index 0000000..33e0c50
--- /dev/null
+++ b/assets/audio/OGG/SFX/chopping/chop3.ogg.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="oggvorbisstr"
+type="AudioStreamOggVorbis"
+uid="uid://dyeihn6rpcexy"
+path="res://.godot/imported/chop3.ogg-d2a5ffcab1d1558f23fdf93ca7a3b6da.oggvorbisstr"
+
+[deps]
+
+source_file="res://assets/audio/OGG/SFX/chopping/chop3.ogg"
+dest_files=["res://.godot/imported/chop3.ogg-d2a5ffcab1d1558f23fdf93ca7a3b6da.oggvorbisstr"]
+
+[params]
+
+loop=false
+loop_offset=0
+bpm=0
+beat_count=0
+bar_beats=4
diff --git a/assets/audio/OGG/SFX/Chopping and Mining/chop 4.ogg b/assets/audio/OGG/SFX/chopping/chop4.ogg
similarity index 100%
rename from assets/audio/OGG/SFX/Chopping and Mining/chop 4.ogg
rename to assets/audio/OGG/SFX/chopping/chop4.ogg
diff --git a/assets/audio/OGG/SFX/chopping/chop4.ogg.import b/assets/audio/OGG/SFX/chopping/chop4.ogg.import
new file mode 100644
index 0000000..78a6867
--- /dev/null
+++ b/assets/audio/OGG/SFX/chopping/chop4.ogg.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="oggvorbisstr"
+type="AudioStreamOggVorbis"
+uid="uid://dtv1te54cfra1"
+path="res://.godot/imported/chop4.ogg-930a76e627570bb0a5bcd1e12c512a03.oggvorbisstr"
+
+[deps]
+
+source_file="res://assets/audio/OGG/SFX/chopping/chop4.ogg"
+dest_files=["res://.godot/imported/chop4.ogg-930a76e627570bb0a5bcd1e12c512a03.oggvorbisstr"]
+
+[params]
+
+loop=false
+loop_offset=0
+bpm=0
+beat_count=0
+bar_beats=4
diff --git a/assets/audio/coin.mp3 b/assets/audio/coin.mp3
new file mode 100644
index 0000000..58c6841
--- /dev/null
+++ b/assets/audio/coin.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:05439ef240ed7bedd12e462854e309ec02c8de04a3787b1ac5f312b63b631194
+size 12260
diff --git a/assets/audio/coin.mp3.import b/assets/audio/coin.mp3.import
new file mode 100644
index 0000000..cda7a7a
--- /dev/null
+++ b/assets/audio/coin.mp3.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="mp3"
+type="AudioStreamMP3"
+uid="uid://c08bib4emn512"
+path="res://.godot/imported/coin.mp3-5bd517a0009591b634e39f6a9642e1fc.mp3str"
+
+[deps]
+
+source_file="res://assets/audio/coin.mp3"
+dest_files=["res://.godot/imported/coin.mp3-5bd517a0009591b634e39f6a9642e1fc.mp3str"]
+
+[params]
+
+loop=false
+loop_offset=0
+bpm=0
+beat_count=0
+bar_beats=4
diff --git a/assets/characters/animals/Clucking Chicken/CluckingChicken.aseprite b/assets/characters/animals/Clucking Chicken/CluckingChicken.aseprite
new file mode 100644
index 0000000..b205bf4
Binary files /dev/null and b/assets/characters/animals/Clucking Chicken/CluckingChicken.aseprite differ
diff --git a/assets/characters/animals/Clucking Chicken/CluckingChicken.gif b/assets/characters/animals/Clucking Chicken/CluckingChicken.gif
new file mode 100644
index 0000000..8522b1b
Binary files /dev/null and b/assets/characters/animals/Clucking Chicken/CluckingChicken.gif differ
diff --git a/assets/characters/animals/Clucking Chicken/CluckingChicken.png b/assets/characters/animals/Clucking Chicken/CluckingChicken.png
new file mode 100644
index 0000000..527ed7e
Binary files /dev/null and b/assets/characters/animals/Clucking Chicken/CluckingChicken.png differ
diff --git a/icon.svg.import b/assets/characters/animals/Clucking Chicken/CluckingChicken.png.import
similarity index 69%
rename from icon.svg.import
rename to assets/characters/animals/Clucking Chicken/CluckingChicken.png.import
index e725647..fd98dc1 100644
--- a/icon.svg.import
+++ b/assets/characters/animals/Clucking Chicken/CluckingChicken.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://dytbdi8k6f5oj"
-path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
+uid="uid://brfch42cll8qs"
+path="res://.godot/imported/CluckingChicken.png-9269d537f9696347fe906016c6c5b621.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://icon.svg"
-dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
+source_file="res://assets/characters/animals/Clucking Chicken/CluckingChicken.png"
+dest_files=["res://.godot/imported/CluckingChicken.png-9269d537f9696347fe906016c6c5b621.ctex"]
[params]
@@ -38,6 +38,3 @@ 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
diff --git a/assets/characters/animals/Coral Crab/CoralCrab.aseprite b/assets/characters/animals/Coral Crab/CoralCrab.aseprite
new file mode 100644
index 0000000..8ef42c4
Binary files /dev/null and b/assets/characters/animals/Coral Crab/CoralCrab.aseprite differ
diff --git a/assets/characters/animals/Coral Crab/CoralCrab.gif b/assets/characters/animals/Coral Crab/CoralCrab.gif
new file mode 100644
index 0000000..cc2f82d
Binary files /dev/null and b/assets/characters/animals/Coral Crab/CoralCrab.gif differ
diff --git a/assets/characters/animals/Coral Crab/CoralCrab.png b/assets/characters/animals/Coral Crab/CoralCrab.png
new file mode 100644
index 0000000..e89b532
Binary files /dev/null and b/assets/characters/animals/Coral Crab/CoralCrab.png differ
diff --git a/assets/characters/animals/Coral Crab/CoralCrab.png.import b/assets/characters/animals/Coral Crab/CoralCrab.png.import
new file mode 100644
index 0000000..b32da11
--- /dev/null
+++ b/assets/characters/animals/Coral Crab/CoralCrab.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ro8a7khvhpbi"
+path="res://.godot/imported/CoralCrab.png-d94278909b6d7f3fd928af0b32655ef0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Coral Crab/CoralCrab.png"
+dest_files=["res://.godot/imported/CoralCrab.png-d94278909b6d7f3fd928af0b32655ef0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Croaking Toad/CroakingToad.aseprite b/assets/characters/animals/Croaking Toad/CroakingToad.aseprite
new file mode 100644
index 0000000..ace11e3
Binary files /dev/null and b/assets/characters/animals/Croaking Toad/CroakingToad.aseprite differ
diff --git a/assets/characters/animals/Croaking Toad/CroakingToad.gif b/assets/characters/animals/Croaking Toad/CroakingToad.gif
new file mode 100644
index 0000000..29bb779
Binary files /dev/null and b/assets/characters/animals/Croaking Toad/CroakingToad.gif differ
diff --git a/assets/characters/animals/Croaking Toad/CroakingToad.png b/assets/characters/animals/Croaking Toad/CroakingToad.png
new file mode 100644
index 0000000..4a6c3fc
Binary files /dev/null and b/assets/characters/animals/Croaking Toad/CroakingToad.png differ
diff --git a/assets/characters/animals/Croaking Toad/CroakingToad.png.import b/assets/characters/animals/Croaking Toad/CroakingToad.png.import
new file mode 100644
index 0000000..e29d3a0
--- /dev/null
+++ b/assets/characters/animals/Croaking Toad/CroakingToad.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://rvdwc66bww3"
+path="res://.godot/imported/CroakingToad.png-fb812ca808291c048fac7fdaa36878d0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Croaking Toad/CroakingToad.png"
+dest_files=["res://.godot/imported/CroakingToad.png-fb812ca808291c048fac7fdaa36878d0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Dainty Pig/DaintyPig.aseprite b/assets/characters/animals/Dainty Pig/DaintyPig.aseprite
new file mode 100644
index 0000000..d8a6064
Binary files /dev/null and b/assets/characters/animals/Dainty Pig/DaintyPig.aseprite differ
diff --git a/assets/characters/animals/Dainty Pig/DaintyPig.gif b/assets/characters/animals/Dainty Pig/DaintyPig.gif
new file mode 100644
index 0000000..ea8268f
Binary files /dev/null and b/assets/characters/animals/Dainty Pig/DaintyPig.gif differ
diff --git a/assets/characters/animals/Dainty Pig/DaintyPig.png b/assets/characters/animals/Dainty Pig/DaintyPig.png
new file mode 100644
index 0000000..9968817
Binary files /dev/null and b/assets/characters/animals/Dainty Pig/DaintyPig.png differ
diff --git a/assets/characters/animals/Dainty Pig/DaintyPig.png.import b/assets/characters/animals/Dainty Pig/DaintyPig.png.import
new file mode 100644
index 0000000..85c3490
--- /dev/null
+++ b/assets/characters/animals/Dainty Pig/DaintyPig.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d2dpt56hf53vv"
+path="res://.godot/imported/DaintyPig.png-1fa1d8560b1693c9ba5edaa5ddd52345.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Dainty Pig/DaintyPig.png"
+dest_files=["res://.godot/imported/DaintyPig.png-1fa1d8560b1693c9ba5edaa5ddd52345.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Honking Goose/HonkingGoose.aseprite b/assets/characters/animals/Honking Goose/HonkingGoose.aseprite
new file mode 100644
index 0000000..6b051b5
Binary files /dev/null and b/assets/characters/animals/Honking Goose/HonkingGoose.aseprite differ
diff --git a/assets/characters/animals/Honking Goose/HonkingGoose.gif b/assets/characters/animals/Honking Goose/HonkingGoose.gif
new file mode 100644
index 0000000..3badda7
Binary files /dev/null and b/assets/characters/animals/Honking Goose/HonkingGoose.gif differ
diff --git a/assets/characters/animals/Honking Goose/HonkingGoose.png b/assets/characters/animals/Honking Goose/HonkingGoose.png
new file mode 100644
index 0000000..eeb674e
Binary files /dev/null and b/assets/characters/animals/Honking Goose/HonkingGoose.png differ
diff --git a/assets/characters/animals/Honking Goose/HonkingGoose.png.import b/assets/characters/animals/Honking Goose/HonkingGoose.png.import
new file mode 100644
index 0000000..086867e
--- /dev/null
+++ b/assets/characters/animals/Honking Goose/HonkingGoose.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://drghlunsulio"
+path="res://.godot/imported/HonkingGoose.png-a80027fda17c6e2accb6a5c9a61c188f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Honking Goose/HonkingGoose.png"
+dest_files=["res://.godot/imported/HonkingGoose.png-a80027fda17c6e2accb6a5c9a61c188f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Leaping Frog/LeapingFrog.aseprite b/assets/characters/animals/Leaping Frog/LeapingFrog.aseprite
new file mode 100644
index 0000000..7a9588a
Binary files /dev/null and b/assets/characters/animals/Leaping Frog/LeapingFrog.aseprite differ
diff --git a/assets/characters/animals/Leaping Frog/LeapingFrog.gif b/assets/characters/animals/Leaping Frog/LeapingFrog.gif
new file mode 100644
index 0000000..614c670
Binary files /dev/null and b/assets/characters/animals/Leaping Frog/LeapingFrog.gif differ
diff --git a/assets/characters/animals/Leaping Frog/LeapingFrog.png b/assets/characters/animals/Leaping Frog/LeapingFrog.png
new file mode 100644
index 0000000..d035d2e
Binary files /dev/null and b/assets/characters/animals/Leaping Frog/LeapingFrog.png differ
diff --git a/assets/characters/animals/Leaping Frog/LeapingFrog.png.import b/assets/characters/animals/Leaping Frog/LeapingFrog.png.import
new file mode 100644
index 0000000..55da402
--- /dev/null
+++ b/assets/characters/animals/Leaping Frog/LeapingFrog.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://mprebvbv6gms"
+path="res://.godot/imported/LeapingFrog.png-057f3b71b6b5e09bb7754ccf5be7f652.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Leaping Frog/LeapingFrog.png"
+dest_files=["res://.godot/imported/LeapingFrog.png-057f3b71b6b5e09bb7754ccf5be7f652.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Mad Boar/MadBoar.aseprite b/assets/characters/animals/Mad Boar/MadBoar.aseprite
new file mode 100644
index 0000000..d8d5509
Binary files /dev/null and b/assets/characters/animals/Mad Boar/MadBoar.aseprite differ
diff --git a/assets/characters/animals/Mad Boar/MadBoar.gif b/assets/characters/animals/Mad Boar/MadBoar.gif
new file mode 100644
index 0000000..3fbf248
Binary files /dev/null and b/assets/characters/animals/Mad Boar/MadBoar.gif differ
diff --git a/assets/characters/animals/Mad Boar/MadBoar.png b/assets/characters/animals/Mad Boar/MadBoar.png
new file mode 100644
index 0000000..ba2208b
Binary files /dev/null and b/assets/characters/animals/Mad Boar/MadBoar.png differ
diff --git a/assets/characters/animals/Mad Boar/MadBoar.png.import b/assets/characters/animals/Mad Boar/MadBoar.png.import
new file mode 100644
index 0000000..032e018
--- /dev/null
+++ b/assets/characters/animals/Mad Boar/MadBoar.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://lvaakhex8snn"
+path="res://.godot/imported/MadBoar.png-b446fcf6ed52b7d39df2c522d9fe58ec.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Mad Boar/MadBoar.png"
+dest_files=["res://.godot/imported/MadBoar.png-b446fcf6ed52b7d39df2c522d9fe58ec.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Meowing Cat/MeowingCat.aseprite b/assets/characters/animals/Meowing Cat/MeowingCat.aseprite
new file mode 100644
index 0000000..75771a4
Binary files /dev/null and b/assets/characters/animals/Meowing Cat/MeowingCat.aseprite differ
diff --git a/assets/characters/animals/Meowing Cat/MeowingCat.gif b/assets/characters/animals/Meowing Cat/MeowingCat.gif
new file mode 100644
index 0000000..21ff081
Binary files /dev/null and b/assets/characters/animals/Meowing Cat/MeowingCat.gif differ
diff --git a/assets/characters/animals/Meowing Cat/MeowingCat.png b/assets/characters/animals/Meowing Cat/MeowingCat.png
new file mode 100644
index 0000000..a32ff03
Binary files /dev/null and b/assets/characters/animals/Meowing Cat/MeowingCat.png differ
diff --git a/assets/characters/animals/Meowing Cat/MeowingCat.png.import b/assets/characters/animals/Meowing Cat/MeowingCat.png.import
new file mode 100644
index 0000000..078577c
--- /dev/null
+++ b/assets/characters/animals/Meowing Cat/MeowingCat.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dccs841gqvsnr"
+path="res://.godot/imported/MeowingCat.png-46c7da26115980fa28ba085ab6f5d447.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Meowing Cat/MeowingCat.png"
+dest_files=["res://.godot/imported/MeowingCat.png-46c7da26115980fa28ba085ab6f5d447.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Pasturing Sheep/PasturingSheep.aseprite b/assets/characters/animals/Pasturing Sheep/PasturingSheep.aseprite
new file mode 100644
index 0000000..b4031e6
Binary files /dev/null and b/assets/characters/animals/Pasturing Sheep/PasturingSheep.aseprite differ
diff --git a/assets/characters/animals/Pasturing Sheep/PasturingSheep.gif b/assets/characters/animals/Pasturing Sheep/PasturingSheep.gif
new file mode 100644
index 0000000..68cb642
Binary files /dev/null and b/assets/characters/animals/Pasturing Sheep/PasturingSheep.gif differ
diff --git a/assets/characters/animals/Pasturing Sheep/PasturingSheep.png b/assets/characters/animals/Pasturing Sheep/PasturingSheep.png
new file mode 100644
index 0000000..6e3d9c1
Binary files /dev/null and b/assets/characters/animals/Pasturing Sheep/PasturingSheep.png differ
diff --git a/assets/characters/animals/Pasturing Sheep/PasturingSheep.png.import b/assets/characters/animals/Pasturing Sheep/PasturingSheep.png.import
new file mode 100644
index 0000000..7856e23
--- /dev/null
+++ b/assets/characters/animals/Pasturing Sheep/PasturingSheep.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cvg3idufs050w"
+path="res://.godot/imported/PasturingSheep.png-aed02be6fa372b65f45c92ee8c6681b5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Pasturing Sheep/PasturingSheep.png"
+dest_files=["res://.godot/imported/PasturingSheep.png-aed02be6fa372b65f45c92ee8c6681b5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/SleepDog.png b/assets/characters/animals/SleepDog.png
new file mode 100644
index 0000000..2f269d3
Binary files /dev/null and b/assets/characters/animals/SleepDog.png differ
diff --git a/assets/characters/animals/SleepDog.png.import b/assets/characters/animals/SleepDog.png.import
new file mode 100644
index 0000000..355b1ad
--- /dev/null
+++ b/assets/characters/animals/SleepDog.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b7hgiys1h18ov"
+path="res://.godot/imported/SleepDog.png-5a732d01d494bef231adf10678a32db9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/SleepDog.png"
+dest_files=["res://.godot/imported/SleepDog.png-5a732d01d494bef231adf10678a32db9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Slow Turtle/SlowTurtle.aseprite b/assets/characters/animals/Slow Turtle/SlowTurtle.aseprite
new file mode 100644
index 0000000..ff7c480
Binary files /dev/null and b/assets/characters/animals/Slow Turtle/SlowTurtle.aseprite differ
diff --git a/assets/characters/animals/Slow Turtle/SlowTurtle.gif b/assets/characters/animals/Slow Turtle/SlowTurtle.gif
new file mode 100644
index 0000000..8587ac2
Binary files /dev/null and b/assets/characters/animals/Slow Turtle/SlowTurtle.gif differ
diff --git a/assets/characters/animals/Slow Turtle/SlowTurtle.png b/assets/characters/animals/Slow Turtle/SlowTurtle.png
new file mode 100644
index 0000000..1f4643a
Binary files /dev/null and b/assets/characters/animals/Slow Turtle/SlowTurtle.png differ
diff --git a/assets/characters/animals/Slow Turtle/SlowTurtle.png.import b/assets/characters/animals/Slow Turtle/SlowTurtle.png.import
new file mode 100644
index 0000000..2119a39
--- /dev/null
+++ b/assets/characters/animals/Slow Turtle/SlowTurtle.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ddpod17b0clk4"
+path="res://.godot/imported/SlowTurtle.png-23aae53fe1e7eaed37e13b3e52381e08.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Slow Turtle/SlowTurtle.png"
+dest_files=["res://.godot/imported/SlowTurtle.png-23aae53fe1e7eaed37e13b3e52381e08.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Snow Fox/SnowFox.aseprite b/assets/characters/animals/Snow Fox/SnowFox.aseprite
new file mode 100644
index 0000000..358e6e1
Binary files /dev/null and b/assets/characters/animals/Snow Fox/SnowFox.aseprite differ
diff --git a/assets/characters/animals/Snow Fox/SnowFox.gif b/assets/characters/animals/Snow Fox/SnowFox.gif
new file mode 100644
index 0000000..db05fa5
Binary files /dev/null and b/assets/characters/animals/Snow Fox/SnowFox.gif differ
diff --git a/assets/characters/animals/Snow Fox/SnowFox.png b/assets/characters/animals/Snow Fox/SnowFox.png
new file mode 100644
index 0000000..43f1636
Binary files /dev/null and b/assets/characters/animals/Snow Fox/SnowFox.png differ
diff --git a/assets/characters/animals/Snow Fox/SnowFox.png.import b/assets/characters/animals/Snow Fox/SnowFox.png.import
new file mode 100644
index 0000000..f78e0cb
--- /dev/null
+++ b/assets/characters/animals/Snow Fox/SnowFox.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cig2j8vh061c2"
+path="res://.godot/imported/SnowFox.png-5da2eb6b56f0d3049f1451c7aed0b878.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Snow Fox/SnowFox.png"
+dest_files=["res://.godot/imported/SnowFox.png-5da2eb6b56f0d3049f1451c7aed0b878.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.aseprite b/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.aseprite
new file mode 100644
index 0000000..f747378
Binary files /dev/null and b/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.aseprite differ
diff --git a/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.gif b/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.gif
new file mode 100644
index 0000000..f43a16f
Binary files /dev/null and b/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.gif differ
diff --git a/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.png b/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.png
new file mode 100644
index 0000000..ea9f161
Binary files /dev/null and b/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.png differ
diff --git a/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.png.import b/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.png.import
new file mode 100644
index 0000000..b08992b
--- /dev/null
+++ b/assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://1uh6meq2g7pw"
+path="res://.godot/imported/SpikeyPorcupine.png-3588d2059214390b3bd6e5d5653cf965.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.png"
+dest_files=["res://.godot/imported/SpikeyPorcupine.png-3588d2059214390b3bd6e5d5653cf965.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Stinky Skunk/StinkySkunk.aseprite b/assets/characters/animals/Stinky Skunk/StinkySkunk.aseprite
new file mode 100644
index 0000000..ea8d09d
Binary files /dev/null and b/assets/characters/animals/Stinky Skunk/StinkySkunk.aseprite differ
diff --git a/assets/characters/animals/Stinky Skunk/StinkySkunk.gif b/assets/characters/animals/Stinky Skunk/StinkySkunk.gif
new file mode 100644
index 0000000..f34db7a
Binary files /dev/null and b/assets/characters/animals/Stinky Skunk/StinkySkunk.gif differ
diff --git a/assets/characters/animals/Stinky Skunk/StinkySkunk.png b/assets/characters/animals/Stinky Skunk/StinkySkunk.png
new file mode 100644
index 0000000..953b5ea
Binary files /dev/null and b/assets/characters/animals/Stinky Skunk/StinkySkunk.png differ
diff --git a/assets/characters/animals/Stinky Skunk/StinkySkunk.png.import b/assets/characters/animals/Stinky Skunk/StinkySkunk.png.import
new file mode 100644
index 0000000..0e3d8b5
--- /dev/null
+++ b/assets/characters/animals/Stinky Skunk/StinkySkunk.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dx323aeidjler"
+path="res://.godot/imported/StinkySkunk.png-4c08d373fe1dc8fbb5695a9ace20973e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Stinky Skunk/StinkySkunk.png"
+dest_files=["res://.godot/imported/StinkySkunk.png-4c08d373fe1dc8fbb5695a9ace20973e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Timber Wolf/TimberWolf.aseprite b/assets/characters/animals/Timber Wolf/TimberWolf.aseprite
new file mode 100644
index 0000000..f00ee35
Binary files /dev/null and b/assets/characters/animals/Timber Wolf/TimberWolf.aseprite differ
diff --git a/assets/characters/animals/Timber Wolf/TimberWolf.gif b/assets/characters/animals/Timber Wolf/TimberWolf.gif
new file mode 100644
index 0000000..4ae2739
Binary files /dev/null and b/assets/characters/animals/Timber Wolf/TimberWolf.gif differ
diff --git a/assets/characters/animals/Timber Wolf/TimberWolf.png b/assets/characters/animals/Timber Wolf/TimberWolf.png
new file mode 100644
index 0000000..22f8af0
Binary files /dev/null and b/assets/characters/animals/Timber Wolf/TimberWolf.png differ
diff --git a/assets/characters/animals/Timber Wolf/TimberWolf.png.import b/assets/characters/animals/Timber Wolf/TimberWolf.png.import
new file mode 100644
index 0000000..d33b962
--- /dev/null
+++ b/assets/characters/animals/Timber Wolf/TimberWolf.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ctg64vc7kxwcq"
+path="res://.godot/imported/TimberWolf.png-6517eed8f8f91d741f29eab507d7592a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Timber Wolf/TimberWolf.png"
+dest_files=["res://.godot/imported/TimberWolf.png-6517eed8f8f91d741f29eab507d7592a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/characters/animals/Tiny Chick/TinyChick.aseprite b/assets/characters/animals/Tiny Chick/TinyChick.aseprite
new file mode 100644
index 0000000..7010441
Binary files /dev/null and b/assets/characters/animals/Tiny Chick/TinyChick.aseprite differ
diff --git a/assets/characters/animals/Tiny Chick/TinyChick.gif b/assets/characters/animals/Tiny Chick/TinyChick.gif
new file mode 100644
index 0000000..902efc9
Binary files /dev/null and b/assets/characters/animals/Tiny Chick/TinyChick.gif differ
diff --git a/assets/characters/animals/Tiny Chick/TinyChick.png b/assets/characters/animals/Tiny Chick/TinyChick.png
new file mode 100644
index 0000000..d8ac7e2
Binary files /dev/null and b/assets/characters/animals/Tiny Chick/TinyChick.png differ
diff --git a/assets/characters/animals/Tiny Chick/TinyChick.png.import b/assets/characters/animals/Tiny Chick/TinyChick.png.import
new file mode 100644
index 0000000..4b90264
--- /dev/null
+++ b/assets/characters/animals/Tiny Chick/TinyChick.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://elhjscadwqu0"
+path="res://.godot/imported/TinyChick.png-d2a991e7fc7dfdafcae1d64e25a723a9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/animals/Tiny Chick/TinyChick.png"
+dest_files=["res://.godot/imported/TinyChick.png-d2a991e7fc7dfdafcae1d64e25a723a9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/assets/font/NotoColorEmoji-Regular.ttf b/assets/font/NotoColorEmoji-Regular.ttf
new file mode 100644
index 0000000..24be958
--- /dev/null
+++ b/assets/font/NotoColorEmoji-Regular.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:52215b679b7e1b2d69740af8ad7549bf9697084307ac157c404ffae7ccdcee5d
+size 25097144
diff --git a/assets/font/NotoColorEmoji-Regular.ttf.import b/assets/font/NotoColorEmoji-Regular.ttf.import
new file mode 100644
index 0000000..9390485
--- /dev/null
+++ b/assets/font/NotoColorEmoji-Regular.ttf.import
@@ -0,0 +1,36 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://d2vuqc82hbh5b"
+path="res://.godot/imported/NotoColorEmoji-Regular.ttf-3577461e3a993fe0291874403bf6cecc.fontdata"
+
+[deps]
+
+source_file="res://assets/font/NotoColorEmoji-Regular.ttf"
+dest_files=["res://.godot/imported/NotoColorEmoji-Regular.ttf-3577461e3a993fe0291874403bf6cecc.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=true
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=1
+msdf_size=48
+allow_system_fallback=false
+force_autohinter=false
+modulate_color_glyphs=false
+hinting=0
+subpixel_positioning=0
+keep_rounding_remainders=false
+oversampling=5.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/assets/images/logo.png b/assets/images/logo.png
new file mode 100644
index 0000000..fe12632
Binary files /dev/null and b/assets/images/logo.png differ
diff --git a/assets/images/logo.png.import b/assets/images/logo.png.import
new file mode 100644
index 0000000..0c383a7
--- /dev/null
+++ b/assets/images/logo.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bndq3avrtfck0"
+path="res://.godot/imported/logo.png-9164f87f81119ac7320e78ed71593bc1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/images/logo.png"
+dest_files=["res://.godot/imported/logo.png-9164f87f81119ac7320e78ed71593bc1.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/export_presets.cfg b/export_presets.cfg
index 93f5cbb..dc55008 100644
--- a/export_presets.cfg
+++ b/export_presets.cfg
@@ -31,9 +31,9 @@ variant/thread_support=false
vram_texture_compression/for_desktop=true
vram_texture_compression/for_mobile=false
html/export_icon=true
-html/custom_html_shell=""
+html/custom_html_shell="res://export_template.html"
html/head_include=""
-html/canvas_resize_policy=2
+html/canvas_resize_policy=1
html/focus_canvas_on_start=true
html/experimental_virtual_keyboard=false
progressive_web_app/enabled=true
@@ -44,6 +44,6 @@ progressive_web_app/orientation=0
progressive_web_app/icon_144x144=""
progressive_web_app/icon_180x180=""
progressive_web_app/icon_512x512=""
-progressive_web_app/background_color=Color(0, 0, 0, 1)
+progressive_web_app/background_color=Color(0.81960785, 0.7176471, 0.47843137, 1)
threads/emscripten_pool_size=8
threads/godot_pool_size=4
diff --git a/export_template.html b/export_template.html
new file mode 100644
index 0000000..871aae2
--- /dev/null
+++ b/export_template.html
@@ -0,0 +1,340 @@
+
+
+
+
+
+ $GODOT_PROJECT_NAME
+
+ $GODOT_HEAD_INCLUDE
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
diff --git a/icon.svg b/icon.svg
deleted file mode 100644
index c6bbb7d..0000000
--- a/icon.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/project.godot b/project.godot
index 581b109..80cacf3 100644
--- a/project.godot
+++ b/project.godot
@@ -17,7 +17,8 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
config/name="Clicker"
run/main_scene="uid://bqtexca6cdr54"
config/features=PackedStringArray("4.6", "Forward Plus")
-config/icon="res://icon.svg"
+boot_splash/bg_color=Color(0.5921569, 0.44313726, 0.2901961, 1)
+boot_splash/image="uid://bndq3avrtfck0"
[autoload]
@@ -28,6 +29,7 @@ DebugMenu="*uid://cggqb75a8w8r"
Inventory="*res://scripts/inventory.gd"
Unlocks="*res://scripts/unlocks.gd"
Audio="*res://scripts/audio.gd"
+AnimalFriendsManager="*res://scripts/animal_friends_manager.gd"
[display]
diff --git a/resources/UnlockData.tres b/resources/UnlockData.tres
index 87baba5..b0ffd5a 100644
--- a/resources/UnlockData.tres
+++ b/resources/UnlockData.tres
@@ -1,4 +1,4 @@
-[gd_resource type="Resource" script_class="UnlockDataCollection" load_steps=12 format=3 uid="uid://b4c01yrmp1wf2"]
+[gd_resource type="Resource" script_class="UnlockDataCollection" format=3 uid="uid://b4c01yrmp1wf2"]
[ext_resource type="Script" uid="uid://bg1ymgbdcwc0j" path="res://resources/UnlockDataCollection.gd" id="1_gdehu"]
[ext_resource type="Script" uid="uid://biqqffne7dd8r" path="res://resources/UnlockDataResource.gd" id="2_1js7i"]
@@ -6,13 +6,13 @@
[sub_resource type="Resource" id="Resource_gdehu"]
script = ExtResource("2_1js7i")
unlock_id = 1
-unlock_name = "Marketing"
+unlock_name = "Market Charm"
unlock_description = "Affects the amount people are willing to pay for your whittling"
base_cost = 100
is_scaling = true
max_rank = 8
-cost_ladder = [100, 350, 1000, 3000, 9000, 28000, 80000, 220000]
-effect_ladder = [1.08, 1.18, 1.3, 1.45, 1.65, 1.9, 2.3, 3.0]
+cost_ladder = Array[int]([100, 350, 1000, 3000, 9000, 28000, 80000, 220000])
+effect_ladder = Array[float]([1.08, 1.18, 1.3, 1.45, 1.65, 1.9, 2.3, 3.0])
base_modifiers = {
"sale_price_modifier": 1.08
}
@@ -21,13 +21,13 @@ metadata/_custom_type_script = "uid://biqqffne7dd8r"
[sub_resource type="Resource" id="Resource_1js7i"]
script = ExtResource("2_1js7i")
unlock_id = 2
-unlock_name = "Wood"
+unlock_name = "Sharper Blades"
unlock_description = "Increases the amount of wood produced per click"
base_cost = 30
is_scaling = true
max_rank = 5
-cost_ladder = [30, 100, 300, 900, 2500]
-effect_ladder = [2.0, 3.0, 5.0, 7.0, 10.0]
+cost_ladder = Array[int]([30, 100, 300, 900, 2500])
+effect_ladder = Array[float]([2.0, 3.0, 5.0, 7.0, 10.0])
base_modifiers = {
"wood_per_click_modifier": 2.0
}
@@ -36,13 +36,13 @@ metadata/_custom_type_script = "uid://biqqffne7dd8r"
[sub_resource type="Resource" id="Resource_xbpe0"]
script = ExtResource("2_1js7i")
unlock_id = 3
-unlock_name = "Demand"
-unlock_description = "How many whittled products can be purchased per tick"
+unlock_name = "Trade Winds"
+unlock_description = "How many whittled products can be sold per tick"
base_cost = 120
is_scaling = true
max_rank = 7
-cost_ladder = [120, 400, 1200, 4000, 12000, 38000, 110000]
-effect_ladder = [2.0, 3.0, 5.0, 7.0, 10.0, 14.0, 18.0]
+cost_ladder = Array[int]([120, 400, 1200, 4000, 12000, 38000, 110000])
+effect_ladder = Array[float]([2.0, 3.0, 5.0, 7.0, 10.0, 14.0, 18.0])
base_modifiers = {
"purchase_rate_modifier": 2.0
}
@@ -51,13 +51,13 @@ metadata/_custom_type_script = "uid://biqqffne7dd8r"
[sub_resource type="Resource" id="Resource_nbe0w"]
script = ExtResource("2_1js7i")
unlock_id = 4
-unlock_name = "Efficiency"
+unlock_name = "Whittler's Focus"
unlock_description = "How many things you can produce per whittle"
base_cost = 60
is_scaling = true
max_rank = 6
-cost_ladder = [60, 400, 1800, 8000, 35000, 140000]
-effect_ladder = [2.0, 3.0, 4.0, 5.0, 6.0, 8.0]
+cost_ladder = Array[int]([60, 400, 1800, 8000, 35000, 140000])
+effect_ladder = Array[float]([2.0, 3.0, 4.0, 5.0, 6.0, 8.0])
base_modifiers = {
"efficiency_modifier": 2.0
}
@@ -66,7 +66,7 @@ metadata/_custom_type_script = "uid://biqqffne7dd8r"
[sub_resource type="Resource" id="Resource_ppuju"]
script = ExtResource("2_1js7i")
unlock_id = 5
-unlock_name = "Wholesale"
+unlock_name = "Guild Contract"
unlock_description = "Sell multiples of 100 at 20% less income"
base_cost = 35000
base_modifiers = {
@@ -77,13 +77,13 @@ metadata/_custom_type_script = "uid://biqqffne7dd8r"
[sub_resource type="Resource" id="Resource_chx6j"]
script = ExtResource("2_1js7i")
unlock_id = 6
-unlock_name = "Multicraft"
-unlock_description = "Just craft more stuff"
+unlock_name = "Forest Friends"
+unlock_description = "Your furry friends start crafting for some reason..."
base_cost = 6000
is_scaling = true
max_rank = 5
-cost_ladder = [6000, 20000, 55000, 140000, 320000]
-effect_ladder = [1.0, 2.0, 3.0, 4.0, 5.0]
+cost_ladder = Array[int]([6000, 20000, 55000, 140000, 320000])
+effect_ladder = Array[float]([1.0, 2.0, 3.0, 4.0, 5.0])
base_modifiers = {
"multicraft_increase_modifier": 1.0
}
@@ -92,13 +92,13 @@ metadata/_custom_type_script = "uid://biqqffne7dd8r"
[sub_resource type="Resource" id="Resource_f82ch"]
script = ExtResource("2_1js7i")
unlock_id = 7
-unlock_name = "Autowood"
-unlock_description = "Automatically gather a percent of a clicks wood per tick"
+unlock_name = "Beaver Brigade"
+unlock_description = "Automatically gain wood"
base_cost = 150
is_scaling = true
max_rank = 5
-cost_ladder = [150, 600, 2000, 6500, 20000]
-effect_ladder = [0.1, 0.2, 0.35, 0.55, 0.8]
+cost_ladder = Array[int]([150, 600, 2000, 6500, 20000])
+effect_ladder = Array[float]([0.1, 0.2, 0.35, 0.55, 0.8])
base_modifiers = {
"autowood_modifier": 0.1
}
@@ -107,13 +107,13 @@ metadata/_custom_type_script = "uid://biqqffne7dd8r"
[sub_resource type="Resource" id="Resource_premium"]
script = ExtResource("2_1js7i")
unlock_id = 8
-unlock_name = "Premium Crafts"
+unlock_name = "Artisan's Touch"
unlock_description = "Your reputation for quality allows higher prices"
base_cost = 8000
is_scaling = true
max_rank = 5
-cost_ladder = [8000, 25000, 70000, 160000, 350000]
-effect_ladder = [1.08, 1.18, 1.3, 1.45, 1.65]
+cost_ladder = Array[int]([8000, 25000, 70000, 160000, 350000])
+effect_ladder = Array[float]([1.15, 1.3, 1.45, 1.65, 1.8])
base_modifiers = {
"premium_price_modifier": 1.08
}
@@ -122,13 +122,13 @@ metadata/_custom_type_script = "uid://biqqffne7dd8r"
[sub_resource type="Resource" id="Resource_reputation"]
script = ExtResource("2_1js7i")
unlock_id = 9
-unlock_name = "Reputation"
+unlock_name = "Legendary Crafter"
unlock_description = "Loyal customers provide steady passive income"
base_cost = 4000
is_scaling = true
max_rank = 5
-cost_ladder = [4000, 12000, 35000, 90000, 200000]
-effect_ladder = [4.0, 10.0, 22.0, 42.0, 75.0]
+cost_ladder = Array[int]([4000, 12000, 35000, 90000, 200000])
+effect_ladder = Array[float]([4.0, 10.0, 22.0, 42.0, 75.0])
base_modifiers = {
"reputation_income": 4.0
}
diff --git a/scenes/animal.tscn b/scenes/animal.tscn
new file mode 100644
index 0000000..fe9ccc7
--- /dev/null
+++ b/scenes/animal.tscn
@@ -0,0 +1,473 @@
+[gd_scene format=3 uid="uid://bfkpy8wqqktca"]
+
+[ext_resource type="Script" uid="uid://uhlsvqaaemre" path="res://scenes/scripts/animal.gd" id="1_hb82n"]
+[ext_resource type="Texture2D" uid="uid://cig2j8vh061c2" path="res://assets/characters/animals/Snow Fox/SnowFox.png" id="1_ufccv"]
+[ext_resource type="Texture2D" uid="uid://ckvge3k08px5c" path="res://assets/tiles/sun.png" id="2_35nom"]
+[ext_resource type="Texture2D" uid="uid://1uh6meq2g7pw" path="res://assets/characters/animals/Spikey Porcupine/SpikeyPorcupine.png" id="2_236rq"]
+[ext_resource type="Texture2D" uid="uid://ctg64vc7kxwcq" path="res://assets/characters/animals/Timber Wolf/TimberWolf.png" id="3_3c5tq"]
+[ext_resource type="Texture2D" uid="uid://dccs841gqvsnr" path="res://assets/characters/animals/Meowing Cat/MeowingCat.png" id="4_ieass"]
+[ext_resource type="Texture2D" uid="uid://drghlunsulio" path="res://assets/characters/animals/Honking Goose/HonkingGoose.png" id="5_ieass"]
+[ext_resource type="Texture2D" uid="uid://rvdwc66bww3" path="res://assets/characters/animals/Croaking Toad/CroakingToad.png" id="6_d7epe"]
+[ext_resource type="Texture2D" uid="uid://elhjscadwqu0" path="res://assets/characters/animals/Tiny Chick/TinyChick.png" id="7_brjsm"]
+[ext_resource type="Texture2D" uid="uid://b7hgiys1h18ov" path="res://assets/characters/animals/SleepDog.png" id="8_ybi86"]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_35nom"]
+atlas = ExtResource("1_ufccv")
+region = Rect2(0, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_3u0el"]
+atlas = ExtResource("1_ufccv")
+region = Rect2(16, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_brrv0"]
+atlas = ExtResource("1_ufccv")
+region = Rect2(32, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_2g4xt"]
+atlas = ExtResource("1_ufccv")
+region = Rect2(48, 0, 16, 16)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_m5q2l"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_35nom")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_3u0el")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_brrv0")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_2g4xt")
+}],
+"loop": true,
+"name": &"default",
+"speed": 4.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_3v7ex"]
+atlas = ExtResource("2_236rq")
+region = Rect2(0, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_3c5tq"]
+atlas = ExtResource("2_236rq")
+region = Rect2(16, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_ieass"]
+atlas = ExtResource("2_236rq")
+region = Rect2(32, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_d7epe"]
+atlas = ExtResource("2_236rq")
+region = Rect2(48, 0, 16, 16)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_brjsm"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_3v7ex")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_3c5tq")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_ieass")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_d7epe")
+}],
+"loop": true,
+"name": &"default",
+"speed": 4.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_brjsm"]
+atlas = ExtResource("3_3c5tq")
+region = Rect2(0, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_ybi86"]
+atlas = ExtResource("3_3c5tq")
+region = Rect2(16, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_hb82n"]
+atlas = ExtResource("3_3c5tq")
+region = Rect2(32, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_fiknr"]
+atlas = ExtResource("3_3c5tq")
+region = Rect2(48, 0, 16, 16)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_d06xb"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_brjsm")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_ybi86")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_hb82n")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_fiknr")
+}],
+"loop": true,
+"name": &"default",
+"speed": 2.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_gceah"]
+atlas = ExtResource("4_ieass")
+region = Rect2(0, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_s21jh"]
+atlas = ExtResource("4_ieass")
+region = Rect2(16, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_xjlt7"]
+atlas = ExtResource("4_ieass")
+region = Rect2(32, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_jp041"]
+atlas = ExtResource("4_ieass")
+region = Rect2(48, 0, 16, 16)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_7do3a"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_gceah")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_s21jh")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_xjlt7")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_jp041")
+}],
+"loop": true,
+"name": &"default",
+"speed": 4.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_d06xb"]
+atlas = ExtResource("5_ieass")
+region = Rect2(0, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_7do3a"]
+atlas = ExtResource("5_ieass")
+region = Rect2(16, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_qag4x"]
+atlas = ExtResource("5_ieass")
+region = Rect2(32, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_10oeq"]
+atlas = ExtResource("5_ieass")
+region = Rect2(48, 0, 16, 16)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_lt1bt"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_d06xb")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_7do3a")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_qag4x")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_10oeq")
+}],
+"loop": true,
+"name": &"default",
+"speed": 4.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_lt1bt"]
+atlas = ExtResource("6_d7epe")
+region = Rect2(0, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_748p7"]
+atlas = ExtResource("6_d7epe")
+region = Rect2(16, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_h4cte"]
+atlas = ExtResource("6_d7epe")
+region = Rect2(32, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_rori6"]
+atlas = ExtResource("6_d7epe")
+region = Rect2(48, 0, 16, 16)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_gbowl"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_lt1bt")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_748p7")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_h4cte")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_rori6")
+}],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_gbowl"]
+atlas = ExtResource("7_brjsm")
+region = Rect2(0, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_em8nj"]
+atlas = ExtResource("7_brjsm")
+region = Rect2(16, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_j1qxf"]
+atlas = ExtResource("7_brjsm")
+region = Rect2(32, 0, 16, 16)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_lowru"]
+atlas = ExtResource("7_brjsm")
+region = Rect2(48, 0, 16, 16)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_20wpp"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_gbowl")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_em8nj")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_j1qxf")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_lowru")
+}],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_20wpp"]
+atlas = ExtResource("8_ybi86")
+region = Rect2(0, 0, 64, 64)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_eaemt"]
+atlas = ExtResource("8_ybi86")
+region = Rect2(64, 0, 64, 64)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_xa74n"]
+atlas = ExtResource("8_ybi86")
+region = Rect2(128, 0, 64, 64)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_v8kal"]
+atlas = ExtResource("8_ybi86")
+region = Rect2(192, 0, 64, 64)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_r1mgj"]
+atlas = ExtResource("8_ybi86")
+region = Rect2(256, 0, 64, 64)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_wxa41"]
+atlas = ExtResource("8_ybi86")
+region = Rect2(320, 0, 64, 64)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_33tgj"]
+atlas = ExtResource("8_ybi86")
+region = Rect2(384, 0, 64, 64)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_l8a8u"]
+atlas = ExtResource("8_ybi86")
+region = Rect2(448, 0, 64, 64)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_d57ma"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_20wpp")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_eaemt")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_xa74n")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_v8kal")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_r1mgj")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_wxa41")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_33tgj")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_l8a8u")
+}],
+"loop": true,
+"name": &"default",
+"speed": 3.0
+}]
+
+[sub_resource type="Curve" id="Curve_lrhn5"]
+_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
+point_count = 2
+
+[sub_resource type="CurveTexture" id="CurveTexture_okhi1"]
+curve = SubResource("Curve_lrhn5")
+
+[sub_resource type="Gradient" id="Gradient_my7n2"]
+colors = PackedColorArray(0.6267965, 0.35946804, 0.115510084, 0.3764706, 0.5640522, 0.46861154, 0.16076079, 0.627451)
+
+[sub_resource type="GradientTexture1D" id="GradientTexture1D_rkhd4"]
+gradient = SubResource("Gradient_my7n2")
+
+[sub_resource type="Curve" id="Curve_dp8ca"]
+_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
+point_count = 2
+
+[sub_resource type="CurveTexture" id="CurveTexture_deeqb"]
+curve = SubResource("Curve_dp8ca")
+
+[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_0lw5n"]
+lifetime_randomness = 0.73
+particle_flag_disable_z = true
+emission_shape = 1
+emission_sphere_radius = 1.0
+angle_min = 1.0728835e-05
+angle_max = 115.70001
+inherit_velocity_ratio = 0.154
+direction = Vector3(0, 0, 0)
+spread = 98.933
+initial_velocity_max = 2.0
+gravity = Vector3(0, 0, 0)
+linear_accel_min = 0.99999774
+linear_accel_max = 4.9999976
+scale_min = 0.19999999
+scale_max = 0.7
+scale_curve = SubResource("CurveTexture_deeqb")
+color_ramp = SubResource("GradientTexture1D_rkhd4")
+alpha_curve = SubResource("CurveTexture_okhi1")
+hue_variation_min = -0.11000002
+hue_variation_max = 0.089999974
+turbulence_enabled = true
+turbulence_noise_strength = 0.56
+turbulence_noise_scale = 4.861
+
+[sub_resource type="Curve" id="Curve_rkhd4"]
+_data = [Vector2(0, 0.007272601), 0.0, 0.0, 0, 0, Vector2(0.46874994, 0.758909), 0.0, 0.0, 0, 0, Vector2(0.73632807, 0.8865454), 0.0, 0.0, 0, 0]
+point_count = 3
+
+[sub_resource type="CurveTexture" id="CurveTexture_lrhn5"]
+curve = SubResource("Curve_rkhd4")
+
+[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_agfs1"]
+particle_flag_disable_z = true
+gravity = Vector3(0, 0, 0)
+linear_accel_min = -2.2351742e-06
+linear_accel_max = 6.4299974
+radial_accel_min = -2.2351742e-06
+radial_accel_max = 23.289997
+tangential_accel_min = -24.100002
+tangential_accel_max = 10.439998
+color = Color(0.84313726, 0.56078434, 0, 0.7921569)
+alpha_curve = SubResource("CurveTexture_lrhn5")
+
+[node name="Character" type="Node2D" unique_id=1357587844]
+script = ExtResource("1_hb82n")
+show_shavings = null
+
+[node name="Fox" type="AnimatedSprite2D" parent="." unique_id=565500848]
+unique_name_in_owner = true
+visible = false
+sprite_frames = SubResource("SpriteFrames_m5q2l")
+autoplay = "default"
+frame_progress = 0.44348216
+
+[node name="Porcupine" type="AnimatedSprite2D" parent="." unique_id=1272266059]
+unique_name_in_owner = true
+visible = false
+sprite_frames = SubResource("SpriteFrames_brjsm")
+autoplay = "default"
+frame = 3
+frame_progress = 0.8018118
+
+[node name="Wolf" type="AnimatedSprite2D" parent="." unique_id=1759146555]
+unique_name_in_owner = true
+visible = false
+sprite_frames = SubResource("SpriteFrames_d06xb")
+autoplay = "default"
+frame = 3
+frame_progress = 0.19724818
+
+[node name="Cat" type="AnimatedSprite2D" parent="." unique_id=1332976928]
+unique_name_in_owner = true
+visible = false
+sprite_frames = SubResource("SpriteFrames_7do3a")
+autoplay = "default"
+frame_progress = 0.031060848
+
+[node name="Goose" type="AnimatedSprite2D" parent="." unique_id=222745417]
+unique_name_in_owner = true
+visible = false
+sprite_frames = SubResource("SpriteFrames_lt1bt")
+autoplay = "default"
+frame_progress = 0.4729133
+
+[node name="Frog" type="AnimatedSprite2D" parent="." unique_id=679106242]
+unique_name_in_owner = true
+visible = false
+sprite_frames = SubResource("SpriteFrames_gbowl")
+autoplay = "default"
+frame = 1
+frame_progress = 0.27542892
+
+[node name="Chick" type="AnimatedSprite2D" parent="." unique_id=1754919209]
+unique_name_in_owner = true
+visible = false
+sprite_frames = SubResource("SpriteFrames_20wpp")
+autoplay = "default"
+frame = 1
+frame_progress = 0.11994407
+
+[node name="Dog" type="AnimatedSprite2D" parent="." unique_id=197188039]
+unique_name_in_owner = true
+visible = false
+position = Vector2(0, -32)
+sprite_frames = SubResource("SpriteFrames_d57ma")
+autoplay = "default"
+
+[node name="Dust" type="GPUParticles2D" parent="." unique_id=785652497]
+visible = false
+position = Vector2(0, 1)
+amount = 100
+texture = ExtResource("2_35nom")
+lifetime = 10.0
+preprocess = 2.0
+explosiveness = 0.06
+randomness = 0.39
+process_material = SubResource("ParticleProcessMaterial_0lw5n")
+
+[node name="Shavings" type="GPUParticles2D" parent="." unique_id=1628407210]
+unique_name_in_owner = true
+visible = false
+preprocess = 2.0
+process_material = SubResource("ParticleProcessMaterial_agfs1")
diff --git a/scenes/button.tscn b/scenes/button.tscn
index 2ad2a86..664af4a 100644
--- a/scenes/button.tscn
+++ b/scenes/button.tscn
@@ -1,33 +1,43 @@
-[gd_scene load_steps=7 format=3 uid="uid://b0bmsqlrg77le"]
+[gd_scene format=3 uid="uid://b0bmsqlrg77le"]
[ext_resource type="Texture2D" uid="uid://dx134esqj3kg3" path="res://assets/ui/buttonLong_brown.png" id="1_1bdt2"]
[ext_resource type="Texture2D" uid="uid://bmdc4875jf16r" path="res://assets/ui/buttonLong_brown_pressed.png" id="2_8m7bo"]
-[ext_resource type="Texture2D" uid="uid://ddghl4cooepr1" path="res://assets/ui/buttonLong_blue.png" id="3_t81cg"]
-[ext_resource type="Texture2D" uid="uid://f0tde4s55m2o" path="res://assets/ui/buttonLong_grey.png" id="4_is61r"]
+[ext_resource type="Texture2D" uid="uid://c3gmw2rffktva" path="res://assets/ui/buttonLong_beige_pressed.png" id="3_8m7bo"]
+[ext_resource type="Texture2D" uid="uid://bk377v70i8bsw" path="res://assets/ui/buttonLong_blue_pressed.png" id="4_8m7bo"]
[ext_resource type="Script" uid="uid://dj7uoaxxat5n4" path="res://scenes/scripts/button.gd" id="5_8m7bo"]
[ext_resource type="Theme" uid="uid://bnbtwoxxd6cg5" path="res://assets/theme/clicker.theme" id="5_iw4ej"]
-[node name="TextureButton" type="TextureButton"]
+[node name="TextureButton" type="TextureButton" unique_id=186080222]
custom_minimum_size = Vector2(100, 25)
-offset_right = 100.0
-offset_bottom = 25.0
+offset_right = 103.0
+offset_bottom = 32.0
tooltip_text = "I need a tooltip bro"
texture_normal = ExtResource("1_1bdt2")
texture_pressed = ExtResource("2_8m7bo")
-texture_hover = ExtResource("3_t81cg")
-texture_disabled = ExtResource("4_is61r")
+texture_hover = ExtResource("3_8m7bo")
+texture_disabled = ExtResource("4_8m7bo")
ignore_texture_size = true
stretch_mode = 0
script = ExtResource("5_8m7bo")
-[node name="CenterContainer" type="CenterContainer" parent="."]
+[node name="CenterContainer" type="CenterContainer" parent="." unique_id=313903696]
clip_contents = true
custom_minimum_size = Vector2(100, 25)
layout_mode = 0
offset_right = 100.0
offset_bottom = 25.0
-[node name="Label" type="Label" parent="CenterContainer"]
+[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer" unique_id=1263225362]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="NameLabel" type="Label" parent="CenterContainer/VBoxContainer" unique_id=1225878363]
+layout_mode = 2
+theme = ExtResource("5_iw4ej")
+text = "-"
+horizontal_alignment = 1
+
+[node name="PriceLabel" type="Label" parent="CenterContainer/VBoxContainer" unique_id=1012864835]
layout_mode = 2
theme = ExtResource("5_iw4ej")
text = "-"
diff --git a/scenes/config_panel.tscn b/scenes/config_panel.tscn
new file mode 100644
index 0000000..b545a0b
--- /dev/null
+++ b/scenes/config_panel.tscn
@@ -0,0 +1,91 @@
+[gd_scene format=3 uid="uid://bqy5j8xm7o8ra"]
+
+[ext_resource type="Script" path="res://scenes/scripts/config_panel.gd" id="1_config"]
+
+[node name="ConfigPanel" type="Panel"]
+unique_name_in_owner = true
+light_mask = 2
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -200.0
+offset_top = -150.0
+offset_right = 200.0
+offset_bottom = 150.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_config")
+
+[node name="MarginContainer" type="MarginContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 20
+theme_override_constants/margin_top = 20
+theme_override_constants/margin_right = 20
+theme_override_constants/margin_bottom = 20
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
+layout_mode = 2
+theme_override_constants/separation = 15
+
+[node name="TitleLabel" type="Label" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Sound Settings"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="MusicContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/MusicContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Background Music"
+vertical_alignment = 1
+
+[node name="MusicToggle" type="CheckButton" parent="MarginContainer/VBoxContainer/MusicContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="ChopContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ChopContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Wood Chop Sound"
+vertical_alignment = 1
+
+[node name="ChopToggle" type="CheckButton" parent="MarginContainer/VBoxContainer/ChopContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="MoneyContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/MoneyContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Money Sound"
+vertical_alignment = 1
+
+[node name="MoneyToggle" type="CheckButton" parent="MarginContainer/VBoxContainer/MoneyContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HSeparator2" type="HSeparator" parent="MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="CloseButton" type="Button" parent="MarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Close"
diff --git a/scenes/game.tscn b/scenes/game.tscn
deleted file mode 100644
index 170b5f8..0000000
--- a/scenes/game.tscn
+++ /dev/null
@@ -1,3 +0,0 @@
-[gd_scene format=3 uid="uid://duhwm7m5hc506"]
-
-[node name="Game" type="Node3D"]
diff --git a/scenes/game2.tscn b/scenes/game2.tscn
index 22808b9..c5efb4e 100644
--- a/scenes/game2.tscn
+++ b/scenes/game2.tscn
@@ -18,11 +18,16 @@
[ext_resource type="Script" uid="uid://cpimo8q5dcjxf" path="res://scenes/scripts/fire_light.gd" id="16_5kdtj"]
[ext_resource type="PackedScene" uid="uid://cbrkq6jd5a4ho" path="res://scenes/character.tscn" id="17_1hpkv"]
[ext_resource type="PackedScene" uid="uid://cnyxwsj6i27ja" path="res://scenes/stock_pile.tscn" id="17_deeqb"]
+[ext_resource type="PackedScene" uid="uid://bfkpy8wqqktca" path="res://scenes/animal.tscn" id="17_k8jaa"]
[ext_resource type="PackedScene" uid="uid://bubjxrs8qmr4y" path="res://scenes/wood_pile.tscn" id="17_oibj5"]
[ext_resource type="Script" uid="uid://cm84m3olmcc8o" path="res://scenes/scripts/ui_control.gd" id="17_q7h7c"]
[ext_resource type="PackedScene" uid="uid://b0bmsqlrg77le" path="res://scenes/button.tscn" id="19_v4v8k"]
+[ext_resource type="PackedScene" path="res://scenes/config_panel.tscn" id="20_config"]
+[ext_resource type="Texture2D" uid="uid://dylc6y7ajsln3" path="res://assets/ui/panel_brown.png" id="22_hcndq"]
+[ext_resource type="FontFile" uid="uid://bfq6d6y56gr4s" path="res://assets/font/m5x7.ttf" id="22_k8i0y"]
[ext_resource type="Theme" uid="uid://bnbtwoxxd6cg5" path="res://assets/theme/clicker.theme" id="22_q7h7c"]
[ext_resource type="Texture2D" uid="uid://dx134esqj3kg3" path="res://assets/ui/buttonLong_brown.png" id="23_lv1cq"]
+[ext_resource type="FontFile" uid="uid://d2vuqc82hbh5b" path="res://assets/font/NotoColorEmoji-Regular.ttf" id="24_chdjo"]
[ext_resource type="Texture2D" uid="uid://bmdc4875jf16r" path="res://assets/ui/buttonLong_brown_pressed.png" id="24_k8i0y"]
[ext_resource type="Texture2D" uid="uid://ddghl4cooepr1" path="res://assets/ui/buttonLong_blue.png" id="25_hcndq"]
[ext_resource type="Texture2D" uid="uid://f0tde4s55m2o" path="res://assets/ui/buttonLong_grey.png" id="26_chdjo"]
@@ -1946,9 +1951,26 @@ gradient = SubResource("Gradient_5kdtj")
fill = 1
fill_from = Vector2(0.5, 0.5)
+[sub_resource type="FontVariation" id="FontVariation_hcndq"]
+base_font = ExtResource("22_k8i0y")
+variation_embolden = 0.2
+
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q7h7c"]
bg_color = Color(0, 0, 0, 0.7921569)
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_mjadu"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_k8jaa"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hp34j"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_lxcs1"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hxr5a"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mjadu"]
+bg_color = Color(0.5921569, 0.44313726, 0.2901961, 1)
+
[node name="GameScene" type="Node2D" unique_id=1879752572]
[node name="Scene" type="Node2D" parent="." unique_id=1170541436]
@@ -1960,42 +1982,57 @@ metadata/_edit_lock_ = true
[node name="Sprite2D" type="Sprite2D" parent="Scene/Background" unique_id=1234031660]
position = Vector2(43, -45)
texture = ExtResource("1_xrrf0")
+metadata/_edit_lock_ = true
[node name="Sprite2D2" type="Sprite2D" parent="Scene/Background" unique_id=531673597]
position = Vector2(43, -45)
texture = ExtResource("2_kh4a2")
+metadata/_edit_lock_ = true
[node name="Sprite2D3" type="Sprite2D" parent="Scene/Background" unique_id=1328011980]
position = Vector2(43, -45)
texture = ExtResource("3_8j387")
+metadata/_edit_lock_ = true
[node name="Sprite2D4" type="Sprite2D" parent="Scene/Background" unique_id=236948197]
position = Vector2(43, -45)
texture = ExtResource("4_5dhap")
+metadata/_edit_lock_ = true
[node name="Sprite2D5" type="Sprite2D" parent="Scene/Background" unique_id=1992550436]
position = Vector2(44, -45)
texture = ExtResource("5_s427y")
+metadata/_edit_lock_ = true
[node name="BGTrees2" type="TileMapLayer" parent="Scene" unique_id=1214041213]
position = Vector2(-49, 59)
tile_map_data = PackedByteArray("AAACAPr/BgAAAAEAAAACAPv/BgAAAAIAAAACAPz/BgAAAAMAAAACAP3/BgAAAAQAAAADAPn/BgABAAAAAAADAPr/BgABAAEAAAADAPv/BgABAAIAAAADAPz/BgABAAMAAAADAP3/BgABAAQAAAADAP7/BgABAAUAAAAEAPn/BgACAAAAAAAEAPr/BgACAAEAAAAEAPv/BgACAAIAAAAEAPz/BgACAAMAAAAEAP3/BgACAAQAAAAEAP7/BgACAAUAAAAEAP//BgACAAYAAAAFAPn/BgADAAAAAAAFAPr/BgADAAEAAAAFAPv/BgADAAIAAAAFAPz/BgADAAMAAAAFAP3/BgADAAQAAAAGAPr/BgAEAAEAAAAGAPv/BgAEAAIAAAAGAPz/BgAEAAMAAAAGAP3/BgAEAAQAAAAOAPr/BgAAAAEAAAAOAPv/BgAAAAIAAAAOAPz/BgAAAAMAAAAOAP3/BgAAAAQAAAAPAPn/BgABAAAAAAAPAPr/BgABAAEAAAAPAPv/BgABAAIAAAAPAPz/BgABAAMAAAAPAP3/BgABAAQAAAAPAP7/BgABAAUAAAAQAPn/BgACAAAAAAAQAPr/BgACAAEAAAAQAPv/BgACAAIAAAAQAPz/BgACAAMAAAAQAP3/BgACAAQAAAAQAP7/BgACAAUAAAAQAP//BgACAAYAAAARAPn/BgADAAAAAAARAPr/BgADAAEAAAARAPv/BgADAAIAAAARAPz/BgADAAMAAAARAP3/BgADAAQAAAASAPr/BgAEAAEAAAASAPv/BgAEAAIAAAASAPz/BgAEAAMAAAASAP3/BgAEAAQAAAD5//z/BwAAAAcAAAD5//3/BwAAAAgAAAD5//7/BwAAAAkAAAD5////BwAAAAoAAAD6//v/BwABAAYAAAD6//z/BwABAAcAAAD6//3/BwABAAgAAAD6//7/BwABAAkAAAD6////BwABAAoAAAD7//n/BwACAAQAAAD7//r/BwACAAUAAAD7//v/BwACAAYAAAD7//z/BwACAAcAAAD7//3/BwACAAgAAAD7//7/BwACAAkAAAD7////BwACAAoAAAD8//f/BwADAAIAAAD8//j/BwADAAMAAAD8//n/BwADAAQAAAD8//r/BwADAAUAAAD8//v/BwADAAYAAAD8//z/BwADAAcAAAD8//3/BwADAAgAAAD8//7/BwADAAkAAAD9//b/BwAEAAEAAAD9//f/BwAEAAIAAAD9//j/BwAEAAMAAAD9//n/BwAEAAQAAAD9//r/BwAEAAUAAAD9//v/BwAEAAYAAAD9//z/BwAEAAcAAAD9//3/BwAEAAgAAAD9//7/BwAEAAkAAAD+//b/BwAFAAEAAAD+//f/BwAFAAIAAAD+//j/BwAFAAMAAAD+//n/BwAFAAQAAAD+//r/BwAFAAUAAAD+//v/BwAFAAYAAAD+//z/BwAFAAcAAAD+//3/BwAFAAgAAAD+//7/BwAFAAkAAAD///X/BwAGAAAAAAD///b/BwAGAAEAAAD///f/BwAGAAIAAAD///j/BwAGAAMAAAD///n/BwAGAAQAAAD///r/BwAGAAUAAAD///v/BwAGAAYAAAD///z/BwAGAAcAAAD///3/BwAGAAgAAAAAAPj/BwAHAAMAAAAAAPn/BwAHAAQAAAAAAPr/BwAHAAUAAAAMAP7/AQAMABQAAAAMAP//AQAMABUAAAANAP7/AQANABQAAAANAP//AQANABUAAAAOAP7/AQAOABQAAAAOAP//AQAOABUAAAAPAP//AQAPABUAAAAKAP//AQAKABUAAAALAP//AQALABUAAAAAAP7/AQAAABwAAAAAAP//AQAAAB0AAAABAP7/AQABABwAAAABAP//AQABAB0AAAACAP7/AQACABwAAAACAP//AQACAB0AAAADAP//AQADAB0AAAAVAP//AQAGAB0AAAAWAP//AQAHAB0AAAA=")
tile_set = SubResource("TileSet_btr28")
+metadata/_edit_lock_ = true
[node name="BGTrees1" type="TileMapLayer" parent="Scene" unique_id=232331490]
position = Vector2(-49, 59)
tile_map_data = PackedByteArray("AAD9//7/BAAKAAMAAAD9////BAAKAAQAAAD+////BAALAAQAAAAHAP7/AgAGAAsAAAAHAP//AgAGAAwAAAAIAP3/AgAHAAoAAAAIAP7/AgAHAAsAAAAIAP//AgAHAAwAAAAJAP7/AgAIAAsAAAAJAP//AgAIAAwAAAABAPj/AgAAAAUAAAABAPn/AgAAAAYAAAACAPf/AgABAAQAAAACAPj/AgABAAUAAAACAPn/AgABAAYAAAACAPr/AgABAAcAAAADAPf/AgACAAQAAAADAPj/AgACAAUAAAADAPn/AgACAAYAAAADAPr/AgACAAcAAAAEAPX/AgADAAIAAAAEAPb/AgADAAMAAAAEAPf/AgADAAQAAAAEAPj/AgADAAUAAAAEAPn/AgADAAYAAAAEAPr/AgADAAcAAAAEAPv/AgADAAgAAAAFAPT/AgAEAAEAAAAFAPX/AgAEAAIAAAAFAPb/AgAEAAMAAAAFAPf/AgAEAAQAAAAFAPj/AgAEAAUAAAAFAPn/AgAEAAYAAAAFAPr/AgAEAAcAAAAFAPv/AgAEAAgAAAAGAPT/AgAFAAEAAAAGAPX/AgAFAAIAAAAGAPb/AgAFAAMAAAAGAPf/AgAFAAQAAAAGAPj/AgAFAAUAAAAGAPn/AgAFAAYAAAAGAPr/AgAFAAcAAAAGAPv/AgAFAAgAAAAHAPP/AgAGAAAAAAAHAPT/AgAGAAEAAAAHAPX/AgAGAAIAAAAHAPb/AgAGAAMAAAAHAPf/AgAGAAQAAAAHAPj/AgAGAAUAAAAHAPn/AgAGAAYAAAAHAPr/AgAGAAcAAAAHAPv/AgAGAAgAAAAHAPz/AgAGAAkAAAAIAPP/AgAHAAAAAAAIAPT/AgAHAAEAAAAIAPX/AgAHAAIAAAAIAPb/AgAHAAMAAAAIAPf/AgAHAAQAAAAIAPj/AgAHAAUAAAAIAPn/AgAHAAYAAAAIAPr/AgAHAAcAAAAIAPv/AgAHAAgAAAAIAPz/AgAHAAkAAAAJAPP/AgAIAAAAAAAJAPT/AgAIAAEAAAAJAPX/AgAIAAIAAAAJAPb/AgAIAAMAAAAJAPf/AgAIAAQAAAAJAPj/AgAIAAUAAAAJAPn/AgAIAAYAAAAJAPr/AgAIAAcAAAAJAPv/AgAIAAgAAAAJAPz/AgAIAAkAAAAJAP3/AgAIAAoAAAAKAPP/AgAJAAAAAAAKAPT/AgAJAAEAAAAKAPX/AgAJAAIAAAAKAPb/AgAJAAMAAAAKAPf/AgAJAAQAAAAKAPj/AgAJAAUAAAAKAPn/AgAJAAYAAAAKAPr/AgAJAAcAAAAKAPv/AgAJAAgAAAAKAP//AgAJAAwAAAALAPP/AgAKAAAAAAALAPT/AgAKAAEAAAALAPX/AgAKAAIAAAALAPb/AgAKAAMAAAALAPf/AgAKAAQAAAALAPj/AgAKAAUAAAALAPn/AgAKAAYAAAALAPr/AgAKAAcAAAALAPv/AgAKAAgAAAAMAPT/AgALAAEAAAAMAPX/AgALAAIAAAAMAPb/AgALAAMAAAAMAPf/AgALAAQAAAAMAPj/AgALAAUAAAAMAPn/AgALAAYAAAAMAPr/AgALAAcAAAAMAPv/AgALAAgAAAAMAPz/AgALAAkAAAANAPT/AgAMAAEAAAANAPX/AgAMAAIAAAANAPb/AgAMAAMAAAANAPf/AgAMAAQAAAANAPj/AgAMAAUAAAANAPn/AgAMAAYAAAANAPr/AgAMAAcAAAANAPv/AgAMAAgAAAANAPz/AgAMAAkAAAAOAPf/AgANAAQAAAAOAPj/AgANAAUAAAAOAPn/AgANAAYAAAAOAPr/AgANAAcAAAAOAPv/AgANAAgAAAAPAPj/AgAOAAUAAAAPAPn/AgAOAAYAAAAPAPr/AgAOAAcAAAAPAPv/AgAOAAgAAAAQAPj/AgAPAAUAAAAQAPn/AgAPAAYAAAAQAPr/AgAPAAcAAAAQAPv/AgAPAAgAAAD5//v/BAAGAAAAAAD5//z/BAAGAAEAAAD5//3/BAAGAAIAAAD5//7/BAAGAAMAAAD6//v/BAAHAAAAAAD6//z/BAAHAAEAAAD6//3/BAAHAAIAAAD6//7/BAAHAAMAAAD7//v/BAAIAAAAAAD7//z/BAAIAAEAAAD7//3/BAAIAAIAAAD7//7/BAAIAAMAAAD7////BAAIAAQAAAD7/wAABAAIAAUAAAD8//z/BAAJAAEAAAD8//3/BAAJAAIAAAD8//7/BAAJAAMAAAD8////BAAJAAQAAAD8/wAABAAJAAUAAAD9/wAABAAKAAUAAAD+/wAABAALAAUAAAD5////BAAGAAMAAAD5/wAABAAGAAMAAAD6/wEABAAGAAMAAAD6/wIABAAGAAMAAAD6/wMABAAGAAMAAAD5/wMABAAGAAMAAAD5/wQABAAGAAMAAAD6/wQABAAGAAMAAAD7/wQABAAGAAMAAAD8/wQABAAGAAMAAAD9/wQABAAGAAMAAAD9/wMABAAGAAMAAAD8/wMABAAGAAMAAAD7/wMABAAGAAMAAAD4/wIABAAGAAMAAAD4/wEABAAGAAMAAAD5/wEABAAGAAMAAAD6/wAABAAGAAMAAAD6////BAAGAAMAAAD5/wIABAAGAAMAAAD7/wEABAAGAAMAAAD8/wEABAAGAAMAAAD9/wEABAAGAAMAAAD+/wEABAAGAAMAAAD+/wIABAAGAAMAAAD9/wIABAAGAAMAAAD8/wIABAAGAAMAAAD7/wIABAAGAAMAAAD+/wMABAAGAAMAAAD+/wQABAAGAAMAAAD4/wMABAAGAAMAAAD4/wQABAAGAAMAAAD4////BAAGAAMAAAD4//7/AAAEAA8AAAD4//3/AAAEAA4AAADx////AAACAA8AAADy////AAACAA8AAADz//7/AAADAA8AAADz////AAACAA8AAAD0//7/AAAEAA8AAAD0////AAACAA8AAAD1//7/AAABAA8AAAD1////AAACAA8AAAD2////AAACAA8AAAD1//v/AAABAAwAAAD1//z/AAABAA0AAAD1//3/AAABAA4AAAD2//v/AAACAAwAAAD2//z/AAACAA0AAAD2//3/AAACAA4AAAD2//7/AAACAA8AAAD3//v/AAADAAwAAAD3//z/AAADAA0AAAD3//3/AAADAA4AAAD3//7/AAADAA8AAAD4//v/AAAEAAwAAAD4//z/AAAEAA0AAADx//v/AAADAAwAAADx//z/AAADAA0AAADx//3/AAADAA4AAADx//7/AAADAA8AAADy//v/AAAEAAwAAADy//z/AAAEAA0AAADy//3/AAAEAA4AAADy//7/AAAEAA8AAADz//v/AAADAAwAAADz//z/AAADAA0AAADz//3/AAADAA4AAAD0//v/AAAEAAwAAAD0//z/AAAEAA0AAAD0//3/AAAEAA4AAADv//v/AAABAAwAAADv//z/AAABAA0AAADv//3/AAABAA4AAADv//7/AAABAA8AAADw//v/AAACAAwAAADw//z/AAACAA0AAADw//3/AAACAA4AAADw//7/AAACAA8AAAD3////AAACAA8AAADw////AAACAA8AAADv////AAACAA8AAAA=")
tile_set = SubResource("TileSet_btr28")
+metadata/_edit_lock_ = true
[node name="BGTrees0" type="TileMapLayer" parent="Scene" unique_id=192241590]
position = Vector2(-49, 59)
tile_map_data = PackedByteArray("AAARAPn/BgAAAAEAAAARAPr/BgAAAAIAAAARAPv/BgAAAAMAAAARAPz/BgAAAAQAAAASAPj/BgABAAAAAAASAPn/BgABAAEAAAASAPr/BgABAAIAAAASAPv/BgABAAMAAAASAPz/BgABAAQAAAASAP3/BgABAAUAAAATAPj/BgACAAAAAAATAPn/BgACAAEAAAATAPr/BgACAAIAAAATAPv/BgACAAMAAAATAPz/BgACAAQAAAATAP3/BgACAAUAAAATAP7/BgACAAYAAAAUAPj/BgADAAAAAAAUAPn/BgADAAEAAAAUAPr/BgADAAIAAAAUAPv/BgADAAMAAAAUAPz/BgADAAQAAAAVAPn/BgAEAAEAAAAVAPr/BgAEAAIAAAAVAPv/BgAEAAMAAAAVAPz/BgAEAAQAAADx//n/BgAAAAEAAADx//r/BgAAAAIAAADx//v/BgAAAAMAAADx//z/BgAAAAQAAADy//j/BgABAAAAAADy//n/BgABAAEAAADy//r/BgABAAIAAADy//v/BgABAAMAAADy//z/BgABAAQAAADy//3/BgABAAUAAADz//j/BgACAAAAAADz//n/BgACAAEAAADz//r/BgACAAIAAADz//v/BgACAAMAAADz//z/BgACAAQAAADz//3/BgACAAUAAADz//7/BgACAAYAAAD0//j/BgADAAAAAAD0//n/BgADAAEAAAD0//r/BgADAAIAAAD0//v/BgADAAMAAAD0//z/BgADAAQAAAD1//n/BgAEAAEAAAD1//r/BgAEAAIAAAD1//v/BgAEAAMAAAD1//z/BgAEAAQAAAD1//7/AQAAABwAAAD1////AQAAAB0AAAD2//7/AQABABwAAAD2////AQABAB0AAAD3//7/AQACABwAAAD3////AQACAB0AAAD4////AQADAB0AAADv//7/AQAAABwAAADv////AQAAAB0AAADw//7/AQABABwAAADw////AQABAB0AAADx//7/AQACABwAAADx////AQACAB0AAADy////AQADAB0AAAD5//v/CAAWAAAAAAD5//z/CAAWAAEAAAD5//3/CAAWAAIAAAD5//7/CAAWAAMAAAD6//v/CAAXAAAAAAD6//z/CAAXAAEAAAD6//3/CAAXAAIAAAD6//7/CAAXAAMAAAD5////CAAUAAMAAAD6////CAAVAAMAAAD3//n/CAAaAAIAAAD3//r/CAAaAAMAAAD4//n/CAAbAAIAAAD4//r/CAAbAAMAAAA=")
tile_set = SubResource("TileSet_btr28")
+metadata/_edit_lock_ = true
[node name="BackgroundDecor" type="TileMapLayer" parent="Scene" unique_id=1466162064]
position = Vector2(-49, 59)
tile_map_data = PackedByteArray("AAD9//7/AQAAAAQAAAD9////AQAAAAUAAAD+//3/AQABAAMAAAD+//7/AQABAAQAAAD+////AQABAAUAAAD///3/AQACAAMAAAD///7/AQACAAQAAAD/////AQACAAUAAAAAAP3/AQADAAMAAAAAAP7/AQADAAQAAAAAAP//AQADAAUAAAABAP3/AQAEAAMAAAABAP7/AQAEAAQAAAABAP//AQAEAAUAAAACAP7/AQAFAAQAAAACAP//AQAFAAUAAAAEAP7/AQAGAAQAAAAEAP//AQAGAAUAAAAFAP3/AQAHAAMAAAAFAP7/AQAHAAQAAAAFAP//AQAHAAUAAAAGAP3/AQAIAAMAAAAGAP7/AQAIAAQAAAAGAP//AQAIAAUAAAAHAP3/AQAJAAMAAAAHAP7/AQAJAAQAAAAHAP//AQAJAAUAAAAIAP3/AQAKAAMAAAAIAP7/AQAKAAQAAAAIAP//AQAKAAUAAAAJAP7/AQALAAQAAAAJAP//AQALAAUAAADx////AQAKABUAAADy////AQALABUAAADz//7/AQAMABQAAADz////AQAMABUAAAD0//7/AQANABQAAAD0////AQANABUAAAD1//7/AQAOABQAAAD1////AQAOABUAAAD2////AQAPABUAAAARAP//AQAKABUAAAASAP//AQALABUAAAATAP7/AQAMABQAAAATAP//AQAMABUAAAAUAP7/AQANABQAAAAUAP//AQANABUAAAAVAP7/AQAOABQAAAAVAP//AQAOABUAAAAWAP//AQAPABUAAAA=")
tile_set = SubResource("TileSet_btr28")
+metadata/_edit_lock_ = true
+
+[node name="Cat" parent="Scene" unique_id=1357587844 groups=["animal_friends"] instance=ExtResource("17_k8jaa")]
+position = Vector2(127, 35)
+animal_type = "Cat"
+flip_horizontal = true
+show_shavings = true
[node name="LeafParticles" type="GPUParticles2D" parent="Scene" unique_id=420003071]
material = SubResource("CanvasItemMaterial_w330p")
@@ -2034,11 +2071,41 @@ script = ExtResource("16_5kdtj")
position = Vector2(-49, 59)
tile_map_data = PackedByteArray("AAD9/wAAAAABAAwAAAD+/wAAAAACAAwAAAD//wAAAAADAAwAAAAAAAAAAAABAAwAAAABAAAAAAACAAwAAAACAAAAAAADAAwAAAADAAAAAAABAAwAAAAEAAAAAAACAAwAAAAFAAAAAAADAAwAAAAGAAAAAAABAAwAAAAHAAAAAAACAAwAAAAIAAAAAAADAAwAAAAJAAAAAAABAAwAAAAKAAAAAAACAAwAAAAKAAEAAAACAA0AAAAKAAIAAAACAA4AAAAKAAMAAAADAA4AAAAKAAQAAAADAA8AAAAHAAEAAAACAA0AAAAHAAIAAAACAA4AAAAHAAMAAAACAA4AAAAIAAEAAAADAA0AAAAIAAIAAAADAA4AAAAIAAMAAAADAA4AAAAJAAEAAAABAA0AAAAJAAIAAAABAA4AAAAJAAMAAAACAA4AAAAEAAEAAAACAA0AAAAEAAIAAAACAA4AAAAEAAMAAAADAA4AAAAFAAEAAAADAA0AAAAFAAIAAAADAA4AAAAFAAMAAAACAA4AAAAGAAEAAAABAA0AAAAGAAIAAAABAA4AAAAGAAMAAAADAA4AAAABAAEAAAACAA0AAAABAAIAAAACAA4AAAABAAMAAAACAA4AAAACAAEAAAADAA0AAAACAAIAAAADAA4AAAACAAMAAAADAA4AAAADAAEAAAABAA0AAAADAAIAAAABAA4AAAADAAMAAAACAA4AAAD+/wEAAAACAA0AAAD+/wIAAAACAA4AAAD+/wMAAAACAA8AAAD//wEAAAADAA0AAAD//wIAAAADAA4AAAD//wMAAAADAA8AAAAAAAEAAAABAA0AAAAAAAIAAAABAA4AAAAAAAMAAAABAA8AAAD9/wEAAAABAA0AAAD9/wIAAAABAA4AAAD9/wMAAAABAA8AAAAHAAQAAAACAA8AAAAIAAQAAAADAA8AAAAJAAQAAAACAA8AAAAEAAQAAAADAA8AAAAFAAQAAAACAA8AAAAGAAQAAAADAA8AAAABAAQAAAACAA8AAAACAAQAAAADAA8AAAADAAQAAAACAA8AAAD+/wQAAAACABAAAAD//wQAAAADABAAAAAAAAQAAAABABAAAAD9/wQAAAABABAAAADw/wAAAAAAAAwAAADw/wEAAAACAA4AAADw/wIAAAACAA8AAADw/wMAAAACAA4AAADw/wQAAAACAA8AAADx/wAAAAABAAwAAADx/wEAAAADAA4AAADx/wIAAAADAA8AAADx/wMAAAADAA4AAADx/wQAAAADAA8AAADy/wAAAAACAAwAAADy/wEAAAACAA0AAADy/wIAAAACAA4AAADy/wMAAAACAA8AAADy/wQAAAACABAAAADz/wAAAAADAAwAAADz/wEAAAADAA0AAADz/wIAAAADAA4AAADz/wMAAAADAA8AAADz/wQAAAADABAAAAD0/wAAAAABAAwAAAD0/wEAAAABAA0AAAD0/wIAAAABAA4AAAD0/wMAAAABAA8AAAD0/wQAAAABABAAAAD1/wAAAAACAAwAAAD1/wEAAAACAA0AAAD1/wIAAAACAA4AAAD1/wMAAAACAA8AAAD1/wQAAAACABAAAAD2/wAAAAADAAwAAAD2/wEAAAADAA0AAAD2/wIAAAADAA4AAAD2/wMAAAADAA8AAAD2/wQAAAADABAAAAD3/wAAAAABAAwAAAD3/wEAAAABAA0AAAD3/wIAAAABAA4AAAD3/wMAAAABAA8AAAD3/wQAAAABABAAAAD4/wAAAAACAAwAAAD4/wEAAAACAA0AAAD4/wIAAAACAA4AAAD4/wMAAAACAA8AAAD4/wQAAAACABAAAAD5/wAAAAADAAwAAAD5/wEAAAADAA0AAAD5/wIAAAADAA4AAAD5/wMAAAADAA8AAAD5/wQAAAADABAAAAD6/wAAAAABAAwAAAD6/wEAAAABAA0AAAD6/wIAAAABAA4AAAD6/wMAAAABAA8AAAD6/wQAAAABABAAAAD7/wAAAAACAAwAAAD7/wEAAAACAA0AAAD7/wIAAAACAA4AAAD7/wMAAAACAA8AAAD7/wQAAAACABAAAAD8/wAAAAADAAwAAAD8/wEAAAADAA0AAAD8/wIAAAADAA4AAAD8/wMAAAADAA8AAAD8/wQAAAADABAAAAALAAAAAAADAAwAAAALAAEAAAADAA0AAAALAAIAAAADAA4AAAALAAMAAAADAA8AAAALAAQAAAADABAAAAAMAAAAAAABAAwAAAAMAAEAAAABAA0AAAAMAAIAAAABAA4AAAAMAAMAAAABAA8AAAAMAAQAAAABABAAAAANAAAAAAACAAwAAAANAAEAAAACAA0AAAANAAIAAAACAA4AAAANAAMAAAACAA8AAAANAAQAAAACABAAAAAOAAAAAAADAAwAAAAOAAEAAAADAA0AAAAOAAIAAAADAA4AAAAOAAMAAAADAA8AAAAOAAQAAAADABAAAAAPAAAAAAABAAwAAAAPAAEAAAABAA0AAAAPAAIAAAABAA4AAAAPAAMAAAABAA8AAAAPAAQAAAABABAAAAAQAAAAAAACAAwAAAAQAAEAAAACAA0AAAAQAAIAAAACAA4AAAAQAAMAAAACAA8AAAAQAAQAAAACABAAAAARAAAAAAABAAwAAAARAAEAAAABAA0AAAARAAIAAAABAA4AAAARAAMAAAABAA8AAAARAAQAAAABABAAAAASAAAAAAACAAwAAAASAAEAAAACAA0AAAASAAIAAAACAA4AAAASAAMAAAACAA8AAAASAAQAAAACABAAAAATAAAAAAADAAwAAAATAAEAAAADAA0AAAATAAIAAAADAA4AAAATAAMAAAADAA8AAAATAAQAAAADABAAAAAUAAAAAAABAAwAAAAUAAEAAAABAA0AAAAUAAIAAAABAA4AAAAUAAMAAAABAA8AAAAUAAQAAAABABAAAAAVAAAAAAACAAwAAAAVAAEAAAACAA0AAAAVAAIAAAACAA4AAAAVAAMAAAACAA8AAAAVAAQAAAACABAAAAAWAAAAAAADAAwAAAAWAAEAAAADAA0AAAAWAAIAAAADAA4AAAAWAAMAAAADAA8AAAAWAAQAAAADABAAAADx/wUAAAADAA4AAADx/wYAAAADAA8AAADy/wUAAAADAA4AAADy/wYAAAADAA8AAADz/wUAAAACAA4AAADz/wYAAAACAA8AAAD0/wUAAAADAA4AAAD0/wYAAAADAA8AAAD1/wUAAAACAA4AAAD1/wYAAAACAA8AAAD2/wUAAAADAA4AAAD2/wYAAAADAA8AAAD3/wUAAAACAA4AAAD3/wYAAAACAA8AAAD4/wUAAAADAA4AAAD4/wYAAAADAA8AAAD5/wUAAAACAA4AAAD5/wYAAAACAA8AAAD6/wUAAAADAA4AAAD6/wYAAAADAA8AAAD7/wUAAAACAA4AAAD7/wYAAAACAA8AAAD8/wUAAAADAA4AAAD8/wYAAAADAA8AAAD9/wUAAAACAA4AAAD9/wYAAAACAA8AAAD+/wUAAAADAA4AAAD+/wYAAAADAA8AAAD//wUAAAACAA4AAAD//wYAAAACAA8AAAAAAAUAAAADAA4AAAAAAAYAAAADAA8AAAABAAUAAAACAA4AAAABAAYAAAACAA8AAAACAAUAAAADAA4AAAACAAYAAAADAA8AAAADAAUAAAACAA4AAAADAAYAAAACAA8AAAAEAAUAAAADAA4AAAAEAAYAAAADAA8AAAAFAAUAAAACAA4AAAAFAAYAAAACAA8AAAAGAAUAAAADAA4AAAAGAAYAAAADAA8AAAAHAAUAAAACAA4AAAAHAAYAAAACAA8AAAAIAAUAAAADAA4AAAAIAAYAAAADAA8AAAAJAAUAAAACAA4AAAAJAAYAAAACAA8AAAAKAAUAAAADAA4AAAAKAAYAAAADAA8AAAALAAUAAAACAA4AAAALAAYAAAACAA8AAAAMAAUAAAADAA4AAAAMAAYAAAADAA8AAAANAAUAAAACAA4AAAANAAYAAAACAA8AAAAOAAUAAAADAA4AAAAOAAYAAAADAA8AAAAPAAUAAAACAA4AAAAPAAYAAAACAA8AAAAQAAUAAAADAA4AAAAQAAYAAAADAA8AAAARAAUAAAACAA4AAAARAAYAAAACAA8AAAASAAUAAAADAA4AAAASAAYAAAADAA8AAAATAAUAAAACAA4AAAATAAYAAAACAA8AAAAUAAUAAAADAA4AAAAUAAYAAAADAA8AAAAVAAUAAAACAA4AAAAVAAYAAAACAA8AAAAWAAUAAAADAA4AAAAWAAYAAAADAA8AAADw/wUAAAACAA4AAADw/wYAAAACAA8AAAAKAP7/AQAOAAAAAAAKAP//AQAOAAEAAAALAP7/AQAPAAAAAAALAP//AQAPAAEAAAA=")
tile_set = SubResource("TileSet_btr28")
+metadata/_edit_lock_ = true
[node name="InteractionLayer" type="Node2D" parent="Scene" unique_id=955061762]
+[node name="Goose" parent="Scene/InteractionLayer" unique_id=495867136 groups=["animal_friends"] instance=ExtResource("17_k8jaa")]
+position = Vector2(-187, 19)
+animal_type = "Goose"
+flip_horizontal = true
+show_shavings = true
+
+[node name="Fox" parent="Scene/InteractionLayer" unique_id=1333255729 groups=["animal_friends"] instance=ExtResource("17_k8jaa")]
+position = Vector2(-52, 51)
+animal_type = "Fox"
+show_shavings = true
+
+[node name="Frog" parent="Scene/InteractionLayer" unique_id=1883056059 groups=["animal_friends"] instance=ExtResource("17_k8jaa")]
+position = Vector2(-127, 51)
+animal_type = "Frog"
+show_shavings = true
+
+[node name="Wolf" parent="Scene/InteractionLayer" unique_id=526034363 groups=["animal_friends"] instance=ExtResource("17_k8jaa")]
+position = Vector2(-149, -29)
+animal_type = "Wolf"
+flip_horizontal = true
+show_shavings = true
+
+[node name="Dog" parent="Scene/InteractionLayer" unique_id=165373951 instance=ExtResource("17_k8jaa")]
+position = Vector2(28, 59)
+animal_type = "Dog"
+show_shavings = false
+metadata/_edit_lock_ = true
+
[node name="Character" parent="Scene/InteractionLayer" unique_id=916171989 instance=ExtResource("17_1hpkv")]
-position = Vector2(-62, 29)
+position = Vector2(-70, 29)
+metadata/_edit_lock_ = true
[node name="StockPile" parent="Scene/InteractionLayer" unique_id=1961584119 instance=ExtResource("17_deeqb")]
position = Vector2(-215, 51)
@@ -2051,17 +2118,21 @@ position = Vector2(164, 48)
position = Vector2(-49, 59)
tile_map_data = PackedByteArray("AAACAP7/AQACAAYAAAACAP//AQACAAcAAAADAP7/AQADAAYAAAADAP//AQADAAcAAAAHAP//AQAYAAcAAAAIAP//AQAZAAcAAADz////AQAIAAkAAAD0////AQAJAAkAAAD3////AQAKAAkAAAD4////AQALAAkAAADw////AQAWAAcAAADx////AQAXAAcAAAD8////AQAGAA0AAAD9////AQAHAA0AAAD5////AQAGAA8AAAD6////AQAHAA8AAAAOAP//AQAIAA8AAAAPAP7/AQAJAA4AAAAPAP//AQAJAA8AAAAQAP7/AQAKAA4AAAAQAP//AQAKAA8AAAARAP//AQALAA8AAAATAP//AQAGAA0AAAAUAP//AQAHAA0AAAAJAP//AQAGAA0AAAAKAP//AQAHAA0AAAALAP//AQAKAAkAAAAMAP//AQALAAkAAAA=")
tile_set = SubResource("TileSet_btr28")
+metadata/_edit_lock_ = true
[node name="Camera2D" type="Camera2D" parent="." unique_id=1719721614]
zoom = Vector2(2, 2)
+metadata/_edit_lock_ = true
[node name="UI" type="Control" parent="." unique_id=1827364964]
+light_mask = 2
layout_mode = 3
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
theme = ExtResource("22_q7h7c")
script = ExtResource("17_q7h7c")
+metadata/_edit_lock_ = true
[node name="Panel" type="Panel" parent="UI" unique_id=35561461]
visible = false
@@ -2080,31 +2151,77 @@ offset_right = 45.0
offset_bottom = -5.0
theme_override_font_sizes/font_size = 12
+[node name="CurrencyLabel" type="Label" parent="UI" unique_id=508512755]
+unique_name_in_owner = true
+layout_mode = 0
+offset_left = -41.0
+offset_top = -137.0
+offset_right = 43.0
+offset_bottom = -120.375
+theme = ExtResource("22_q7h7c")
+theme_override_fonts/font = SubResource("FontVariation_hcndq")
+theme_override_font_sizes/font_size = 21
+text = "Currency"
+horizontal_alignment = 1
+
+[node name="CurrenciesBG" type="Sprite2D" parent="UI" unique_id=400039529]
+position = Vector2(-246.24998, -132)
+scale = Vector2(0.7450001, 0.44000018)
+texture = ExtResource("22_hcndq")
+
[node name="Currencies" type="VBoxContainer" parent="UI" unique_id=1952407607]
layout_mode = 0
-offset_left = -284.0
-offset_top = -159.0
-offset_right = -239.0
-offset_bottom = -103.0
+offset_left = -278.0
+offset_top = -151.0
+offset_right = -182.0
+offset_bottom = -114.0
theme_override_constants/separation = 5
alignment = 1
-[node name="CurrencyLabel" type="Label" parent="UI/Currencies" unique_id=508512755]
-unique_name_in_owner = true
+[node name="HBoxContainer" type="HBoxContainer" parent="UI/Currencies" unique_id=1028765341]
layout_mode = 2
-theme = ExtResource("22_q7h7c")
-text = "Currency"
-[node name="WoodLabel" type="Label" parent="UI/Currencies" unique_id=173301448]
+[node name="RichTextLabel2" type="RichTextLabel" parent="UI/Currencies/HBoxContainer" unique_id=622882040]
+clip_contents = false
+custom_minimum_size = Vector2(10, 14)
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+theme_override_fonts/normal_font = ExtResource("24_chdjo")
+theme_override_font_sizes/normal_font_size = 9
+text = "🪵"
+scroll_active = false
+autowrap_mode = 0
+autowrap_trim_flags = 0
+
+[node name="WoodLabel" type="Label" parent="UI/Currencies/HBoxContainer" unique_id=173301448]
unique_name_in_owner = true
layout_mode = 2
theme = ExtResource("22_q7h7c")
+theme_override_font_sizes/font_size = 18
text = "0"
-[node name="StockLabel" type="Label" parent="UI/Currencies" unique_id=1137115173]
+[node name="HBoxContainer2" type="HBoxContainer" parent="UI/Currencies" unique_id=231882591]
+layout_mode = 2
+
+[node name="RichTextLabel" type="RichTextLabel" parent="UI/Currencies/HBoxContainer2" unique_id=248143892]
+clip_contents = false
+custom_minimum_size = Vector2(10, 14)
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+theme_override_fonts/normal_font = ExtResource("24_chdjo")
+theme_override_font_sizes/normal_font_size = 10
+text = "📦"
+scroll_active = false
+autowrap_mode = 0
+autowrap_trim_flags = 0
+
+[node name="StockLabel" type="Label" parent="UI/Currencies/HBoxContainer2" unique_id=1137115173]
unique_name_in_owner = true
layout_mode = 2
theme = ExtResource("22_q7h7c")
+theme_override_font_sizes/font_size = 18
text = "0"
[node name="UnlockContainer" type="GridContainer" parent="UI" unique_id=1701514762]
@@ -2112,9 +2229,9 @@ layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
-offset_left = -282.0
-offset_top = 79.0
-offset_right = 242.0
+offset_left = -257.0
+offset_top = 87.0
+offset_right = 217.0
offset_bottom = 115.0
grow_horizontal = 2
grow_vertical = 2
@@ -2128,10 +2245,11 @@ size_flags_vertical = 0
[node name="Timer" type="Label" parent="UI" unique_id=405529286]
unique_name_in_owner = true
layout_mode = 0
-offset_left = -20.0
+offset_left = -29.0
offset_top = -157.0
-offset_right = 20.0
-offset_bottom = -144.0
+offset_right = 29.0
+offset_bottom = -138.79167
+theme_override_font_sizes/font_size = 23
text = "00:00:00"
[node name="GameCompleted" type="Panel" parent="UI" unique_id=1824061781]
@@ -2156,6 +2274,7 @@ text = "YOU WIN!"
horizontal_alignment = 1
[node name="CompletionTimeLabel" type="Label" parent="UI/GameCompleted" unique_id=1845505160]
+unique_name_in_owner = true
layout_mode = 0
offset_left = 221.0
offset_top = 211.0
@@ -2183,6 +2302,7 @@ Completion Time"
horizontal_alignment = 1
[node name="TextEdit" type="TextEdit" parent="UI/GameCompleted" unique_id=637671853]
+unique_name_in_owner = true
layout_mode = 0
offset_left = 35.0
offset_top = 277.0
@@ -2192,6 +2312,7 @@ theme = ExtResource("22_q7h7c")
placeholder_text = "Your Name (Optional)"
[node name="SubmitScoreButton" type="TextureButton" parent="UI/GameCompleted" unique_id=111534642]
+unique_name_in_owner = true
custom_minimum_size = Vector2(100, 25)
layout_mode = 0
offset_left = 171.0
@@ -2221,6 +2342,17 @@ theme = ExtResource("22_q7h7c")
text = "Submit Score"
horizontal_alignment = 1
+[node name="SubmissionStatusLabel" type="Label" parent="UI/GameCompleted" unique_id=976543210]
+unique_name_in_owner = true
+layout_mode = 0
+offset_left = 281.0
+offset_top = 277.0
+offset_right = 450.0
+offset_bottom = 304.0
+theme_override_font_sizes/font_size = 12
+horizontal_alignment = 1
+vertical_alignment = 1
+
[node name="ContinueButton" type="TextureButton" parent="UI/GameCompleted" unique_id=1202982034]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 25)
@@ -2252,4 +2384,33 @@ theme = ExtResource("22_q7h7c")
text = "Continue Whittling"
horizontal_alignment = 1
+[node name="ConfigButton" type="Button" parent="UI" unique_id=2147483647]
+unique_name_in_owner = true
+layout_mode = 0
+offset_left = 255.0
+offset_top = 134.0
+offset_right = 275.0
+offset_bottom = 153.0
+theme = ExtResource("22_q7h7c")
+theme_override_colors/font_color = Color(0.3764706, 0.31764707, 0.16078432, 1)
+theme_override_colors/font_pressed_color = Color(0.7882353, 0.36078432, 0.19215687, 1)
+theme_override_colors/font_hover_color = Color(0.49803922, 0.4627451, 0.2901961, 1)
+theme_override_fonts/font = ExtResource("24_chdjo")
+theme_override_font_sizes/font_size = 13
+theme_override_styles/normal = SubResource("StyleBoxEmpty_mjadu")
+theme_override_styles/pressed = SubResource("StyleBoxEmpty_k8jaa")
+theme_override_styles/hover = SubResource("StyleBoxEmpty_hp34j")
+theme_override_styles/disabled = SubResource("StyleBoxEmpty_lxcs1")
+theme_override_styles/focus = SubResource("StyleBoxEmpty_hxr5a")
+text = "🎵"
+
+[node name="ConfigPanel" parent="UI" unique_id=281194599 instance=ExtResource("20_config")]
+visible = false
+layout_mode = 1
+offset_left = -129.0
+offset_top = -132.0
+offset_right = 90.0
+offset_bottom = 81.0
+theme_override_styles/panel = SubResource("StyleBoxFlat_mjadu")
+
[connection signal="pressed" from="UI/GameCompleted/ContinueButton" to="UI" method="_on_continue_button_pressed"]
diff --git a/scenes/scripts/animal.gd b/scenes/scripts/animal.gd
new file mode 100644
index 0000000..e739839
--- /dev/null
+++ b/scenes/scripts/animal.gd
@@ -0,0 +1,79 @@
+@tool
+extends Node2D
+
+@export var animal_type : String:
+ set(value):
+ animal_type = value
+ if is_node_ready():
+ _update_sprite()
+
+@export var flip_horizontal : bool = false:
+ set(value):
+ flip_horizontal = value
+ if is_node_ready():
+ _update_sprite()
+
+@export var show_shavings : bool = false:
+ set(value):
+ show_shavings = value
+ if is_node_ready():
+ _update_shavings()
+
+# Called when the node enters the scene tree for the first time.
+func _ready() -> void:
+ _update_sprite()
+ _update_shavings()
+
+
+func _update_sprite() -> void:
+
+ # Hide all animal sprites first
+ %Fox.visible = false
+ %Porcupine.visible = false
+ %Wolf.visible = false
+ %Cat.visible = false
+ %Goose.visible = false
+ %Frog.visible = false
+ %Chick.visible = false
+ %Dog.visible = false
+
+ # Show the appropriate animal based on animal_type
+ var active_sprite: AnimatedSprite2D
+ match animal_type:
+ "Fox":
+ %Fox.visible = true
+ active_sprite = %Fox
+ "Porcupine":
+ %Porcupine.visible = true
+ active_sprite = %Porcupine
+ "Wolf":
+ %Wolf.visible = true
+ active_sprite = %Wolf
+ "Cat":
+ %Cat.visible = true
+ active_sprite = %Cat
+ "Goose":
+ %Goose.visible = true
+ active_sprite = %Goose
+ "Frog":
+ %Frog.visible = true
+ active_sprite = %Frog
+ "Chick":
+ %Chick.visible = true
+ active_sprite = %Chick
+ "Dog":
+ %Dog.visible = true
+ active_sprite = %Dog
+
+ # Apply horizontal flip if enabled
+ if active_sprite:
+ active_sprite.flip_h = flip_horizontal
+
+
+func _update_shavings() -> void:
+ %Shavings.visible = show_shavings
+
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta: float) -> void:
+ pass
diff --git a/scenes/scripts/animal.gd.uid b/scenes/scripts/animal.gd.uid
new file mode 100644
index 0000000..20884b1
--- /dev/null
+++ b/scenes/scripts/animal.gd.uid
@@ -0,0 +1 @@
+uid://uhlsvqaaemre
diff --git a/scenes/scripts/button.gd b/scenes/scripts/button.gd
index 13baf3f..30da171 100644
--- a/scenes/scripts/button.gd
+++ b/scenes/scripts/button.gd
@@ -1,58 +1,136 @@
extends TextureButton
-@onready var label: Label = $CenterContainer/Label # Adjust path to your Label node
+@onready var name_label: Label = $CenterContainer/VBoxContainer/NameLabel
+@onready var price_label: Label = $CenterContainer/VBoxContainer/PriceLabel
-var unlock_id = "" # Store the unlock ID
+var unlock_id = -1 # Store the unlock ID
+var unlock_description_text = "" # Store the description for custom tooltip
func _ready():
- label.visible = false # Hide label initially
+ name_label.visible = false # Hide label initially
+ price_label.visible = false
adjust_label_font_size()
# Connect the pressed signal
pressed.connect(_on_button_pressed)
+ # Connect to currency changes to update button state
+ Inventory.currency_changed.connect(_on_currency_changed)
+ # Connect to unlock events to update button state when items are unlocked
+ Unlocks.item_unlocked.connect(_on_item_unlocked)
func setup(unlock_data):
# Log.pr("Setting up button for unlock:", unlock_data.unlock_name)
unlock_id = unlock_data.unlock_id # Store the ID
- if label:
- label.visible = false
- label.text = unlock_data.unlock_name + " " + str(unlock_data.get_next_rank())
- label.text = label.text + " - " + Global.currency_symbol + str(unlock_data.get_next_cost())
- label.text = label.text + "\n" + unlock_data.get_next_modifiers_string()
- #self.disabled = unlock_data.is_unlocked
+ if name_label and price_label:
+ name_label.visible = false
+ price_label.visible = false
+ update_button_state(unlock_data)
adjust_label_font_size()
else:
- Log.pr("Warning: Label node not found in button.")
+ Log.pr("Warning: Label nodes not found in button.")
+
+func update_button_state(unlock_data):
+ # Store unlock description for custom tooltip
+ if unlock_data.unlock_description:
+ unlock_description_text = unlock_data.unlock_description
+ tooltip_text = unlock_data.unlock_description
+
+ # Check if at max rank
+ if not unlock_data.can_rank_up():
+ self.disabled = true
+ name_label.text = unlock_data.unlock_name + " (MAX)"
+ price_label.text = ""
+ return
+
+ # Build name text - only show rank if it's a scaling unlock
+ if unlock_data.is_scaling:
+ name_label.text = unlock_data.unlock_name + " " + str(unlock_data.get_next_rank())
+ else:
+ name_label.text = unlock_data.unlock_name
+
+ # Build price text
+ price_label.text = Global.currency_symbol + Global.format_number(unlock_data.get_next_cost())
+
+ # Check if player has enough currency
+ var cost = unlock_data.get_next_cost()
+ var current_currency = Inventory.get_currency()
+ self.disabled = current_currency < cost
+
+func _on_currency_changed(new_amount: float):
+ # Update button state when currency changes
+ if unlock_id >= 0:
+ var unlock_data = Unlocks.get_unlock_by_id(unlock_id)
+ if unlock_data:
+ update_button_state(unlock_data)
+
+func _on_item_unlocked():
+ # Update button state when any item is unlocked (in case this button reached max rank)
+ if unlock_id >= 0:
+ var unlock_data = Unlocks.get_unlock_by_id(unlock_id)
+ if unlock_data:
+ update_button_state(unlock_data)
+ adjust_label_font_size()
func _on_button_pressed():
# Log.pr("Button pressed, unlocking item:", unlock_id)
Unlocks.unlock_item(unlock_id)
func adjust_label_font_size():
- if not label:
+ if not name_label or not price_label:
return
var available_width = size.x - 10
var available_height = size.y - 10
- # Start with a reasonable font size
- var font_size = 32
+
+ # Calculate font sizes for both labels
+ # Name label gets 60% of the height, price label gets 40%
+ var name_height = available_height * 0.6
+ var price_height = available_height * 0.4
+
+ # Start with reasonable font sizes
+ var name_font_size = 32
+ var price_font_size = 24
var min_font_size = 8
- # Get or create a font
- var font = label.get_theme_font("font")
- # Binary search for the optimal font size
- while font_size > min_font_size:
- label.add_theme_font_size_override("font_size", font_size)
- # Force update and get the actual text size
- await get_tree().process_frame
- var text_size = font.get_string_size(label.text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size)
- # Check if it fits
- if text_size.x <= available_width and text_size.y <= available_height:
+
+ # Get fonts
+ var name_font = name_label.get_theme_font("font")
+ var price_font = price_label.get_theme_font("font")
+
+ # Calculate name label font size without applying it
+ while name_font_size > min_font_size:
+ var text_size = name_font.get_string_size(name_label.text, HORIZONTAL_ALIGNMENT_LEFT, -1, name_font_size)
+ if text_size.x <= available_width and text_size.y <= name_height:
break
- # Reduce font size and try again
- font_size -= 1
- label.add_theme_font_size_override("font_size", font_size)
- label.visible = true # Show label after resizing is complete
+ name_font_size -= 1
+
+ # Calculate price label font size without applying it
+ while price_font_size > min_font_size:
+ var text_size = price_font.get_string_size(price_label.text, HORIZONTAL_ALIGNMENT_LEFT, -1, price_font_size)
+ if text_size.x <= available_width and text_size.y <= price_height:
+ break
+ price_font_size -= 1
+
+ # Apply both font sizes at once
+ name_label.add_theme_font_size_override("font_size", name_font_size)
+ price_label.add_theme_font_size_override("font_size", price_font_size)
+
+ name_label.visible = true
+ price_label.visible = true
# Call this function whenever you change the label text
func set_label_text(new_text: String):
- if label:
- label.visible = false # Hide while resizing
- label.text = new_text
+ if name_label:
+ name_label.visible = false # Hide while resizing
+ price_label.visible = false
+ name_label.text = new_text
+ price_label.text = ""
adjust_label_font_size()
+
+# Override to create custom tooltip with larger font
+func _make_custom_tooltip(for_text: String) -> Object:
+ var tooltip_label = Label.new()
+ tooltip_label.text = for_text
+ tooltip_label.add_theme_font_size_override("font_size", 26)
+
+ # Create a panel container for background
+ var panel = PanelContainer.new()
+ panel.add_child(tooltip_label)
+
+ return panel
diff --git a/scenes/scripts/config_panel.gd b/scenes/scripts/config_panel.gd
new file mode 100644
index 0000000..80ee62e
--- /dev/null
+++ b/scenes/scripts/config_panel.gd
@@ -0,0 +1,41 @@
+extends Panel
+
+@onready var music_toggle: CheckButton = %MusicToggle
+@onready var chop_toggle: CheckButton = %ChopToggle
+@onready var money_toggle: CheckButton = %MoneyToggle
+@onready var close_button: Button = %CloseButton
+
+func _ready():
+ # Initialize toggle states from Global settings
+ music_toggle.button_pressed = Global.play_background_music
+ chop_toggle.button_pressed = Global.play_chop_sound
+ money_toggle.button_pressed = Global.play_money_sound
+
+ # Connect signals
+ music_toggle.toggled.connect(_on_music_toggled)
+ chop_toggle.toggled.connect(_on_chop_toggled)
+ money_toggle.toggled.connect(_on_money_toggled)
+ close_button.pressed.connect(_on_close_pressed)
+
+ # Start hidden
+ visible = false
+
+func _on_music_toggled(enabled: bool):
+ Global.play_background_music = enabled
+ var audio_manager = get_node("/root/Audio")
+ if enabled:
+ audio_manager.play_background_music()
+ else:
+ audio_manager.stop_background_music()
+
+func _on_chop_toggled(enabled: bool):
+ Global.play_chop_sound = enabled
+
+func _on_money_toggled(enabled: bool):
+ Global.play_money_sound = enabled
+
+func _on_close_pressed():
+ visible = false
+
+func toggle_visibility():
+ visible = !visible
diff --git a/scenes/scripts/config_panel.gd.uid b/scenes/scripts/config_panel.gd.uid
new file mode 100644
index 0000000..a8d36ed
--- /dev/null
+++ b/scenes/scripts/config_panel.gd.uid
@@ -0,0 +1 @@
+uid://2jm25u1ehlac
diff --git a/scenes/scripts/ui_control.gd b/scenes/scripts/ui_control.gd
index f94d094..c163777 100644
--- a/scenes/scripts/ui_control.gd
+++ b/scenes/scripts/ui_control.gd
@@ -8,10 +8,19 @@ extends Control
@onready var timer_label : Label = %Timer
@onready var game_complete_screen : Panel = %GameCompleted
+@onready var completion_time_label : Label = %CompletionTimeLabel
@onready var continue_button : TextureButton = %ContinueButton
+@onready var config_button : Button = %ConfigButton
+@onready var config_panel : Panel = %ConfigPanel
+@onready var player_name_input : TextEdit = %TextEdit
+@onready var submit_score_button : TextureButton = %SubmitScoreButton
+@onready var submission_status_label : Label = %SubmissionStatusLabel
var game_timer : Timer
var elapsed_time := 0.0
+var current_nonce : String = ""
+var bridge = null
+var score_submitted := false
func _ready():
populate_modifiers_display()
@@ -34,6 +43,7 @@ func _ready():
Inventory.currency_changed.connect(_on_currency_changed)
Inventory.currency_added.connect(spawn_currency_increase)
+ Inventory.currency_added.connect(_on_currency_added)
Inventory.wood_changed.connect(_on_currency_changed)
Inventory.wood_added.connect(spawn_wood_increase)
Inventory.stock_added.connect(spawn_stock_increase)
@@ -41,17 +51,27 @@ func _ready():
Unlocks.item_unlocked.connect(populate_unlock_buttons)
GameManager.currency_goal_met.connect(_on_currency_goal_met)
+ if config_button:
+ config_button.pressed.connect(_on_config_button_pressed)
+
+ if submit_score_button:
+ submit_score_button.pressed.connect(_on_submit_score_button_pressed)
+
+ # Initialize JavaScript bridge for web builds
+ if OS.has_feature("web"):
+ bridge = JavaScriptBridge.get_interface("godotBridge")
+
# get_tree().paused = true
func update_currency_label():
- currency_label.text = Global.currency_symbol + " " + str(int(Inventory.get_currency()))
+ currency_label.text = Global.currency_symbol + Global.format_number(Inventory.get_currency())
func update_wood_label():
- wood_label.text = "W: " + str(int(Inventory.get_wood()))
+ wood_label.text = Global.format_number(Inventory.get_wood())
func update_stock_label():
- stock_label.text = "S: " + str(int(Inventory.get_stock()))
+ stock_label.text = Global.format_number(Inventory.get_stock())
func spawn_currency_increase(value, _total):
spawn_inventory_change_value(value, _total, "+", Global.currency_symbol, Global.money_color)
@@ -64,7 +84,7 @@ func spawn_stock_increase(value, _total):
func spawn_inventory_change_value(value, _total, display_sign: String = "+", symbol: String = "", label_color: Color = Color.WHITE):
var float_label = Label.new()
- float_label.text = display_sign + symbol + str(int(abs(value)))
+ float_label.text = display_sign + symbol + Global.format_number(abs(value))
float_label.add_theme_font_size_override("font_size", 16)
float_label.modulate = label_color
@@ -99,10 +119,10 @@ func populate_unlock_buttons():
func populate_modifiers_display():
var modifiers_text = ""
- modifiers_text = modifiers_text + "Sale Price: " + Global.currency_symbol + str(Unlocks.get_sale_price_per_item()) + "\n"
- modifiers_text = modifiers_text + "Items Produced Per Tick: " + str(Unlocks.get_items_produced_per_tick()) + "\n"
- modifiers_text = modifiers_text + "Wood per Click: " + str(Unlocks.get_wood_per_click()) + "\n\n"
- modifiers_text = modifiers_text + "Demand: " + str(int(Unlocks.get_sale_demand())) + "\n\n"
+ modifiers_text = modifiers_text + "Sale Price: " + Global.currency_symbol + Global.format_number(Unlocks.get_sale_price_per_item()) + "\n"
+ modifiers_text = modifiers_text + "Items Produced Per Tick: " + Global.format_number(Unlocks.get_items_produced_per_tick()) + "\n"
+ modifiers_text = modifiers_text + "Wood per Click: " + Global.format_number(Unlocks.get_wood_per_click()) + "\n\n"
+ modifiers_text = modifiers_text + "Demand: " + Global.format_number(Unlocks.get_sale_demand()) + "\n\n"
modifiers_text = modifiers_text + "Current Modifiers:\n"
for key in Unlocks.current_modifiers.keys():
@@ -129,7 +149,20 @@ func _on_timer_tick():
func _on_currency_goal_met():
Log.pr("Currency goal met!")
get_tree().paused = true
+ completion_time_label.text = format_time(elapsed_time)
game_complete_screen.visible = true
+ score_submitted = false
+
+ # Request nonce from API when game is completed
+ if bridge != null:
+ # Disable submit button until nonce is received
+ submit_score_button.disabled = true
+ submission_status_label.visible = false
+ _request_nonce()
+ else:
+ # No web bridge available, hide the submit button entirely
+ submit_score_button.visible = false
+ submission_status_label.visible = false
func format_time(seconds: float) -> String:
var total_seconds := int(seconds)
@@ -145,3 +178,228 @@ func _on_continue_button_pressed() -> void:
Global.game_continue_pressed = true
game_complete_screen.visible = false
get_tree().paused = false
+
+func _on_config_button_pressed() -> void:
+ if config_panel:
+ config_panel.toggle_visibility()
+
+func _on_currency_added(_value, _total):
+ var audio_manager = get_node("/root/Audio")
+ if audio_manager:
+ audio_manager.play_money_sound()
+
+# HIGH SCORE SUBMISSION FUNCTIONS
+
+func _request_nonce():
+ if bridge == null:
+ return
+
+ # Call JavaScript bridge to request nonce
+ var result = bridge.requestNonce()
+
+ # Set up polling to check if nonce was received
+ # (since JS callbacks are async)
+ var nonce_check_timer = Timer.new()
+ nonce_check_timer.wait_time = 0.5
+ nonce_check_timer.one_shot = false
+ nonce_check_timer.process_mode = Node.PROCESS_MODE_ALWAYS # Run even when paused
+ add_child(nonce_check_timer)
+
+ var attempts = 0
+ var max_attempts = 20 # 10 seconds total
+
+ nonce_check_timer.timeout.connect(func():
+ attempts += 1
+ var nonce = _get_nonce_from_js()
+
+ if nonce != null and nonce != "" and nonce != "null":
+ current_nonce = nonce
+ # Enable submit button when nonce is ready
+ submit_score_button.disabled = false
+ nonce_check_timer.stop()
+ nonce_check_timer.queue_free()
+ elif attempts >= max_attempts:
+ # Failed to get nonce - show error and keep button disabled
+ submission_status_label.text = "Failed to connect to server"
+ submission_status_label.modulate = Color(1.0, 0.3, 0.3) # Red
+ submission_status_label.visible = true
+ nonce_check_timer.stop()
+ nonce_check_timer.queue_free()
+ )
+
+ nonce_check_timer.start()
+
+func _get_nonce_from_js() -> String:
+ if bridge == null:
+ return ""
+
+ # Try to get the nonce that JavaScript stored
+ var result = JavaScriptBridge.eval("""
+ (function() {
+ if (window.godotNonce) {
+ return window.godotNonce;
+ }
+ return '';
+ })();
+ """, true)
+
+ return str(result) if result != null else ""
+
+func _on_submit_score_button_pressed():
+ if score_submitted:
+ submission_status_label.text = "Score already submitted!"
+ submission_status_label.modulate = Color(1.0, 0.8, 0.3) # Yellow
+ submission_status_label.visible = true
+ return
+
+ if bridge == null or current_nonce == "":
+ # This shouldn't happen as button should be disabled
+ return
+
+ # Disable button during submission and show status
+ submit_score_button.disabled = true
+ submission_status_label.text = "Submitting..."
+ submission_status_label.modulate = Color(1.0, 1.0, 1.0) # White
+ submission_status_label.visible = true
+
+ # Get player name from input and sanitize
+ var player_name = _sanitize_player_name(player_name_input.text)
+ if player_name == "":
+ player_name = "Anonymous"
+
+ # Get completion time in seconds
+ var completion_time_seconds = int(elapsed_time)
+
+ # Create and encode payload on GDScript side
+ var encoded_payload = _create_encoded_payload(current_nonce, player_name, completion_time_seconds)
+
+ # Call JavaScript to submit score with pre-encoded payload and nonce
+ bridge.submitScore(encoded_payload, current_nonce)
+
+ # Poll for submission result
+ var submit_check_timer = Timer.new()
+ submit_check_timer.wait_time = 0.5
+ submit_check_timer.one_shot = false
+ submit_check_timer.process_mode = Node.PROCESS_MODE_ALWAYS # Run even when paused
+ add_child(submit_check_timer)
+
+ var attempts = 0
+ var max_attempts = 30 # 15 seconds total
+
+ submit_check_timer.timeout.connect(func():
+ attempts += 1
+ var result = _get_submission_result_from_js()
+
+ if result.has("completed") and result["completed"]:
+ submission_status_label.visible = true
+ if result["success"]:
+ submission_status_label.text = result.get("message", "Score submitted!")
+ submission_status_label.modulate = Color(0.5, 1.0, 0.5) # Green
+ score_submitted = true
+
+ # Show rank if available
+ if result.has("rank") and result["rank"] > 0:
+ submission_status_label.text += " (Rank #%d)" % result["rank"]
+ else:
+ submission_status_label.text = result.get("message", "Failed to submit")
+ submission_status_label.modulate = Color(1.0, 0.3, 0.3) # Red
+ submit_score_button.disabled = false
+
+ submit_check_timer.stop()
+ submit_check_timer.queue_free()
+ elif attempts >= max_attempts:
+ submission_status_label.text = "Submission timeout"
+ submission_status_label.modulate = Color(1.0, 0.3, 0.3) # Red
+ submission_status_label.visible = true
+ submit_score_button.disabled = false
+ submit_check_timer.stop()
+ submit_check_timer.queue_free()
+ )
+
+ submit_check_timer.start()
+
+func _get_submission_result_from_js() -> Dictionary:
+ if bridge == null:
+ return {}
+
+ var result = JavaScriptBridge.eval("""
+ (function() {
+ if (window.godotSubmissionResult) {
+ var result = window.godotSubmissionResult;
+ window.godotSubmissionResult = null; // Clear after reading
+ return JSON.stringify(result);
+ }
+ return '{}';
+ })();
+ """, true)
+
+ if result != null and result != "":
+ var json = JSON.new()
+ var error = json.parse(str(result))
+ if error == OK:
+ return json.data
+
+ return {}
+
+func _create_encoded_payload(nonce: String, player_name: String, completion_time: int) -> String:
+ # Create the payload as JSON
+ var payload = {
+ "nonce": nonce,
+ "gameId": "whittling-clicker", # Unique identifier for this game
+ "playerName": player_name,
+ "completionTime": completion_time,
+ "timestamp": Time.get_unix_time_from_system() * 1000 # Convert to milliseconds
+ }
+
+ var json_string = JSON.stringify(payload)
+
+ # XOR encode with key derived from nonce
+ var key = "WHITTLING_KEY_" + nonce.substr(0, 8)
+ var encoded_bytes = _xor_encode(json_string, key)
+
+ # Base64 encode (using raw bytes)
+ var base64_encoded = Marshalls.raw_to_base64(encoded_bytes)
+
+ return base64_encoded
+
+func _sanitize_player_name(name: String) -> String:
+ # Strip leading/trailing whitespace
+ name = name.strip_edges()
+
+ # Remove control characters and most special characters, keep letters, numbers, spaces, and safe punctuation
+ var safe_name = ""
+ for i in range(name.length()):
+ var c = name[i]
+ var code = name.unicode_at(i)
+
+ # Allow: letters, numbers, spaces, hyphens, underscores, periods
+ # Block: control chars, path separators, quotes, angle brackets, etc
+ if (code >= 48 and code <= 57) or \
+ (code >= 65 and code <= 90) or \
+ (code >= 97 and code <= 122) or \
+ c == " " or c == "-" or c == "_" or c == ".":
+ safe_name += c
+
+ # Limit length to 50 characters
+ safe_name = safe_name.substr(0, 50)
+
+ # Remove multiple consecutive spaces
+ while safe_name.find(" ") != -1:
+ safe_name = safe_name.replace(" ", " ")
+
+ # Strip again after processing
+ safe_name = safe_name.strip_edges()
+
+ return safe_name
+
+func _xor_encode(text: String, key: String) -> PackedByteArray:
+ var text_bytes = text.to_utf8_buffer()
+ var key_bytes = key.to_utf8_buffer()
+ var key_length = key_bytes.size()
+ var result = PackedByteArray()
+
+ for i in range(text_bytes.size()):
+ var xor_byte = text_bytes[i] ^ key_bytes[i % key_length]
+ result.append(xor_byte)
+
+ return result
diff --git a/scenes/simulator.tscn b/scenes/simulator.tscn
deleted file mode 100644
index d5b2274..0000000
--- a/scenes/simulator.tscn
+++ /dev/null
@@ -1,124 +0,0 @@
-[gd_scene format=3 uid="uid://br6hgvb4buyji"]
-
-[ext_resource type="Script" uid="uid://citjokiv6skqi" path="res://scripts/sim_direct.gd" id="1_sim"]
-
-[node name="Simulator" type="Control" unique_id=1833845714]
-layout_mode = 3
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-script = ExtResource("1_sim")
-
-[node name="MarginContainer" type="MarginContainer" parent="." unique_id=689981813]
-layout_mode = 1
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-theme_override_constants/margin_left = 20
-theme_override_constants/margin_top = 20
-theme_override_constants/margin_right = 20
-theme_override_constants/margin_bottom = 20
-
-[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer" unique_id=1425713346]
-layout_mode = 2
-theme_override_constants/separation = 15
-
-[node name="Title" type="Label" parent="MarginContainer/VBoxContainer" unique_id=1101365395]
-layout_mode = 2
-theme_override_font_sizes/font_size = 32
-text = "Unlock Simulator"
-horizontal_alignment = 1
-
-[node name="StatusPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer" unique_id=21717961]
-layout_mode = 2
-
-[node name="VBox" type="VBoxContainer" parent="MarginContainer/VBoxContainer/StatusPanel" unique_id=846585479]
-layout_mode = 2
-theme_override_constants/separation = 8
-
-[node name="StatusLabel" type="Label" parent="MarginContainer/VBoxContainer/StatusPanel/VBox" unique_id=234737032]
-layout_mode = 2
-theme_override_font_sizes/font_size = 20
-text = "Status: Initializing..."
-
-[node name="ProgressLabel" type="Label" parent="MarginContainer/VBoxContainer/StatusPanel/VBox" unique_id=2064314389]
-layout_mode = 2
-theme_override_font_sizes/font_size = 16
-text = "Progress: 0.0% (0/0)"
-
-[node name="ProgressBar" type="ProgressBar" parent="MarginContainer/VBoxContainer/StatusPanel/VBox" unique_id=1093115744]
-custom_minimum_size = Vector2(0, 30)
-layout_mode = 2
-max_value = 1.0
-step = 0.001
-show_percentage = false
-
-[node name="RateLabel" type="Label" parent="MarginContainer/VBoxContainer/StatusPanel/VBox" unique_id=2006247697]
-layout_mode = 2
-theme_override_font_sizes/font_size = 16
-text = "Speed: 0.0 combos/sec"
-
-[node name="ETALabel" type="Label" parent="MarginContainer/VBoxContainer/StatusPanel/VBox" unique_id=300266005]
-layout_mode = 2
-theme_override_font_sizes/font_size = 16
-text = "ETA: Calculating..."
-
-[node name="CachePanel" type="PanelContainer" parent="MarginContainer/VBoxContainer" unique_id=242908015]
-layout_mode = 2
-
-[node name="VBox" type="VBoxContainer" parent="MarginContainer/VBoxContainer/CachePanel" unique_id=1164261399]
-layout_mode = 2
-theme_override_constants/separation = 8
-
-[node name="CacheTitle" type="Label" parent="MarginContainer/VBoxContainer/CachePanel/VBox" unique_id=615421163]
-layout_mode = 2
-theme_override_font_sizes/font_size = 20
-text = "Cache Statistics"
-
-[node name="CacheHitsLabel" type="Label" parent="MarginContainer/VBoxContainer/CachePanel/VBox" unique_id=1993375767]
-layout_mode = 2
-theme_override_font_sizes/font_size = 16
-text = "Cache Hits: 0"
-
-[node name="CacheMissesLabel" type="Label" parent="MarginContainer/VBoxContainer/CachePanel/VBox" unique_id=159876992]
-layout_mode = 2
-theme_override_font_sizes/font_size = 16
-text = "Cache Misses: 0"
-
-[node name="CacheRateLabel" type="Label" parent="MarginContainer/VBoxContainer/CachePanel/VBox" unique_id=51938306]
-layout_mode = 2
-theme_override_font_sizes/font_size = 16
-text = "Hit Rate: 0.0%"
-
-[node name="CacheSizeLabel" type="Label" parent="MarginContainer/VBoxContainer/CachePanel/VBox" unique_id=2001183562]
-layout_mode = 2
-theme_override_font_sizes/font_size = 16
-text = "Cache Entries: 0"
-
-[node name="ResultsPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer" unique_id=1825974467]
-layout_mode = 2
-size_flags_vertical = 3
-
-[node name="VBox" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ResultsPanel" unique_id=339315001]
-layout_mode = 2
-
-[node name="ResultsTitle" type="Label" parent="MarginContainer/VBoxContainer/ResultsPanel/VBox" unique_id=975903988]
-layout_mode = 2
-theme_override_font_sizes/font_size = 20
-text = "Results"
-
-[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/ResultsPanel/VBox" unique_id=1950199865]
-layout_mode = 2
-size_flags_vertical = 3
-
-[node name="ResultsLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/ResultsPanel/VBox/ScrollContainer" unique_id=107644773]
-layout_mode = 2
-size_flags_horizontal = 3
-size_flags_vertical = 3
-bbcode_enabled = true
-text = "Waiting for results..."
-fit_content = true
diff --git a/scripts/animal_friends_manager.gd b/scripts/animal_friends_manager.gd
new file mode 100644
index 0000000..a342d0a
--- /dev/null
+++ b/scripts/animal_friends_manager.gd
@@ -0,0 +1,101 @@
+extends Node
+
+## Manages the visibility of animal friends in the animal_friends group
+## Animals start invisible and are revealed one at a time randomly as ranks are unlocked
+
+const ANIMAL_FRIENDS_UNLOCK_ID: int = 6 # Forest Friends unlock
+
+var all_animals: Array[Node] = []
+var hidden_animals: Array[Node] = []
+var revealed_animals: Array[Node] = []
+var last_known_rank: int = 0
+
+func _ready() -> void:
+ # Wait a frame to ensure all nodes are ready
+ call_deferred("_initialize")
+
+func _initialize() -> void:
+ # Get all nodes in the animal_friends group
+ all_animals = get_tree().get_nodes_in_group("animal_friends")
+
+ if all_animals.is_empty():
+ Log.pr("Warning: No animal friends found in the animal_friends group")
+ return
+
+ # Shuffle the animals to randomize the order they'll be revealed
+ all_animals.shuffle()
+
+ # Start with all animals hidden
+ for animal in all_animals:
+ animal.visible = false
+ hidden_animals.append(animal)
+
+ Log.pr("Animal Friends Manager initialized with %d animals" % all_animals.size())
+
+ # Get the Forest Friends unlock to track its rank
+ var forest_friends = Unlocks.get_unlock_by_id(ANIMAL_FRIENDS_UNLOCK_ID)
+ if forest_friends:
+ last_known_rank = forest_friends.current_rank
+ # Reveal animals based on current rank (for game loads)
+ _reveal_animals_for_current_rank()
+ else:
+ Log.pr("Warning: Forest Friends unlock (ID %d) not found" % ANIMAL_FRIENDS_UNLOCK_ID)
+
+ # Connect to unlock signal to detect when new ranks are purchased
+ Unlocks.item_unlocked.connect(_on_item_unlocked)
+
+func _reveal_animals_for_current_rank() -> void:
+ # Reveal one animal per rank in the Forest Friends unlock
+ var forest_friends = Unlocks.get_unlock_by_id(ANIMAL_FRIENDS_UNLOCK_ID)
+ if not forest_friends:
+ return
+
+ var ranks_to_reveal = forest_friends.current_rank
+ for i in range(ranks_to_reveal):
+ if hidden_animals.is_empty():
+ break
+ _reveal_next_animal()
+
+func _on_item_unlocked() -> void:
+ # Check if the Forest Friends unlock was ranked up
+ var forest_friends = Unlocks.get_unlock_by_id(ANIMAL_FRIENDS_UNLOCK_ID)
+ if not forest_friends:
+ return
+
+ var current_rank = forest_friends.current_rank
+ var new_ranks = current_rank - last_known_rank
+
+ if new_ranks > 0:
+ Log.pr("Forest Friends ranked up! Revealing %d animal friend(s)" % new_ranks)
+
+ # Reveal one animal for each new rank
+ for i in range(new_ranks):
+ if hidden_animals.is_empty():
+ Log.pr("All animal friends have been revealed!")
+ break
+ _reveal_next_animal()
+
+ last_known_rank = current_rank
+
+func _reveal_next_animal() -> void:
+ if hidden_animals.is_empty():
+ return
+
+ # Take the first animal from the shuffled hidden list (random order)
+ var animal = hidden_animals.pop_front()
+ animal.visible = true
+ revealed_animals.append(animal)
+
+ Log.pr("Revealed animal friend: %s (%d/%d visible)" % [animal.name, revealed_animals.size(), all_animals.size()])
+
+## Debug function to manually reveal the next animal
+func debug_reveal_next() -> void:
+ _reveal_next_animal()
+
+## Debug function to hide all animals
+func debug_reset_all() -> void:
+ for animal in all_animals:
+ animal.visible = false
+ hidden_animals = all_animals.duplicate()
+ revealed_animals.clear()
+ Log.pr("All animals hidden")
diff --git a/scripts/animal_friends_manager.gd.uid b/scripts/animal_friends_manager.gd.uid
new file mode 100644
index 0000000..a67e2c5
--- /dev/null
+++ b/scripts/animal_friends_manager.gd.uid
@@ -0,0 +1 @@
+uid://ewmlbda2adc7
diff --git a/scripts/audio.gd b/scripts/audio.gd
index ea268d5..334fe05 100644
--- a/scripts/audio.gd
+++ b/scripts/audio.gd
@@ -7,16 +7,24 @@ func _ready():
play_background_music()
func play_chop_sound():
+ if not Global.play_chop_sound:
+ return
## Pick one of the chopping sounds randomly
var chop_sounds = [
- "res://assets/audio/ogg/SFX/Chopping and Mining/chop 1.ogg",
- "res://assets/audio/ogg/SFX/Chopping and Mining/chop 2.ogg",
- "res://assets/audio/ogg/SFX/Chopping and Mining/chop 3.ogg",
- "res://assets/audio/ogg/SFX/Chopping and Mining/chop 4.ogg"
+ "res://assets/audio/OGG/SFX/chopping/chop1.ogg",
+ "res://assets/audio/OGG/SFX/chopping/chop2.ogg",
+ "res://assets/audio/OGG/SFX/chopping/chop3.ogg",
+ "res://assets/audio/OGG/SFX/chopping/chop4.ogg"
]
var random_index = randi() % chop_sounds.size()
play_sound_effect(chop_sounds[random_index])
+func play_money_sound():
+ if not Global.play_money_sound:
+ return
+ # Using a simple coin sound - you can replace with your own sound file
+ play_sound_effect("res://assets/audio/coin.mp3")
+
func play_sound_effect(sound_path: String):
var sfx_player = AudioStreamPlayer.new()
@@ -26,10 +34,22 @@ func play_sound_effect(sound_path: String):
sfx_player.play()
sfx_player.connect("finished", sfx_player.queue_free)
+var music_player: AudioStreamPlayer = null
+
func play_background_music():
- var music_player = AudioStreamPlayer.new()
+ if not Global.play_background_music:
+ return
+ if music_player:
+ return # Already playing
+ music_player = AudioStreamPlayer.new()
music_player.stream = load("res://assets/audio/background_music.ogg")
music_player.volume_db = -10 # Set volume to a comfortable level
music_player.autoplay = true
add_child(music_player)
music_player.play()
+
+func stop_background_music():
+ if music_player:
+ music_player.stop()
+ music_player.queue_free()
+ music_player = null
diff --git a/scripts/globals.gd b/scripts/globals.gd
index b58e330..9d08dac 100644
--- a/scripts/globals.gd
+++ b/scripts/globals.gd
@@ -2,6 +2,8 @@ extends Node
# SETTINGS
var play_background_music: bool = true
+var play_chop_sound: bool = true
+var play_money_sound: bool = false
var game_continue_pressed : bool = false
# STRINGS
@@ -13,7 +15,7 @@ var wood_color: Color = Color(0.95, 0.6, 0.35) # Light pumpkin orange (autumn le
var stock_color: Color = Color(0.6, 0.75, 0.95) # Light periwinkle blue (clear autumn sky)
# GAMEPLAY VALUES
-var target_currency: float = 1000000
+var target_currency: float = 100
var base_sale_price: float = 30
var base_wood_respawn: float = 5 # seconds
var wood_per_click: float = 5
@@ -30,3 +32,17 @@ var autowood_unlock_id: int = 7
var premium_crafts_unlock_id: int = 8
var reputation_unlock_id: int = 9
+
+# FORMAT NUMBERS WITH COMMAS FOR DISPLAY
+func format_number(value: float) -> String:
+ var num_str = str(int(value))
+ var result = ""
+ var length = num_str.length()
+
+ for i in range(length):
+ result += num_str[i]
+ var remaining = length - i - 1
+ if remaining > 0 and remaining % 3 == 0:
+ result += ","
+
+ return result
diff --git a/scripts/sim_cached.gd b/scripts/sim_cached.gd
deleted file mode 100644
index a719385..0000000
--- a/scripts/sim_cached.gd
+++ /dev/null
@@ -1,931 +0,0 @@
-class_name UnlockSimulatorCached
-extends Control
-
-# CACHED VERSION - Uses aggressive chunk-based caching for 10-100x speedup
-
-# Load the actual game resources
-var unlock_collection: UnlockDataCollection = load("res://resources/UnlockData.tres")
-var inventory_resource: InventoryResource = load("res://resources/InventoryData.tres")
-
-# Results tracking
-var all_results: Array[Dictionary] = []
-var results_mutex: Mutex = Mutex.new()
-
-# Chunk-based caching for intermediate simulation states
-# Key: "unlock_id:rank,..." sorted string
-# Value: {cache_key, ticks, currency, wood, stock, current_ranks, modifiers}
-var simulation_cache: Dictionary = {}
-var cache_mutex: Mutex = Mutex.new()
-
-# Global unlock struct cache (build once, clone per simulation)
-var global_unlock_structs: Array[Dictionary] = []
-var global_unlock_structs_mutex: Mutex = Mutex.new()
-var unlock_structs_initialized: bool = false
-
-# Manual thread pool
-var num_threads: int = 14 # Increase this for more CPU usage
-var threads: Array[Thread] = []
-var task_queue: Array[Dictionary] = []
-var queue_mutex: Mutex = Mutex.new()
-var completed_count: int = 0
-var completed_mutex: Mutex = Mutex.new()
-var active_threads: int = 0
-var threads_done: bool = false
-
-# Pre-calculated cost arrays for faster lookup - OPTIMIZATION
-var cost_cache: Dictionary = {}
-
-var start_time: int = 0
-var total_combinations: int = 0
-var last_progress_time: int = 0
-var monitoring_active: bool = false
-var cache_hits: int = 0
-var cache_misses: int = 0
-
-# UI References
-@onready var status_label = $MarginContainer/VBoxContainer/StatusPanel/VBox/StatusLabel
-@onready var progress_label = $MarginContainer/VBoxContainer/StatusPanel/VBox/ProgressLabel
-@onready var progress_bar = $MarginContainer/VBoxContainer/StatusPanel/VBox/ProgressBar
-@onready var rate_label = $MarginContainer/VBoxContainer/StatusPanel/VBox/RateLabel
-@onready var eta_label = $MarginContainer/VBoxContainer/StatusPanel/VBox/ETALabel
-@onready var cache_hits_label = $MarginContainer/VBoxContainer/CachePanel/VBox/CacheHitsLabel
-@onready var cache_misses_label = $MarginContainer/VBoxContainer/CachePanel/VBox/CacheMissesLabel
-@onready var cache_rate_label = $MarginContainer/VBoxContainer/CachePanel/VBox/CacheRateLabel
-@onready var cache_size_label = $MarginContainer/VBoxContainer/CachePanel/VBox/CacheSizeLabel
-@onready var results_label = $MarginContainer/VBoxContainer/ResultsPanel/VBox/ScrollContainer/ResultsLabel
-
-func _ready():
- GameManager.tick.stop()
- print("=== CACHED Unlock Simulator Started ===")
- print("Using aggressive chunk-based caching for 10-100x speedup")
- var cpu_count = OS.get_processor_count()
- print("CPU cores detected: %d" % cpu_count)
- print("Creating %d worker threads (adjust num_threads variable for more/less)" % num_threads)
-
- # Update UI
- status_label.text = "Status: Starting cached simulation..."
- results_label.text = "[b]CACHED Unlock Simulator Started[/b]\n\nCPU cores: %d\nWorker threads: %d\n\nGenerating combinations..." % [cpu_count, num_threads]
-
- run_comprehensive_test()
-
-func _process(_delta):
- if monitoring_active:
- # Only update progress once per second
- var current_time = Time.get_ticks_msec()
- if current_time - last_progress_time >= 1000:
- last_progress_time = current_time
- update_progress()
-
-func update_progress():
- """Update progress display"""
- var current_count = 0
- completed_mutex.lock()
- current_count = completed_count
- completed_mutex.unlock()
-
- # Check if all work is complete
- if current_count >= total_combinations:
- monitoring_active = false
- finish_processing()
- return
-
- var percent = float(current_count) / total_combinations * 100.0
- var elapsed = (Time.get_ticks_msec() - start_time) / 1000.0
- var rate = current_count / elapsed if elapsed > 0 else 0
- var eta_seconds = (total_combinations - current_count) / rate if rate > 0 else 0
-
- # Calculate cache hit rate
- var total_cache_checks = cache_hits + cache_misses
- var cache_hit_rate = (float(cache_hits) / total_cache_checks * 100.0) if total_cache_checks > 0 else 0.0
-
- # Format ETA
- var eta_str = ""
- if eta_seconds > 0:
- var eta_minutes = int(eta_seconds) / 60
- var eta_secs = int(eta_seconds) % 60
- if eta_minutes > 0:
- eta_str = "%dm %ds" % [eta_minutes, eta_secs]
- else:
- eta_str = "%ds" % eta_secs
- else:
- eta_str = "calculating..."
-
- print("Progress: %.1f%% (%d/%d) - %.1f combos/sec - Cache: %.1f%% hits - ETA: %s" % [
- percent, current_count, total_combinations, rate, cache_hit_rate, eta_str
- ])
-
- # Update UI
- status_label.text = "Status: Running simulation..."
- progress_label.text = "Progress: %.1f%% (%d/%d)" % [percent, current_count, total_combinations]
- progress_bar.value = percent / 100.0
- rate_label.text = "Speed: %.1f combos/sec" % rate
- eta_label.text = "ETA: %s" % eta_str
-
- # Update cache stats
- cache_hits_label.text = "Cache Hits: %d" % cache_hits
- cache_misses_label.text = "Cache Misses: %d" % cache_misses
- cache_rate_label.text = "Hit Rate: %.1f%%" % cache_hit_rate
- cache_mutex.lock()
- cache_size_label.text = "Cache Entries: %d" % simulation_cache.size()
- cache_mutex.unlock()
-
-func worker_thread(thread_id: int):
- """Worker thread function that pulls tasks from the queue"""
- # Local batch storage to reduce mutex contention - OPTIMIZATION
- var local_results: Array[Dictionary] = []
- var local_count: int = 0
- var batch_size: int = 10 # Process 10 before syncing
-
- while true:
- # Get next task from queue
- var task_data = null
- queue_mutex.lock()
- if task_queue.size() > 0:
- task_data = task_queue.pop_front()
- queue_mutex.unlock()
-
- # If no more tasks, flush results and exit
- if task_data == null:
- if local_results.size() > 0:
- results_mutex.lock()
- all_results.append_array(local_results)
- results_mutex.unlock()
-
- # CRITICAL FIX: Update completed count when flushing final batch
- completed_mutex.lock()
- completed_count += local_results.size()
- completed_mutex.unlock()
- break
-
- # Process the task
- var result = simulate_rank_combination_pure(task_data.combo, task_data.unlock_data, 1000000)
-
- # Store in local batch
- local_results.append(result)
- local_count += 1
-
- # Flush batch periodically to reduce mutex contention
- if local_results.size() >= batch_size:
- results_mutex.lock()
- all_results.append_array(local_results)
- results_mutex.unlock()
-
- # CRITICAL FIX: Update progress counter with mutex protection
- completed_mutex.lock()
- completed_count += local_results.size()
- completed_mutex.unlock()
-
- local_results.clear()
- local_count = 0
-
-func get_cache_key(current_ranks: Dictionary) -> String:
- """Generate a cache key from current unlock ranks"""
- var sorted_keys = current_ranks.keys()
- sorted_keys.sort()
- var key_parts = []
- for k in sorted_keys:
- key_parts.append(str(k) + ":" + str(current_ranks[k]))
- return ",".join(key_parts)
-
-func try_load_best_prefix_from_cache(rank_targets: Dictionary) -> Variant:
- """Balanced cache lookup - fast with good coverage (~10-15 lookups)"""
-
- cache_mutex.lock()
-
- # Try exact match first
- var full_key = get_cache_key(rank_targets)
- if simulation_cache.has(full_key):
- cache_hits += 1
- var result = simulation_cache[full_key]
- cache_mutex.unlock()
- return result
-
- # Sort unlock IDs for consistent ordering
- var unlock_ids = rank_targets.keys()
- unlock_ids.sort()
- var num_unlocks = unlock_ids.size()
-
- var best_match = null
- var best_rank_sum = 0
-
- # STRATEGY: Try progressively shorter prefixes by dropping unlocks from the END
- # This is the most common pattern: {1,2,3,4,5} → {1,2,3,4} → {1,2,3} → {1,2} → {1}
- # Covers 80%+ of cache reuse because combinations are generated in sorted order
-
- for prefix_len in range(num_unlocks - 1, 0, -1):
- var subset = {}
- for i in range(prefix_len):
- subset[unlock_ids[i]] = rank_targets[unlock_ids[i]]
-
- var key = get_cache_key(subset)
- if simulation_cache.has(key):
- var cached_entry = simulation_cache[key]
- var rank_sum = 0
- for r in cached_entry.current_ranks.values():
- rank_sum += r
-
- # Keep best match (longest prefix)
- if rank_sum > best_rank_sum:
- best_match = cached_entry
- best_rank_sum = rank_sum
- # Early exit if we found a substantial match
- if prefix_len >= num_unlocks - 2:
- break
-
- if best_match != null:
- cache_hits += 1
- else:
- cache_misses += 1
- cache_mutex.unlock()
-
- return best_match
-
-func should_cache_state(current_ranks: Dictionary, targets_remaining: int) -> bool:
- """Decide if this state is worth caching"""
- # Don't cache if all targets reached
- if targets_remaining == 0:
- return false
-
- # Cache aggressively for early states
- var total_ranks = 0
- var active_unlocks = 0
-
- for rank in current_ranks.values():
- if rank > 0:
- total_ranks += rank
- active_unlocks += 1
-
- # Cache if: multiple unlocks active OR significant progress on one
- return (active_unlocks >= 2) or (total_ranks >= 2)
-
-func simulate_rank_combination_pure(rank_targets: Dictionary, unlock_data_array: Array, max_ticks: int, track_purchases: bool = false) -> Dictionary:
- """Optimized pure simulation function with struct-based unlocks"""
- var currency: float = 0.0
- var stock: float = 0.0
- var wood: float = 0.0
-
- # Purchase tracking (only enabled for top results)
- var purchases: Array[Dictionary] = []
-
- # GLOBAL STRUCT CACHE - Build once, clone per simulation
- # This avoids rebuilding cost/effect tables for every simulation
- var unlocks: Array[Dictionary] = []
- var unlock_by_id: Dictionary = {}
-
- if not unlock_structs_initialized:
- global_unlock_structs_mutex.lock()
- if not unlock_structs_initialized: # Double-check pattern
- for unlock_data in unlock_data_array:
- var base_mods = unlock_data.base_modifiers
-
- # Pre-calculate cost table for first 20 ranks (avoid pow() in hot loop)
- var cost_table: Array[float] = []
- if unlock_data.is_scaling:
- # Use cost_ladder if defined, otherwise use exponential scaling
- if unlock_data.has("cost_ladder") and unlock_data.cost_ladder.size() > 0:
- # Use fixed cost ladder
- for cost in unlock_data.cost_ladder:
- cost_table.append(float(cost))
- # Fill remaining slots with last cost for safety
- while cost_table.size() < 21:
- cost_table.append(cost_table[cost_table.size() - 1])
- else:
- # Fallback to exponential scaling
- var base_cost_float = float(unlock_data.base_cost)
- var mult = unlock_data.cost_scaling_multiplier
- for r in range(21): # Pre-calc ranks 0-20
- cost_table.append(base_cost_float * pow(mult, r))
- else:
- cost_table.append(float(unlock_data.base_cost))
-
- # Pre-calculate effect scale factors for first 20 ranks
- var effect_scale_table: Array[float] = []
- var effect_mult = unlock_data.effect_scaling_multiplier
- for r in range(21):
- if r == 0:
- effect_scale_table.append(0.0) # No effect at rank 0
- elif r == 1:
- effect_scale_table.append(1.0) # Base effect at rank 1
- else:
- effect_scale_table.append(pow(effect_mult, r - 1))
-
- var unlock_struct = {
- "id": unlock_data.unlock_id,
- "name": unlock_data.unlock_name,
- "base_cost": unlock_data.base_cost,
- "is_scaling": unlock_data.is_scaling,
- "max_rank": unlock_data.max_rank,
- "cost_multiplier": unlock_data.cost_scaling_multiplier,
- "effect_multiplier": unlock_data.effect_scaling_multiplier,
- "base_mods": base_mods,
- "cost_table": cost_table, # Pre-calculated costs
- "effect_scale_table": effect_scale_table, # Pre-calculated effect scales
- # Pre-calculate whether this unlock affects each modifier (avoids string lookups)
- "affects_sale_price": base_mods.has("sale_price_modifier"),
- "affects_efficiency": base_mods.has("efficiency_modifier"),
- "affects_wood_per_click": base_mods.has("wood_per_click_modifier"),
- "affects_purchase_rate": base_mods.has("purchase_rate_modifier"),
- "affects_autowood": base_mods.has("autowood_modifier"),
- "is_multicraft": base_mods.has("multicraft_increase_modifier"),
- # Cache base modifier values to avoid dictionary lookups
- "sale_price_value": base_mods.get("sale_price_modifier", 1.0),
- "efficiency_value": base_mods.get("efficiency_modifier", 1.0),
- "wood_per_click_value": base_mods.get("wood_per_click_modifier", 1.0),
- "purchase_rate_value": base_mods.get("purchase_rate_modifier", 1.0),
- "autowood_value": base_mods.get("autowood_modifier", 0.0)
- }
- global_unlock_structs.append(unlock_struct)
-
- unlock_structs_initialized = true
- global_unlock_structs_mutex.unlock()
-
- # Clone structs for this simulation (fast shallow copy)
- for template in global_unlock_structs:
- var unlock = template.duplicate(false) # Shallow copy
- unlock.current_rank = 0 # Reset rank for this simulation
- unlocks.append(unlock)
- unlock_by_id[unlock.id] = unlock
-
- var ticks: int = 0
- # Removed purchases array - it's only needed for debug output and slows down simulation
-
- # Track how many targets still need to be reached - OPTIMIZATION
- var targets_remaining: int = 0
- var current_ranks: Dictionary = {}
- var active_unlock_ids: Array = [] # Only check unlocks that haven't reached target yet
- for unlock_id in rank_targets.keys():
- current_ranks[unlock_id] = 0
- targets_remaining += rank_targets[unlock_id]
- active_unlock_ids.append(unlock_id)
-
- # Modifiers as individual variables for faster access - MAJOR OPTIMIZATION
- var sale_price_mod: float = 1.0
- var efficiency_mod: float = 1.0
- var wood_per_click_mod: float = 1.0
- var purchase_rate_mod: float = 1.0
- var autowood_mod: float = 0.0
- var multicraft_rank: int = 0
-
- var wholesale_unlocked: bool = false
-
- # Pre-calculate constants
- var wood_per_click_base: float = Global.wood_per_click
- var cost_per_whittle: float = Global.cost_per_whittle
- var base_sale_price: float = Global.base_sale_price
- var base_purchase_rate: float = Global.base_purchase_rate
- var wholesale_id: int = Global.wholesale_unlock_id
- var wholesale_size: float = Global.wholesale_bundle_size
- var wholesale_mult: float = Global.wholesale_discount_multiplier
-
- # CACHE LOOKUP: Try to load from cached intermediate state
- # NOTE: Disable cache when tracking purchases to ensure all purchases are recorded
- var cached_state = null
- if not track_purchases:
- cached_state = try_load_best_prefix_from_cache(rank_targets)
-
- if cached_state != null:
- # Restore full state from cache
- ticks = cached_state.ticks
- currency = cached_state.currency
- stock = cached_state.stock
- wood = cached_state.wood
-
- # Restore modifiers
- sale_price_mod = cached_state.modifiers.sale_price_mod
- efficiency_mod = cached_state.modifiers.efficiency_mod
- wood_per_click_mod = cached_state.modifiers.wood_per_click_mod
- purchase_rate_mod = cached_state.modifiers.purchase_rate_mod
- autowood_mod = cached_state.modifiers.autowood_mod
- multicraft_rank = cached_state.modifiers.multicraft_rank
- wholesale_unlocked = cached_state.modifiers.wholesale_unlocked
-
- # Restore unlock ranks
- for unlock_id in cached_state.current_ranks.keys():
- if unlock_by_id.has(unlock_id):
- unlock_by_id[unlock_id].current_rank = cached_state.current_ranks[unlock_id]
- current_ranks[unlock_id] = cached_state.current_ranks[unlock_id]
-
- # Recalculate targets_remaining and active_unlock_ids
- targets_remaining = 0
- active_unlock_ids.clear()
- for unlock_id in rank_targets.keys():
- if not current_ranks.has(unlock_id):
- current_ranks[unlock_id] = 0
- var remaining = rank_targets[unlock_id] - current_ranks[unlock_id]
- if remaining > 0:
- targets_remaining += remaining
- active_unlock_ids.append(unlock_id)
-
- # PRE-CALCULATE all next costs to avoid repeated lookups in main loop
- var next_costs: Array[float] = []
- next_costs.resize(active_unlock_ids.size())
-
- for i in range(active_unlock_ids.size()):
- var unlock = unlock_by_id[active_unlock_ids[i]]
- var current_rank: int = unlock.current_rank
- if current_rank < unlock.cost_table.size():
- next_costs[i] = unlock.cost_table[current_rank]
- else:
- next_costs[i] = unlock.base_cost * pow(unlock.cost_multiplier, current_rank)
-
- while ticks < max_ticks:
- # Find cheapest affordable unlock using pre-calculated costs
- var cheapest_unlock_id: int = -1
- var cheapest_cost: float = INF
- var cheapest_unlock = null
- var cheapest_index: int = -1
-
- if targets_remaining > 0:
- for i in range(active_unlock_ids.size()):
- if next_costs[i] < cheapest_cost and currency >= next_costs[i]:
- cheapest_cost = next_costs[i]
- cheapest_unlock_id = active_unlock_ids[i]
- cheapest_unlock = unlock_by_id[cheapest_unlock_id]
- cheapest_index = i
-
- # If we can't afford anything and all targets are met, skip to earning 1M
- if cheapest_unlock == null and targets_remaining == 0:
- if currency >= 1000000.0:
- break
- # Skip ahead: calculate ticks needed to reach 1M currency
- # Use current production rate to estimate
- var currency_needed = 1000000.0 - currency
- var price_per_item = base_sale_price * sale_price_mod
- var items_per_tick = max(1.0, floor(base_purchase_rate * purchase_rate_mod))
- var revenue_per_tick = items_per_tick * price_per_item
-
- if revenue_per_tick > 0:
- var ticks_needed = int(ceil(currency_needed / revenue_per_tick))
- ticks += ticks_needed
- currency += revenue_per_tick * ticks_needed
- break
-
- # Purchase the cheapest unlock if found
- if cheapest_unlock != null:
- currency -= cheapest_cost
- cheapest_unlock.current_rank += 1
- current_ranks[cheapest_unlock_id] += 1
- targets_remaining -= 1
-
- # Update wholesale cache
- if cheapest_unlock_id == wholesale_id:
- wholesale_unlocked = true
-
- # OPTIMIZED modifier update - use ratio instead of recalculating from scratch
- var rank: int = cheapest_unlock.current_rank
- var prev_rank: int = rank - 1
-
- if cheapest_unlock.is_multicraft:
- multicraft_rank = rank
-
- # Get scale factors from pre-calculated tables
- var old_scale: float = cheapest_unlock.effect_scale_table[prev_rank] if prev_rank < cheapest_unlock.effect_scale_table.size() else 0.0
- var new_scale: float = cheapest_unlock.effect_scale_table[rank] if rank < cheapest_unlock.effect_scale_table.size() else pow(cheapest_unlock.effect_multiplier, rank - 1)
-
- # Apply incremental changes using ratio
- if cheapest_unlock.affects_sale_price:
- var base_bonus: float = cheapest_unlock.sale_price_value - 1.0
- var old_mult: float = 1.0 + base_bonus * old_scale
- var new_mult: float = 1.0 + base_bonus * new_scale
- sale_price_mod = sale_price_mod * (new_mult / old_mult)
-
- if cheapest_unlock.affects_efficiency:
- var base_bonus: float = cheapest_unlock.efficiency_value - 1.0
- var old_mult: float = 1.0 + base_bonus * old_scale
- var new_mult: float = 1.0 + base_bonus * new_scale
- efficiency_mod = efficiency_mod * (new_mult / old_mult)
-
- if cheapest_unlock.affects_wood_per_click:
- var base_bonus: float = cheapest_unlock.wood_per_click_value - 1.0
- var old_mult: float = 1.0 + base_bonus * old_scale
- var new_mult: float = 1.0 + base_bonus * new_scale
- wood_per_click_mod = wood_per_click_mod * (new_mult / old_mult)
-
- if cheapest_unlock.affects_purchase_rate:
- var base_bonus: float = cheapest_unlock.purchase_rate_value - 1.0
- var old_mult: float = 1.0 + base_bonus * old_scale
- var new_mult: float = 1.0 + base_bonus * new_scale
- purchase_rate_mod = purchase_rate_mod * (new_mult / old_mult)
-
- if cheapest_unlock.affects_autowood:
- autowood_mod = autowood_mod - cheapest_unlock.autowood_value * prev_rank + cheapest_unlock.autowood_value * rank
-
- # Track purchase if enabled
- if track_purchases:
- if purchases.size() == 0:
- print("DEBUG: First purchase being tracked!")
- purchases.append({
- "unlock_id": cheapest_unlock_id,
- "unlock_name": cheapest_unlock.name,
- "rank": rank,
- "cost": cheapest_cost,
- "tick": ticks,
- "currency_after": currency
- })
-
- # Update next cost for this unlock or remove from active list
- if current_ranks[cheapest_unlock_id] >= rank_targets[cheapest_unlock_id]:
- # Target reached - swap with last element and shrink array
- var last_idx = active_unlock_ids.size() - 1
- if cheapest_index != last_idx:
- active_unlock_ids[cheapest_index] = active_unlock_ids[last_idx]
- next_costs[cheapest_index] = next_costs[last_idx]
- active_unlock_ids.resize(last_idx)
- next_costs.resize(last_idx)
- else:
- # Update cost for next rank
- var new_rank = cheapest_unlock.current_rank
- if new_rank < cheapest_unlock.cost_table.size():
- next_costs[cheapest_index] = cheapest_unlock.cost_table[new_rank]
- else:
- next_costs[cheapest_index] = cheapest_unlock.base_cost * pow(cheapest_unlock.cost_multiplier, new_rank)
-
- # Removed purchase tracking for performance - only track in debug mode if needed
- # Don't append to purchases array on every purchase
-
- # CACHE INSERTION: Cache this state if valuable
- if should_cache_state(current_ranks, targets_remaining):
- var cache_key = get_cache_key(current_ranks)
-
- cache_mutex.lock()
- if not simulation_cache.has(cache_key):
- simulation_cache[cache_key] = {
- "cache_key": cache_key,
- "ticks": ticks,
- "currency": currency,
- "stock": stock,
- "wood": wood,
- "current_ranks": current_ranks.duplicate(),
- "modifiers": {
- "sale_price_mod": sale_price_mod,
- "efficiency_mod": efficiency_mod,
- "wood_per_click_mod": wood_per_click_mod,
- "purchase_rate_mod": purchase_rate_mod,
- "autowood_mod": autowood_mod,
- "multicraft_rank": multicraft_rank,
- "wholesale_unlocked": wholesale_unlocked
- }
- }
- cache_mutex.unlock()
-
- # Simulate one tick - HEAVILY OPTIMIZED
-
- # 1. Generate wood
- var wood_per_click_modified = wood_per_click_base * wood_per_click_mod
-
- # Manual clicks based on tick range (pre-calculate to avoid repeated conditions)
- var manual_clicks: float = 1.0 if ticks < 120 else (0.5 if ticks < 300 else (0.25 if (ticks < 600 and autowood_mod < 0.2) else 0.0))
-
- # Total wood generation
- var wood_gen: float = manual_clicks * wood_per_click_modified
- if autowood_mod > 0.0:
- wood_gen += max(wood_per_click_modified * autowood_mod, 1.0)
- wood += wood_gen
-
- # 2. Whittle wood into stock - MATCHES tick_process.gd:19-32
- # Base whittling action (always happens once)
- var multicraft_actions = 1 + multicraft_rank # 1 base + multicraft ranks
-
- # Each whittle action: items_produced_per_tick = cost_per_whittle * efficiency_modifier
- var items_per_whittle = cost_per_whittle * efficiency_mod
-
- for action in range(multicraft_actions):
- if wood >= 1: # Need at least 1 wood to whittle
- # How much wood needed for this whittle (matches tick_process.gd:63-65)
- var wood_needed = ceil(items_per_whittle)
- var wood_to_use = min(wood, wood_needed)
- var items_produced = wood_to_use # 1 wood = 1 item always
-
- wood -= wood_to_use
- stock += items_produced
- else:
- break # Not enough wood for more whittle actions
-
- # 3. Sell stock for currency - MATCHES tick_process.gd:34-58
- var price_per_item = base_sale_price * sale_price_mod
-
- # 3a. Wholesale selling (if unlocked) - matches tick_process.gd:36-42
- # Sell ALL possible 100-item bundles at 1.2x price
- if wholesale_unlocked:
- while stock >= wholesale_size:
- stock -= wholesale_size
- currency += wholesale_size * price_per_item * wholesale_mult
-
- # 3b. Regular selling - matches tick_process.gd:45-58
- if stock > 0:
- var purchase_rate = base_purchase_rate * purchase_rate_mod
- var max_stock_to_sell = floor(purchase_rate)
- # Always sell at least 1, up to the max
- var actual_stock_to_sell = min(stock, max(1.0, max_stock_to_sell))
- stock -= actual_stock_to_sell
- currency += actual_stock_to_sell * price_per_item
-
- ticks += 1
-
- var success = currency >= 1000000.0
-
- var result = {
- "rank_targets": rank_targets,
- "success": success,
- "ticks": ticks if success else -1,
- "final_currency": currency,
- "time_formatted": format_time(ticks) if success else "Failed"
- }
-
- # Include purchase timeline if tracking was enabled
- if track_purchases:
- result["purchases"] = purchases
- print("DEBUG: track_purchases=true, purchases.size()=%d" % purchases.size())
-
- return result
-
-
-func format_time(ticks: int) -> String:
- var seconds = ticks
- var minutes = seconds / 60
- var hours = minutes / 60
-
- if hours > 0:
- return "%dh %dm %ds" % [hours, minutes % 60, seconds % 60]
- elif minutes > 0:
- return "%dm %ds" % [minutes, seconds % 60]
- else:
- return "%ds" % seconds
-
-func generate_all_combinations(unlimited_scaling_cap: int = 5) -> Array[Dictionary]:
- """Generate combinations for ALL unlocks dynamically, respecting max_ranks from resource file
-
- Args:
- unlimited_scaling_cap: Maximum rank to test for unlocks with unlimited scaling (default: 5)
- Lower values = faster testing, higher = more comprehensive
- Cap=3: ~13K combos (~28 sec) | Cap=5: ~47K combos (~93 sec)
- Cap=7: ~111K combos (~3.7min) | Cap=10: ~287K combos (~9.6min)
- """
- var combinations: Array[Dictionary] = []
-
- # Build constraint list from resource file
- var unlock_constraints = []
- for unlock in unlock_collection.unlocks:
- var max_rank: int
- if unlock.max_rank > 0:
- max_rank = unlock.max_rank
- elif not unlock.is_scaling:
- max_rank = 1 # One-shot unlocks
- else:
- max_rank = unlimited_scaling_cap # Configurable cap for unlimited scaling
-
- unlock_constraints.append({
- "id": unlock.unlock_id,
- "name": unlock.unlock_name,
- "max_rank": max_rank
- })
-
- print("\n=== Generating Combinations ===")
- print("Reading from resource file: %d unlocks" % unlock_constraints.size())
- for c in unlock_constraints:
- print(" - %s (ID %d): 0-%d ranks" % [c.name, c.id, c.max_rank])
-
- # Recursive generation
- _generate_combinations_recursive(unlock_constraints, 0, {}, combinations)
-
- print("Generated %d total combinations" % combinations.size())
- return combinations
-
-func _generate_combinations_recursive(constraints: Array, index: int, current: Dictionary, output: Array):
- """Recursively generate all valid combinations"""
- if index >= constraints.size():
- # Skip all-zeros combination
- if current.size() > 0:
- output.append(current.duplicate())
- return
-
- var constraint = constraints[index]
- for rank in range(constraint.max_rank + 1):
- if rank > 0:
- current[constraint.id] = rank
-
- _generate_combinations_recursive(constraints, index + 1, current, output)
-
- if rank > 0:
- current.erase(constraint.id)
-
-func serialize_unlock_data() -> Array:
- """Convert unlock collection to serializable data for threads"""
- var unlock_data = []
- for unlock in unlock_collection.unlocks:
- unlock_data.append({
- "unlock_id": unlock.unlock_id,
- "unlock_name": unlock.unlock_name,
- "base_cost": unlock.base_cost,
- "is_scaling": unlock.is_scaling,
- "max_rank": unlock.max_rank,
- "cost_scaling_multiplier": unlock.cost_scaling_multiplier,
- "effect_scaling_multiplier": unlock.effect_scaling_multiplier,
- "cost_ladder": unlock.cost_ladder.duplicate() if unlock.cost_ladder.size() > 0 else [],
- "base_modifiers": unlock.base_modifiers.duplicate()
- })
- return unlock_data
-
-func run_comprehensive_test():
- """Test all combinations dynamically generated from resource file"""
- print("\n=== Available Unlocks ===")
- for unlock in unlock_collection.unlocks:
- var max_rank_str = str(unlock.max_rank) if unlock.max_rank > 0 else "unlimited"
- print("ID: %d | %s | Base Cost: %d | Scaling: %s | Max Rank: %s" % [
- unlock.unlock_id,
- unlock.unlock_name,
- unlock.base_cost,
- "Yes" if unlock.is_scaling else "No",
- max_rank_str
- ])
- print(" Modifiers: ", unlock.base_modifiers)
-
- print("\n=== Global Constants ===")
- print("Base Sale Price: %s" % Global.base_sale_price)
- print("Base Purchase Rate: %s" % Global.base_purchase_rate)
- print("Cost Per Whittle: %s" % Global.cost_per_whittle)
-
- # Serialize unlock data for threads
- var unlock_data = serialize_unlock_data()
-
- # CACHE WARMUP: Pre-populate cache with common single-unlock states
- print("\n=== Cache Warmup ===")
- print("Pre-populating cache with common prefixes...")
- for unlock in unlock_collection.unlocks:
- var max_warmup_rank = 3
- if unlock.max_rank > 0:
- max_warmup_rank = min(unlock.max_rank, 3)
-
- for rank in range(1, max_warmup_rank + 1):
- var warmup_target = {unlock.unlock_id: rank}
- simulate_rank_combination_pure(warmup_target, unlock_data, 1000000)
-
- print("Cache warmup complete. Cache size: %d entries" % simulation_cache.size())
-
- # Generate all combinations (configurable cap for unlimited scaling)
- var unlimited_cap = 5 # Adjust this to test more/fewer ranks: 3=fast, 5=balanced, 7+=comprehensive
- print("\n=== Generation Settings ===")
- print("Unlimited scaling cap: %d ranks" % unlimited_cap)
- var combinations = generate_all_combinations(unlimited_cap)
- total_combinations = combinations.size()
- print("\n=== Testing %d Combinations ===" % total_combinations)
-
- # Fill task queue
- task_queue.clear()
- for combo in combinations:
- task_queue.append({
- "combo": combo,
- "unlock_data": unlock_data
- })
-
- # Reset counters
- completed_count = 0
- all_results.clear()
- threads_done = false
- start_time = Time.get_ticks_msec()
- last_progress_time = start_time
- monitoring_active = true
-
- # Create and start threads
- print("Starting %d worker threads..." % num_threads)
- for i in range(num_threads):
- var thread = Thread.new()
- thread.start(worker_thread.bind(i))
- threads.append(thread)
-
- print("All threads started, processing...")
-
-func finish_processing():
- """Called when all processing is complete"""
- print("\nAll combinations complete! Waiting for threads to finish...")
-
- # Wait for all threads to finish
- for thread in threads:
- thread.wait_to_finish()
- threads.clear()
- threads_done = true
-
- print("All threads finished. Processing results...")
-
- var total_time = (Time.get_ticks_msec() - start_time) / 1000.0
-
- # SAFETY CHECK: Verify result count matches
- results_mutex.lock()
- var actual_results = all_results.size()
- results_mutex.unlock()
-
- if actual_results != total_combinations:
- print("WARNING: Result count mismatch! Expected %d, got %d" % [total_combinations, actual_results])
- print("This indicates a threading issue where some results weren't flushed")
-
- # Print results
- print("\n=== RESULTS ===")
- print("Total time: %.1f seconds" % total_time)
- print("Total combinations tested: %d (expected %d)" % [actual_results, total_combinations])
-
- # Cache statistics
- var total_cache_checks = cache_hits + cache_misses
- var cache_hit_rate = (float(cache_hits) / total_cache_checks * 100.0) if total_cache_checks > 0 else 0.0
- cache_mutex.lock()
- var cache_size = simulation_cache.size()
- cache_mutex.unlock()
- print("\n=== CACHE STATISTICS ===")
- print("Cache hits: %d" % cache_hits)
- print("Cache misses: %d" % cache_misses)
- print("Hit rate: %.1f%%" % cache_hit_rate)
- print("Cache entries stored: %d" % cache_size)
-
- var successful = all_results.filter(func(r): return r.success)
- print("Successful strategies: %d" % successful.size())
-
- # Update UI status
- status_label.text = "Status: Complete!"
- progress_label.text = "Progress: 100%% (%d/%d)" % [all_results.size(), total_combinations]
- progress_bar.value = 1.0
- eta_label.text = "Total Time: %.1f seconds" % total_time
-
- # Build results text for UI
- var results_text = "[b]SIMULATION COMPLETE[/b]\n\n"
- results_text += "[color=green]Total time: %.1f seconds[/color]\n" % total_time
- results_text += "Combinations tested: %d\n" % all_results.size()
- results_text += "Successful strategies: %d\n\n" % successful.size()
-
- results_text += "[b]Cache Performance:[/b]\n"
- results_text += " Hits: %d\n" % cache_hits
- results_text += " Misses: %d\n" % cache_misses
- results_text += " [color=cyan]Hit Rate: %.1f%%[/color]\n" % cache_hit_rate
- results_text += " Entries: %d\n\n" % cache_size
-
- if successful.size() > 0:
- # Sort by ticks (fastest first)
- successful.sort_custom(func(a, b): return a.ticks < b.ticks)
-
- # Re-simulate top 10 with detailed purchase tracking
- print("\n=== RE-SIMULATING TOP 10 WITH PURCHASE TRACKING ===")
- var unlock_data = serialize_unlock_data()
- var top_10_detailed: Array = []
-
- for i in range(min(10, successful.size())):
- var result = successful[i]
- print("Re-simulating #%d with track_purchases=true..." % (i + 1))
- var detailed_result = simulate_rank_combination_pure(result.rank_targets, unlock_data, 1000000, true)
- print(" Result has purchases key: %s" % detailed_result.has("purchases"))
- if detailed_result.has("purchases"):
- print(" Purchases array size: %d" % detailed_result.purchases.size())
- top_10_detailed.append(detailed_result)
-
- print("\n=== TOP 10 FASTEST STRATEGIES (WITH PURCHASE TIMELINE) ===")
- results_text += "[b]TOP 10 FASTEST STRATEGIES:[/b]\n\n"
-
- for i in range(top_10_detailed.size()):
- var result = top_10_detailed[i]
- print("\n#%d: %s (%d ticks)" % [i + 1, result.time_formatted, result.ticks])
-
- # Format ranks with unlock names
- var rank_display = []
- for unlock_id in result.rank_targets.keys():
- var unlock_name = get_unlock_name_by_id(unlock_id)
- var ranks = result.rank_targets[unlock_id]
- rank_display.append("%s: %d" % [unlock_name, ranks])
- print("Target Ranks: %s" % ", ".join(rank_display))
-
- # Add to UI
- results_text += "[color=yellow]#%d: %s (%d ticks)[/color]\n" % [i + 1, result.time_formatted, result.ticks]
- results_text += " Ranks: %s\n" % ", ".join(rank_display)
- results_text += " Currency: %.0f\n" % result.final_currency
-
- # Add purchase timeline
- if result.has("purchases") and result.purchases.size() > 0:
- print("\nPurchase Timeline:")
- results_text += " [b]Purchase Timeline:[/b]\n"
- for purchase in result.purchases:
- var time_str = format_time(purchase.tick)
- print(" %s: %s Rank %d - Cost: %d¥ @ %s" % [
- time_str, purchase.unlock_name, purchase.rank,
- purchase.cost, time_str
- ])
- results_text += " • %s [color=cyan]%s Rank %d[/color] - %d¥ @ %s\n" % [
- format_time(purchase.tick), purchase.unlock_name, purchase.rank,
- purchase.cost, time_str
- ]
- results_text += "\n"
- else:
- print("\nNo successful strategies found!")
- results_text += "[color=red]No successful strategies found![/color]\n"
-
- # Update results UI
- results_label.text = results_text
-
-func get_unlock_name_by_id(unlock_id: int) -> String:
- """Helper function to get unlock name by ID"""
- for unlock in unlock_collection.unlocks:
- if unlock.unlock_id == unlock_id:
- return unlock.unlock_name
- return "Unknown"
-
-func _exit_tree():
- # Clean up threads
- monitoring_active = false
- for thread in threads:
- if thread.is_alive():
- thread.wait_to_finish()
diff --git a/scripts/sim_cached.gd.uid b/scripts/sim_cached.gd.uid
deleted file mode 100644
index 642ef22..0000000
--- a/scripts/sim_cached.gd.uid
+++ /dev/null
@@ -1 +0,0 @@
-uid://bup76ad02kuse
diff --git a/scripts/sim_direct.gd b/scripts/sim_direct.gd
deleted file mode 100644
index aa28717..0000000
--- a/scripts/sim_direct.gd
+++ /dev/null
@@ -1,1036 +0,0 @@
-class_name SimulatorDirect
-extends Control
-
-# DIRECT SIMULATOR - Uses real TickProcess with isolated state
-# Zero code duplication via dependency injection
-
-# Load the actual game resources
-var unlock_collection: UnlockDataCollection = load("res://resources/UnlockData.tres")
-
-# Results tracking
-var all_results: Array[Dictionary] = []
-var results_mutex: Mutex = Mutex.new()
-
-# Chunk-based caching for intermediate simulation states
-var simulation_cache: Dictionary = {}
-var cache_mutex: Mutex = Mutex.new()
-
-# Global unlock template cache (build once, clone per simulation)
-# Pre-calculated lookup tables (built once from UnlockDataResource, then used statically)
-var precalc_costs: Dictionary = {} # {unlock_id: [cost_at_rank_0, cost_at_rank_1, ...]}
-var precalc_modifiers: Dictionary = {} # {unlock_id: {rank: {"modifier_name": value}}}
-var precalc_base_modifiers: Dictionary = {} # {unlock_id: base_modifiers_dict}
-var precalc_max_ranks: Dictionary = {} # {unlock_id: max_rank}
-var precalc_is_scaling: Dictionary = {} # {unlock_id: is_scaling}
-var precalc_unlock_names: Dictionary = {} # {unlock_id: name}
-var precalc_initialized: bool = false
-var precalc_mutex: Mutex = Mutex.new()
-
-# Manual thread pool
-var num_threads: int = 14
-var threads: Array[Thread] = []
-var task_queue: Array[Dictionary] = []
-var queue_mutex: Mutex = Mutex.new()
-var completed_count: int = 0
-var completed_mutex: Mutex = Mutex.new()
-var threads_done: bool = false
-
-var start_time: int = 0
-var total_combinations: int = 0
-var last_progress_time: int = 0
-var monitoring_active: bool = false
-var cache_hits: int = 0
-var cache_misses: int = 0
-
-# UI References
-@onready var status_label = $MarginContainer/VBoxContainer/StatusPanel/VBox/StatusLabel
-@onready var progress_label = $MarginContainer/VBoxContainer/StatusPanel/VBox/ProgressLabel
-@onready var progress_bar = $MarginContainer/VBoxContainer/StatusPanel/VBox/ProgressBar
-@onready var rate_label = $MarginContainer/VBoxContainer/StatusPanel/VBox/RateLabel
-@onready var eta_label = $MarginContainer/VBoxContainer/StatusPanel/VBox/ETALabel
-@onready var cache_hits_label = $MarginContainer/VBoxContainer/CachePanel/VBox/CacheHitsLabel
-@onready var cache_misses_label = $MarginContainer/VBoxContainer/CachePanel/VBox/CacheMissesLabel
-@onready var cache_rate_label = $MarginContainer/VBoxContainer/CachePanel/VBox/CacheRateLabel
-@onready var cache_size_label = $MarginContainer/VBoxContainer/CachePanel/VBox/CacheSizeLabel
-@onready var results_label = $MarginContainer/VBoxContainer/ResultsPanel/VBox/ScrollContainer/ResultsLabel
-
-func _ready():
- GameManager.tick.stop()
- print("=== DIRECT Unlock Simulator Started ===")
- print("Using real TickProcess with isolated state via dependency injection")
- var cpu_count = OS.get_processor_count()
- print("CPU cores detected: %d" % cpu_count)
- print("Creating %d worker threads" % num_threads)
-
- # Update UI
- status_label.text = "Status: Starting direct simulation..."
- results_label.text = "[b]DIRECT Unlock Simulator Started[/b]\n\nCPU cores: %d\nWorker threads: %d\n\nGenerating combinations..." % [cpu_count, num_threads]
-
- run_comprehensive_test()
-
-func _process(_delta):
- if monitoring_active:
- var current_time = Time.get_ticks_msec()
- if current_time - last_progress_time >= 1000:
- last_progress_time = current_time
- update_progress()
-
-# =============================================================================
-# ISOLATED GAME STATE CLASS
-# =============================================================================
-
-class IsolatedGameState:
- """Isolated game state using pre-calculated lookup tables (NO object creation!)"""
-
- # Reference to pre-calculated tables (shared, read-only)
- var costs: Dictionary # {unlock_id: [cost_array]}
- var modifiers: Dictionary # {unlock_id: {rank: mods_dict}}
- var max_ranks: Dictionary # {unlock_id: max_rank}
- var is_scaling: Dictionary # {unlock_id: bool}
- var base_mods: Dictionary # {unlock_id: base_modifiers}
-
- # Local inventory state (not Inventory singleton)
- var currency: float = 0.0
- var wood: float = 0.0
- var stock: float = 0.0
-
- # Cached modifiers (not Unlocks.current_modifiers)
- var sale_price_mod: float = 1.0
- var efficiency_mod: float = 1.0
- var wood_per_click_mod: float = 1.0
- var purchase_rate_mod: float = 1.0
- var autowood_mod: float = 0.0
- var multicraft_rank: int = 0
- var wholesale_unlocked: bool = false
-
- # Simulation state
- var ticks: int = 0
- var current_ranks: Dictionary = {}
-
- # Cache FakeUnlock objects to avoid repeated creation (OPTIMIZATION)
- var fake_unlock_cache: Dictionary = {} # {unlock_id: FakeUnlock}
-
- func _init(precalc_costs: Dictionary, precalc_modifiers: Dictionary, precalc_max_ranks: Dictionary, precalc_is_scaling: Dictionary, precalc_base_mods: Dictionary):
- """Initialize with references to pre-calculated lookup tables"""
- costs = precalc_costs
- modifiers = precalc_modifiers
- max_ranks = precalc_max_ranks
- is_scaling = precalc_is_scaling
- base_mods = precalc_base_mods
-
- # =============================================================================
- # UNLOCKS SINGLETON INTERFACE REPLACEMENT
- # =============================================================================
-
- func get_unlock_by_id(unlock_id: int):
- """Replacement for Unlocks.get_unlock_by_id() - returns cached fake unlock"""
- if not current_ranks.has(unlock_id):
- return null
-
- # Reuse cached FakeUnlock object (just update its state)
- var fake = fake_unlock_cache.get(unlock_id)
- if fake == null:
- fake = FakeUnlock.new()
- fake.unlock_id = unlock_id
- fake.state = self
- fake_unlock_cache[unlock_id] = fake
-
- # Update state from current_ranks
- fake.current_rank = current_ranks.get(unlock_id, 0)
- fake.is_unlocked = fake.current_rank > 0
- return fake
-
- # Nested class for fake unlock objects (uses parent's lookup tables)
- class FakeUnlock:
- var unlock_id: int
- var current_rank: int
- var is_unlocked: bool
- var state: IsolatedGameState
-
- func get_next_cost() -> int:
- var cost_array = state.costs.get(unlock_id, [])
- if current_rank < cost_array.size():
- return cost_array[current_rank]
- return 999999999
-
- func get_current_modifiers() -> Dictionary:
- if not is_unlocked or current_rank == 0:
- return {}
- var rank_mods = state.modifiers.get(unlock_id, {})
- return rank_mods.get(current_rank, {})
-
- func get_modifiers_at_rank(rank: int) -> Dictionary:
- """Get modifiers at a specific rank (for calculating deltas)"""
- if rank == 0:
- return {}
- var rank_mods = state.modifiers.get(unlock_id, {})
- return rank_mods.get(rank, {})
-
- func can_rank_up() -> bool:
- var max_rank = state.max_ranks.get(unlock_id, -1)
- if max_rank > 0 and current_rank >= max_rank:
- return false
- return true
-
- func unlock() -> bool:
- if not can_rank_up():
- return false
- current_rank += 1
- is_unlocked = true
- state.current_ranks[unlock_id] = current_rank
- return true
-
- func get_modifier_value(modifier_key: String) -> float:
- """Replacement for Unlocks.get_modifier_value()"""
- match modifier_key:
- "sale_price_modifier": return sale_price_mod
- "efficiency_modifier": return efficiency_mod
- "wood_per_click_modifier": return wood_per_click_mod
- "purchase_rate_modifier": return purchase_rate_mod
- "autowood_modifier": return autowood_mod
- _: return 1.0
-
- func get_wood_per_click() -> float:
- """Replacement for Unlocks.get_wood_per_click()"""
- return Global.wood_per_click * wood_per_click_mod
-
- func get_items_produced_per_tick() -> float:
- """Replacement for Unlocks.get_items_produced_per_tick()"""
- return Global.cost_per_whittle * efficiency_mod
-
- func get_sale_price_per_item() -> float:
- """Replacement for Unlocks.get_sale_price_per_item()"""
- return Global.base_sale_price * sale_price_mod
-
- # =============================================================================
- # INVENTORY SINGLETON INTERFACE REPLACEMENT
- # =============================================================================
-
- func add_wood(amount: float):
- """Replacement for Inventory.add_wood()"""
- wood += amount
-
- func get_wood() -> float:
- """Replacement for Inventory.get_wood()"""
- return wood
-
- func spend_wood(amount: float) -> bool:
- """Replacement for Inventory.spend_wood()"""
- if wood >= amount:
- wood -= amount
- return true
- return false
-
- func add_stock(amount: float):
- """Replacement for Inventory.add_stock()"""
- stock += amount
-
- func get_stock() -> float:
- """Replacement for Inventory.get_stock()"""
- return stock
-
- func spend_stock(amount: float) -> bool:
- """Replacement for Inventory.spend_stock()"""
- if stock >= amount:
- stock -= amount
- return true
- return false
-
- func add_currency(amount: float):
- """Replacement for Inventory.add_currency()"""
- currency += amount
-
- func spend_currency(amount: float) -> bool:
- """Replacement for Inventory.spend_currency()"""
- if currency >= amount:
- currency -= amount
- return true
- return false
-
- # =============================================================================
- # UNLOCK PURCHASE AND MODIFIER MANAGEMENT
- # =============================================================================
-
- func purchase_unlock(unlock_id: int) -> bool:
- """Purchase an unlock and update modifiers"""
- var unlock = get_unlock_by_id(unlock_id)
- if not unlock or not unlock.can_rank_up():
- return false
-
- var cost = unlock.get_next_cost()
- if not spend_currency(cost):
- return false
-
- var prev_rank = unlock.current_rank
- unlock.unlock()
- current_ranks[unlock_id] = unlock.current_rank
-
- # Update modifier cache
- update_modifiers_for_unlock(unlock, prev_rank)
-
- # Update special flags
- if unlock_id == Global.wholesale_unlock_id:
- wholesale_unlocked = true
- if unlock_id == Global.multicraft_unlock_id:
- multicraft_rank = unlock.current_rank
- if unlock_id == Global.autowood_unlock_id:
- # Autowood is additive, not multiplicative
- autowood_mod = calculate_autowood_modifier()
-
- return true
-
- func update_modifiers_for_unlock(unlock, prev_rank: int):
- """Update cached modifiers when unlock rank changes"""
- var old_mods = unlock.get_modifiers_at_rank(prev_rank)
- var new_mods = unlock.get_current_modifiers()
-
- # Calculate ratio for multiplicative modifiers
- for key in new_mods.keys():
- var old_val = old_mods.get(key, 1.0)
- var new_val = new_mods[key]
- var ratio = new_val / old_val if old_val != 0 else new_val
-
- match key:
- "sale_price_modifier":
- sale_price_mod *= ratio
- "efficiency_modifier":
- efficiency_mod *= ratio
- "wood_per_click_modifier":
- wood_per_click_mod *= ratio
- "purchase_rate_modifier":
- purchase_rate_mod *= ratio
-
- func calculate_autowood_modifier() -> float:
- """Calculate autowood modifier from scratch (additive)"""
- var total = 0.0
- for unlock_id in current_ranks.keys():
- var rank = current_ranks[unlock_id]
- if rank > 0:
- var rank_mods = modifiers.get(unlock_id, {})
- var mods = rank_mods.get(rank, {})
- if mods.has("autowood_modifier"):
- total += mods["autowood_modifier"]
- return total
-
- # =============================================================================
- # INLINED TICK LOGIC (optimized for performance, matches TickProcess exactly)
- # =============================================================================
-
- func execute_tick():
- """Execute one game tick - INLINED for performance (no TickProcess overhead)"""
- # 1. Generate wood from autowood
- if autowood_mod > 0.0:
- var wood_to_gather = max(get_wood_per_click() * autowood_mod, 1.0)
- wood += wood_to_gather
-
- # 2. Whittle wood into stock
- if wood >= 1:
- # Base whittling action
- var items_produced_per_tick = get_items_produced_per_tick()
- var wood_needed = ceil(items_produced_per_tick)
- var wood_to_whittle = min(wood, wood_needed)
- var items_produced = wood_to_whittle
- wood -= wood_to_whittle
- stock += items_produced
-
- # Multicraft additional whittles
- for i in range(multicraft_rank):
- if wood >= 1:
- wood_needed = ceil(items_produced_per_tick)
- wood_to_whittle = min(wood, wood_needed)
- items_produced = wood_to_whittle
- wood -= wood_to_whittle
- stock += items_produced
- else:
- break
-
- # 3. Sell stock for currency
- if stock > 0:
- var price_per_item = get_sale_price_per_item()
-
- # 3a. Wholesale selling (if unlocked)
- if wholesale_unlocked:
- while stock >= Global.wholesale_bundle_size:
- stock -= Global.wholesale_bundle_size
- currency += Global.wholesale_bundle_size * price_per_item * Global.wholesale_discount_multiplier
-
- # 3b. Regular selling
- if stock > 0:
- var purchase_rate = Global.base_purchase_rate * purchase_rate_mod
- var max_stock_to_sell = floor(purchase_rate)
- var actual_stock_to_sell = min(stock, max(1.0, max_stock_to_sell))
- stock -= actual_stock_to_sell
- currency += actual_stock_to_sell * price_per_item
-
- ticks += 1
-
- # =============================================================================
- # RESET FOR REUSE
- # =============================================================================
-
- func reset_for_new_simulation():
- """Reset state for a new simulation (OPTIMIZATION: reuse same IsolatedGameState)"""
- # Reset inventory
- currency = 0.0
- wood = 0.0
- stock = 0.0
-
- # Reset modifiers
- sale_price_mod = 1.0
- efficiency_mod = 1.0
- wood_per_click_mod = 1.0
- purchase_rate_mod = 1.0
- autowood_mod = 0.0
- multicraft_rank = 0
- wholesale_unlocked = false
-
- # Reset simulation state
- ticks = 0
- current_ranks.clear()
-
- # =============================================================================
- # SNAPSHOT AND RESTORE FOR CACHING
- # =============================================================================
-
- func snapshot_for_cache() -> Dictionary:
- """Create a snapshot for cache storage"""
- return {
- "ticks": ticks,
- "currency": currency,
- "stock": stock,
- "wood": wood,
- "current_ranks": current_ranks.duplicate(),
- "modifiers": {
- "sale_price_mod": sale_price_mod,
- "efficiency_mod": efficiency_mod,
- "wood_per_click_mod": wood_per_click_mod,
- "purchase_rate_mod": purchase_rate_mod,
- "autowood_mod": autowood_mod,
- "multicraft_rank": multicraft_rank,
- "wholesale_unlocked": wholesale_unlocked
- }
- }
-
- func restore_from_cache(snapshot: Dictionary):
- """Restore state from a cache snapshot"""
- ticks = snapshot.ticks
- currency = snapshot.currency
- stock = snapshot.stock
- wood = snapshot.wood
-
- # Restore modifiers
- var mods = snapshot.modifiers
- sale_price_mod = mods.sale_price_mod
- efficiency_mod = mods.efficiency_mod
- wood_per_click_mod = mods.wood_per_click_mod
- purchase_rate_mod = mods.purchase_rate_mod
- autowood_mod = mods.autowood_mod
- multicraft_rank = mods.multicraft_rank
- wholesale_unlocked = mods.wholesale_unlocked
-
- # Restore unlock ranks
- current_ranks = snapshot.current_ranks.duplicate()
- for unlock_id in current_ranks.keys():
- var unlock = get_unlock_by_id(unlock_id)
- if unlock:
- var target_rank = current_ranks[unlock_id]
- unlock.current_rank = target_rank
- unlock.is_unlocked = target_rank > 0
-
-# =============================================================================
-# TEMPLATE MANAGEMENT
-# =============================================================================
-
-func build_precalculated_tables():
- """Pre-calculate all costs and modifiers from real UnlockDataResource objects"""
- precalc_mutex.lock()
- if precalc_initialized:
- precalc_mutex.unlock()
- return
-
- print("Pre-calculating unlock tables from UnlockDataResource...")
- var start_time = Time.get_ticks_msec()
-
- for unlock in unlock_collection.unlocks:
- var unlock_id = unlock.unlock_id
- precalc_unlock_names[unlock_id] = unlock.unlock_name
- precalc_is_scaling[unlock_id] = unlock.is_scaling
- precalc_max_ranks[unlock_id] = unlock.max_rank if unlock.is_scaling else 1
- precalc_base_modifiers[unlock_id] = unlock.base_modifiers.duplicate(true)
-
- # Calculate cost ladder for all possible ranks
- var cost_array = []
- var max_rank_to_calc = unlock.max_rank if (unlock.is_scaling and unlock.max_rank > 0) else (100 if unlock.is_scaling else 1)
- for rank in range(max_rank_to_calc + 1):
- unlock.current_rank = rank
- cost_array.append(unlock.get_next_cost())
- precalc_costs[unlock_id] = cost_array
-
- # Calculate modifier values for all possible ranks
- var mods_by_rank = {}
- for rank in range(max_rank_to_calc + 1):
- unlock.current_rank = rank
- unlock.is_unlocked = rank > 0
- mods_by_rank[rank] = unlock.get_current_modifiers()
- precalc_modifiers[unlock_id] = mods_by_rank
-
- # Reset unlock state
- unlock.current_rank = 0
- unlock.is_unlocked = false
-
- precalc_initialized = true
- var elapsed = Time.get_ticks_msec() - start_time
- print("Pre-calculation complete in %d ms for %d unlocks" % [elapsed, precalc_costs.size()])
- precalc_mutex.unlock()
-
-func create_isolated_state() -> IsolatedGameState:
- """Create a new isolated game state using pre-calculated tables"""
- if not precalc_initialized:
- build_precalculated_tables()
- return IsolatedGameState.new(precalc_costs, precalc_modifiers, precalc_max_ranks, precalc_is_scaling, precalc_base_modifiers)
-
-# =============================================================================
-# CACHE SYSTEM (same as sim_cached.gd)
-# =============================================================================
-
-func get_cache_key(current_ranks: Dictionary) -> String:
- """Generate a cache key from current unlock ranks"""
- var sorted_keys = current_ranks.keys()
- sorted_keys.sort()
- var key_parts = []
- for k in sorted_keys:
- key_parts.append(str(k) + ":" + str(current_ranks[k]))
- return ",".join(key_parts)
-
-func try_load_best_prefix_from_cache(rank_targets: Dictionary) -> Variant:
- """Balanced cache lookup - fast with good coverage"""
- cache_mutex.lock()
-
- # Try exact match first
- var full_key = get_cache_key(rank_targets)
- if simulation_cache.has(full_key):
- cache_hits += 1
- var result = simulation_cache[full_key]
- cache_mutex.unlock()
- return result
-
- # Sort unlock IDs for consistent ordering
- var unlock_ids = rank_targets.keys()
- unlock_ids.sort()
- var num_unlocks = unlock_ids.size()
-
- var best_match = null
- var best_rank_sum = 0
-
- # Try progressively shorter prefixes
- for prefix_len in range(num_unlocks - 1, 0, -1):
- var subset = {}
- for i in range(prefix_len):
- subset[unlock_ids[i]] = rank_targets[unlock_ids[i]]
-
- var key = get_cache_key(subset)
- if simulation_cache.has(key):
- var cached_entry = simulation_cache[key]
- var rank_sum = 0
- for r in cached_entry.current_ranks.values():
- rank_sum += r
-
- if rank_sum > best_rank_sum:
- best_match = cached_entry
- best_rank_sum = rank_sum
- if prefix_len >= num_unlocks - 2:
- break
-
- if best_match != null:
- cache_hits += 1
- else:
- cache_misses += 1
- cache_mutex.unlock()
-
- return best_match
-
-func should_cache_state(current_ranks: Dictionary, targets_remaining: int) -> bool:
- """Decide if this state is worth caching"""
- if targets_remaining == 0:
- return false
-
- var total_ranks = 0
- var active_unlocks = 0
-
- for rank in current_ranks.values():
- if rank > 0:
- total_ranks += rank
- active_unlocks += 1
-
- return (active_unlocks >= 2) or (total_ranks >= 2)
-
-# =============================================================================
-# MAIN SIMULATION FUNCTION
-# =============================================================================
-
-func simulate_rank_combination_direct(
- rank_targets: Dictionary,
- max_ticks: int,
- track_purchases: bool = false,
- _unused_tick_process = null, # Kept for API compatibility but not used
- _unused_state = null # Kept for API compatibility but not used
-) -> Dictionary:
- """Pure simulation using isolated state with inlined tick logic"""
-
- # Always create fresh isolated state to avoid thread conflicts
- var state = create_isolated_state()
-
- # Initialize targets
- var targets_remaining = 0
- var active_unlock_ids: Array = []
- for unlock_id in rank_targets.keys():
- state.current_ranks[unlock_id] = 0
- targets_remaining += rank_targets[unlock_id]
- active_unlock_ids.append(unlock_id)
-
- # Purchase tracking
- var purchases: Array[Dictionary] = []
-
- # Try cache restoration
- var cached_state = null
- if not track_purchases:
- cached_state = try_load_best_prefix_from_cache(rank_targets)
-
- if cached_state != null:
- state.restore_from_cache(cached_state)
-
- # Recalculate remaining targets
- targets_remaining = 0
- active_unlock_ids.clear()
- for unlock_id in rank_targets.keys():
- var remaining = rank_targets[unlock_id] - state.current_ranks.get(unlock_id, 0)
- if remaining > 0:
- targets_remaining += remaining
- active_unlock_ids.append(unlock_id)
-
- # Pre-calculate next costs directly from lookup table
- var next_costs: Array[float] = []
- next_costs.resize(active_unlock_ids.size())
- for i in range(active_unlock_ids.size()):
- var unlock_id = active_unlock_ids[i]
- var current_rank = state.current_ranks.get(unlock_id, 0)
- var cost_array = state.costs.get(unlock_id, [])
- next_costs[i] = cost_array[current_rank] if current_rank < cost_array.size() else 999999999
-
- # Main simulation loop
- while state.ticks < max_ticks:
- # Find cheapest affordable unlock
- var cheapest_index = -1
- var cheapest_cost = INF
- var cheapest_unlock_id = -1
-
- if targets_remaining > 0:
- for i in range(active_unlock_ids.size()):
- if next_costs[i] < cheapest_cost and state.currency >= next_costs[i]:
- cheapest_cost = next_costs[i]
- cheapest_unlock_id = active_unlock_ids[i]
- cheapest_index = i
-
- # Exit early if all targets met and goal reached
- if cheapest_index == -1 and targets_remaining == 0:
- if state.currency >= 1000000.0:
- break
- # Skip ahead to 1M
- var currency_needed = 1000000.0 - state.currency
- var price_per_item = state.get_sale_price_per_item()
- var items_per_tick = max(1.0, floor(
- Global.base_purchase_rate * state.get_modifier_value("purchase_rate_modifier")
- ))
- var revenue_per_tick = items_per_tick * price_per_item
-
- if revenue_per_tick > 0:
- var ticks_needed = int(ceil(currency_needed / revenue_per_tick))
- state.ticks += ticks_needed
- state.currency += revenue_per_tick * ticks_needed
- break
-
- # Purchase unlock if affordable
- if cheapest_index != -1:
- state.purchase_unlock(cheapest_unlock_id)
- targets_remaining -= 1
-
- # Track purchase if enabled
- if track_purchases:
- var current_rank = state.current_ranks[cheapest_unlock_id]
- purchases.append({
- "unlock_id": cheapest_unlock_id,
- "unlock_name": get_unlock_name_by_id(cheapest_unlock_id),
- "rank": current_rank,
- "cost": cheapest_cost,
- "tick": state.ticks,
- "currency_after": state.currency
- })
-
- # Update next cost or remove from active list
- if state.current_ranks[cheapest_unlock_id] >= rank_targets[cheapest_unlock_id]:
- # Target reached
- var last_idx = active_unlock_ids.size() - 1
- if cheapest_index != last_idx:
- active_unlock_ids[cheapest_index] = active_unlock_ids[last_idx]
- next_costs[cheapest_index] = next_costs[last_idx]
- active_unlock_ids.resize(last_idx)
- next_costs.resize(last_idx)
- else:
- # Update cost for next rank directly from lookup table
- var current_rank = state.current_ranks[cheapest_unlock_id]
- var cost_array = state.costs.get(cheapest_unlock_id, [])
- next_costs[cheapest_index] = cost_array[current_rank] if current_rank < cost_array.size() else 999999999
-
- # Cache this state if valuable
- if should_cache_state(state.current_ranks, targets_remaining):
- var cache_key = get_cache_key(state.current_ranks)
- cache_mutex.lock()
- if not simulation_cache.has(cache_key):
- simulation_cache[cache_key] = state.snapshot_for_cache()
- cache_mutex.unlock()
-
- # Simulate manual clicking to bootstrap economy (matches sim_cached.gd logic)
- # Manual clicks based on tick range (pre-calculate to avoid repeated conditions)
- var manual_clicks: float = 1.0 if state.ticks < 120 else (0.5 if state.ticks < 300 else (0.25 if (state.ticks < 600 and state.autowood_mod < 0.2) else 0.0))
- if manual_clicks > 0.0:
- var wood_from_clicks = manual_clicks * state.get_wood_per_click()
- state.add_wood(wood_from_clicks)
-
- # Execute one tick using inlined logic (optimized for performance)
- state.execute_tick()
-
- # Build result
- var success = state.currency >= 1000000.0
- var result = {
- "rank_targets": rank_targets,
- "success": success,
- "ticks": state.ticks if success else -1,
- "final_currency": state.currency,
- "time_formatted": format_time(state.ticks) if success else "Failed"
- }
-
- if track_purchases:
- result["purchases"] = purchases
-
- return result
-
-# =============================================================================
-# HELPER FUNCTIONS
-# =============================================================================
-
-func format_time(ticks: int) -> String:
- var seconds = ticks
- var minutes = seconds / 60
- var hours = minutes / 60
-
- if hours > 0:
- return "%dh %dm %ds" % [hours, minutes % 60, seconds % 60]
- elif minutes > 0:
- return "%dm %ds" % [minutes, seconds % 60]
- else:
- return "%ds" % seconds
-
-func generate_all_combinations(unlimited_scaling_cap: int = 5) -> Array[Dictionary]:
- """Generate combinations for ALL unlocks dynamically"""
- var combinations: Array[Dictionary] = []
-
- var unlock_constraints = []
- for unlock in unlock_collection.unlocks:
- var max_rank: int
- if unlock.max_rank > 0:
- max_rank = unlock.max_rank
- elif not unlock.is_scaling:
- max_rank = 1
- else:
- max_rank = unlimited_scaling_cap
-
- unlock_constraints.append({
- "id": unlock.unlock_id,
- "name": unlock.unlock_name,
- "max_rank": max_rank
- })
-
- print("\n=== Generating Combinations ===")
- print("Reading from resource file: %d unlocks" % unlock_constraints.size())
- for c in unlock_constraints:
- print(" - %s (ID %d): 0-%d ranks" % [c.name, c.id, c.max_rank])
-
- _generate_combinations_recursive(unlock_constraints, 0, {}, combinations)
-
- print("Generated %d total combinations" % combinations.size())
- return combinations
-
-func _generate_combinations_recursive(constraints: Array, index: int, current: Dictionary, output: Array):
- """Recursively generate all valid combinations"""
- if index >= constraints.size():
- if current.size() > 0:
- output.append(current.duplicate())
- return
-
- var constraint = constraints[index]
- for rank in range(constraint.max_rank + 1):
- if rank > 0:
- current[constraint.id] = rank
-
- _generate_combinations_recursive(constraints, index + 1, current, output)
-
- if rank > 0:
- current.erase(constraint.id)
-
-func get_unlock_name_by_id(unlock_id: int) -> String:
- """Helper function to get unlock name by ID"""
- if not precalc_initialized:
- build_precalculated_tables()
- return precalc_unlock_names.get(unlock_id, "Unknown")
-
-# =============================================================================
-# THREADING AND PROGRESS
-# =============================================================================
-
-func worker_thread(thread_id: int):
- """Worker thread function"""
- var local_results: Array[Dictionary] = []
- var batch_size: int = 10
-
- while true:
- var task_data = null
- queue_mutex.lock()
- if task_queue.size() > 0:
- task_data = task_queue.pop_front()
- queue_mutex.unlock()
-
- if task_data == null:
- if local_results.size() > 0:
- results_mutex.lock()
- all_results.append_array(local_results)
- results_mutex.unlock()
-
- completed_mutex.lock()
- completed_count += local_results.size()
- completed_mutex.unlock()
- break
-
- var result = simulate_rank_combination_direct(task_data.combo, 1000000, false, null, null)
-
- local_results.append(result)
-
- if local_results.size() >= batch_size:
- results_mutex.lock()
- all_results.append_array(local_results)
- results_mutex.unlock()
-
- completed_mutex.lock()
- completed_count += local_results.size()
- completed_mutex.unlock()
-
- local_results.clear()
-
-func update_progress():
- """Update progress display"""
- var current_count = 0
- completed_mutex.lock()
- current_count = completed_count
- completed_mutex.unlock()
-
- if current_count >= total_combinations:
- monitoring_active = false
- finish_processing()
- return
-
- var percent = float(current_count) / total_combinations * 100.0
- var elapsed = (Time.get_ticks_msec() - start_time) / 1000.0
- var rate = current_count / elapsed if elapsed > 0 else 0
- var eta_seconds = (total_combinations - current_count) / rate if rate > 0 else 0
-
- var total_cache_checks = cache_hits + cache_misses
- var cache_hit_rate = (float(cache_hits) / total_cache_checks * 100.0) if total_cache_checks > 0 else 0.0
-
- var eta_str = ""
- if eta_seconds > 0:
- var eta_minutes = int(eta_seconds) / 60
- var eta_secs = int(eta_seconds) % 60
- if eta_minutes > 0:
- eta_str = "%dm %ds" % [eta_minutes, eta_secs]
- else:
- eta_str = "%ds" % eta_secs
- else:
- eta_str = "calculating..."
-
- print("Progress: %.1f%% (%d/%d) - %.1f combos/sec - Cache: %.1f%% hits - ETA: %s" % [
- percent, current_count, total_combinations, rate, cache_hit_rate, eta_str
- ])
-
- status_label.text = "Status: Running simulation..."
- progress_label.text = "Progress: %.1f%% (%d/%d)" % [percent, current_count, total_combinations]
- progress_bar.value = percent / 100.0
- rate_label.text = "Speed: %.1f combos/sec" % rate
- eta_label.text = "ETA: %s" % eta_str
-
- cache_hits_label.text = "Cache Hits: %d" % cache_hits
- cache_misses_label.text = "Cache Misses: %d" % cache_misses
- cache_rate_label.text = "Hit Rate: %.1f%%" % cache_hit_rate
- cache_mutex.lock()
- cache_size_label.text = "Cache Entries: %d" % simulation_cache.size()
- cache_mutex.unlock()
-
-func finish_processing():
- """Called when all processing is complete"""
- print("\nAll combinations complete! Waiting for threads to finish...")
-
- for thread in threads:
- thread.wait_to_finish()
- threads.clear()
- threads_done = true
-
- print("All threads finished. Processing results...")
-
- var total_time = (Time.get_ticks_msec() - start_time) / 1000.0
-
- results_mutex.lock()
- var actual_results = all_results.size()
- results_mutex.unlock()
-
- if actual_results != total_combinations:
- print("WARNING: Result count mismatch! Expected %d, got %d" % [total_combinations, actual_results])
-
- print("\n=== RESULTS ===")
- print("Total time: %.1f seconds" % total_time)
- print("Total combinations tested: %d" % actual_results)
-
- var total_cache_checks = cache_hits + cache_misses
- var cache_hit_rate = (float(cache_hits) / total_cache_checks * 100.0) if total_cache_checks > 0 else 0.0
- cache_mutex.lock()
- var cache_size = simulation_cache.size()
- cache_mutex.unlock()
- print("\n=== CACHE STATISTICS ===")
- print("Cache hits: %d" % cache_hits)
- print("Cache misses: %d" % cache_misses)
- print("Hit rate: %.1f%%" % cache_hit_rate)
- print("Cache entries stored: %d" % cache_size)
-
- var successful = all_results.filter(func(r): return r.success)
- print("Successful strategies: %d" % successful.size())
-
- status_label.text = "Status: Complete!"
- progress_label.text = "Progress: 100%% (%d/%d)" % [all_results.size(), total_combinations]
- progress_bar.value = 1.0
- eta_label.text = "Total Time: %.1f seconds" % total_time
-
- var results_text = "[b]SIMULATION COMPLETE[/b]\n\n"
- results_text += "[color=green]Total time: %.1f seconds[/color]\n" % total_time
- results_text += "Combinations tested: %d\n" % all_results.size()
- results_text += "Successful strategies: %d\n\n" % successful.size()
-
- results_text += "[b]Cache Performance:[/b]\n"
- results_text += " Hits: %d\n" % cache_hits
- results_text += " Misses: %d\n" % cache_misses
- results_text += " [color=cyan]Hit Rate: %.1f%%[/color]\n" % cache_hit_rate
- results_text += " Entries: %d\n\n" % cache_size
-
- if successful.size() > 0:
- successful.sort_custom(func(a, b): return a.ticks < b.ticks)
-
- print("\n=== RE-SIMULATING TOP 10 WITH PURCHASE TRACKING ===")
- var top_10_detailed: Array = []
-
- for i in range(min(10, successful.size())):
- var result = successful[i]
- print("Re-simulating #%d..." % (i + 1))
- var detailed_result = simulate_rank_combination_direct(result.rank_targets, 1000000, true)
- top_10_detailed.append(detailed_result)
-
- print("\n=== TOP 10 FASTEST STRATEGIES (WITH PURCHASE TIMELINE) ===")
- results_text += "[b]TOP 10 FASTEST STRATEGIES:[/b]\n\n"
-
- for i in range(top_10_detailed.size()):
- var result = top_10_detailed[i]
- print("\n#%d: %s (%d ticks)" % [i + 1, result.time_formatted, result.ticks])
-
- var rank_display = []
- for unlock_id in result.rank_targets.keys():
- var unlock_name = get_unlock_name_by_id(unlock_id)
- var ranks = result.rank_targets[unlock_id]
- rank_display.append("%s: %d" % [unlock_name, ranks])
- print("Target Ranks: %s" % ", ".join(rank_display))
-
- results_text += "[color=yellow]#%d: %s (%d ticks)[/color]\n" % [i + 1, result.time_formatted, result.ticks]
- results_text += " Ranks: %s\n" % ", ".join(rank_display)
- results_text += " Currency: %.0f\n" % result.final_currency
-
- if result.has("purchases") and result.purchases.size() > 0:
- print("\nPurchase Timeline:")
- results_text += " [b]Purchase Timeline:[/b]\n"
- for purchase in result.purchases:
- var time_str = format_time(purchase.tick)
- print(" %s: %s Rank %d - Cost: %d¥ @ %s" % [
- time_str, purchase.unlock_name, purchase.rank,
- purchase.cost, time_str
- ])
- results_text += " • %s [color=cyan]%s Rank %d[/color] - %d¥ @ %s\n" % [
- format_time(purchase.tick), purchase.unlock_name, purchase.rank,
- purchase.cost, time_str
- ]
- results_text += "\n"
- else:
- print("\nNo successful strategies found!")
- results_text += "[color=red]No successful strategies found![/color]\n"
-
- results_label.text = results_text
-
-func run_comprehensive_test():
- """Test all combinations dynamically generated from resource file"""
- print("\n=== Available Unlocks ===")
- for unlock in unlock_collection.unlocks:
- var max_rank_str = str(unlock.max_rank) if unlock.max_rank > 0 else "unlimited"
- print("ID: %d | %s | Base Cost: %d | Scaling: %s | Max Rank: %s" % [
- unlock.unlock_id,
- unlock.unlock_name,
- unlock.base_cost,
- "Yes" if unlock.is_scaling else "No",
- max_rank_str
- ])
- print(" Modifiers: ", unlock.base_modifiers)
-
- print("\n=== Global Constants ===")
- print("Base Sale Price: %s" % Global.base_sale_price)
- print("Base Purchase Rate: %s" % Global.base_purchase_rate)
- print("Cost Per Whittle: %s" % Global.cost_per_whittle)
-
- # Build pre-calculated tables
- build_precalculated_tables()
-
- # Generate combinations
- var unlimited_cap = 5
- print("\n=== Generation Settings ===")
- print("Unlimited scaling cap: %d ranks" % unlimited_cap)
- var combinations = generate_all_combinations(unlimited_cap)
- total_combinations = combinations.size()
- print("\n=== Testing %d Combinations ===" % total_combinations)
-
- # Fill task queue
- task_queue.clear()
- for combo in combinations:
- task_queue.append({"combo": combo})
-
- # Reset counters
- completed_count = 0
- all_results.clear()
- threads_done = false
- start_time = Time.get_ticks_msec()
- last_progress_time = start_time
- monitoring_active = true
-
- # Create and start threads
- print("Starting %d worker threads..." % num_threads)
- for i in range(num_threads):
- var thread = Thread.new()
- thread.start(worker_thread.bind(i))
- threads.append(thread)
-
- print("All threads started, processing...")
-
-func _exit_tree():
- monitoring_active = false
- for thread in threads:
- if thread.is_alive():
- thread.wait_to_finish()
diff --git a/scripts/sim_direct.gd.uid b/scripts/sim_direct.gd.uid
deleted file mode 100644
index e374a83..0000000
--- a/scripts/sim_direct.gd.uid
+++ /dev/null
@@ -1 +0,0 @@
-uid://citjokiv6skqi
diff --git a/scripts/unlock_data_lightweight.gd b/scripts/unlock_data_lightweight.gd
deleted file mode 100644
index 0aeca12..0000000
--- a/scripts/unlock_data_lightweight.gd
+++ /dev/null
@@ -1,124 +0,0 @@
-class_name UnlockDataLightweight
-## Lightweight unlock data structure for simulations
-## Contains the same calculation logic as UnlockDataResource but without Resource overhead
-
-var unlock_id: int = 0
-var unlock_name: String = ""
-var base_cost: int = 0
-var is_unlocked: bool = false
-
-# Scaling settings
-var is_scaling: bool = false
-var current_rank: int = 0
-var max_rank: int = -1
-
-# Cost scaling
-var cost_scaling_type: int = 1 # 0=Linear, 1=Exponential
-var cost_scaling_multiplier: float = 1.5
-var cost_linear_increase: int = 100
-var cost_ladder: Array[int] = []
-
-# Effect ladder
-var effect_ladder: Array[float] = []
-
-# Base modifiers
-var base_modifiers: Dictionary = {}
-
-## Static factory method to create from UnlockDataResource (one-time conversion)
-static func from_resource(resource: UnlockDataResource) -> UnlockDataLightweight:
- var data = UnlockDataLightweight.new()
- data.unlock_id = resource.unlock_id
- data.unlock_name = resource.unlock_name
- data.base_cost = resource.base_cost
- data.is_scaling = resource.is_scaling
- data.max_rank = resource.max_rank
- data.cost_scaling_type = resource.cost_scaling_type
- data.cost_scaling_multiplier = resource.cost_scaling_multiplier
- data.cost_linear_increase = resource.cost_linear_increase
- data.cost_ladder = resource.cost_ladder.duplicate()
- data.effect_ladder = resource.effect_ladder.duplicate()
- data.base_modifiers = resource.base_modifiers.duplicate(true)
- # Start fresh
- data.is_unlocked = false
- data.current_rank = 0
- return data
-
-## Clone for thread safety (fast - no Resource creation)
-func clone() -> UnlockDataLightweight:
- var copy = UnlockDataLightweight.new()
- copy.unlock_id = unlock_id
- copy.unlock_name = unlock_name
- copy.base_cost = base_cost
- copy.is_scaling = is_scaling
- copy.max_rank = max_rank
- copy.cost_scaling_type = cost_scaling_type
- copy.cost_scaling_multiplier = cost_scaling_multiplier
- copy.cost_linear_increase = cost_linear_increase
- copy.cost_ladder = cost_ladder # Shared - read-only
- copy.effect_ladder = effect_ladder # Shared - read-only
- copy.base_modifiers = base_modifiers # Shared - read-only
- # Mutable state
- copy.is_unlocked = false
- copy.current_rank = 0
- return copy
-
-## Same logic as UnlockDataResource.get_next_cost()
-func get_next_cost() -> int:
- if not is_scaling:
- return base_cost
-
- if cost_ladder.size() > 0 and current_rank < cost_ladder.size():
- return cost_ladder[current_rank]
-
- if cost_scaling_type == 0: # Linear
- return base_cost + (cost_linear_increase * current_rank)
- else: # Exponential
- return int(base_cost * pow(cost_scaling_multiplier, current_rank))
-
-## Same logic as UnlockDataResource.get_current_modifiers()
-func get_current_modifiers() -> Dictionary:
- if not is_unlocked or current_rank == 0:
- return {}
- return get_modifiers_at_rank(current_rank)
-
-## Same logic as UnlockDataResource.get_modifiers_at_rank()
-func get_modifiers_at_rank(rank: int) -> Dictionary:
- if rank == 0:
- return {}
-
- # For one-shot unlocks or empty ladder, return base_modifiers
- if effect_ladder.size() == 0:
- return base_modifiers.duplicate()
-
- var ladder_index = rank - 1
- if ladder_index >= effect_ladder.size():
- ladder_index = effect_ladder.size() - 1 # Use last value if rank exceeds ladder
-
- var result = {}
- for key in base_modifiers.keys():
- result[key] = effect_ladder[ladder_index]
- return result
-
-## Same logic as UnlockDataResource.can_rank_up()
-func can_rank_up() -> bool:
- if not is_scaling:
- return not is_unlocked
-
- if max_rank > 0 and current_rank >= max_rank:
- return false
-
- return true
-
-## Same logic as UnlockDataResource.unlock()
-func unlock() -> bool:
- if not can_rank_up():
- return false
-
- if not is_scaling:
- is_unlocked = true
- current_rank = 1
- else:
- current_rank += 1
- is_unlocked = true
-
- return true
diff --git a/scripts/unlock_data_lightweight.gd.uid b/scripts/unlock_data_lightweight.gd.uid
deleted file mode 100644
index 3336c42..0000000
--- a/scripts/unlock_data_lightweight.gd.uid
+++ /dev/null
@@ -1 +0,0 @@
-uid://yx6cnoob2can