Open CCS Cases 2 in doctor scene
This commit is contained in:
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://f0q6fjw8li34"
|
|
||||||
path="res://.godot/imported/CHIPS-01.wav-582f2a2445236f1807d7d8100300970e.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/CHIPS-01.wav"
|
|
||||||
dest_files=["res://.godot/imported/CHIPS-01.wav-582f2a2445236f1807d7d8100300970e.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://deq0xy3ap7u21"
|
|
||||||
path="res://.godot/imported/CHIPS-02.wav-1b7c7e1f05d4c32bfd5f1a0045e00062.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/CHIPS-02.wav"
|
|
||||||
dest_files=["res://.godot/imported/CHIPS-02.wav-1b7c7e1f05d4c32bfd5f1a0045e00062.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://cp28h2en40qkj"
|
|
||||||
path="res://.godot/imported/CHIPS-03.wav-8b6513f8b6cb9a7a7db482c04e7994a8.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/CHIPS-03.wav"
|
|
||||||
dest_files=["res://.godot/imported/CHIPS-03.wav-8b6513f8b6cb9a7a7db482c04e7994a8.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://d3lj71hfhlvoe"
|
|
||||||
path="res://.godot/imported/CHIPS-04.wav-149d7eaf2a33e2747d2f6fcbec4af0fe.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/CHIPS-04.wav"
|
|
||||||
dest_files=["res://.godot/imported/CHIPS-04.wav-149d7eaf2a33e2747d2f6fcbec4af0fe.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://bbyksq0tjsw7t"
|
|
||||||
path="res://.godot/imported/CLICK-01.wav-12c8c2888253f4fe1c2b6cd3ed702340.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/CLICK-01.wav"
|
|
||||||
dest_files=["res://.godot/imported/CLICK-01.wav-12c8c2888253f4fe1c2b6cd3ed702340.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://btj7os30hhbqt"
|
|
||||||
path="res://.godot/imported/CLICK-02.wav-d3e48ccc9b37871e4c1f58ad7a98c6e2.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/CLICK-02.wav"
|
|
||||||
dest_files=["res://.godot/imported/CLICK-02.wav-d3e48ccc9b37871e4c1f58ad7a98c6e2.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://sg7gexab0vvo"
|
|
||||||
path="res://.godot/imported/CLICK-03.wav-4bbeb40624144445710defe3f04dff22.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/CLICK-03.wav"
|
|
||||||
dest_files=["res://.godot/imported/CLICK-03.wav-4bbeb40624144445710defe3f04dff22.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://dshj5v2aihbb0"
|
|
||||||
path="res://.godot/imported/CLICK-04.wav-156128b0878fd0ff55b94b89c4fc9c6f.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/CLICK-04.wav"
|
|
||||||
dest_files=["res://.godot/imported/CLICK-04.wav-156128b0878fd0ff55b94b89c4fc9c6f.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://bf2gtxwjd6eox"
|
|
||||||
path="res://.godot/imported/DEEPFLAP-01.wav-df2c3f7c79142841e98da9c5a921057d.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/DEEPFLAP-01.wav"
|
|
||||||
dest_files=["res://.godot/imported/DEEPFLAP-01.wav-df2c3f7c79142841e98da9c5a921057d.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://phq30ylu5bo3"
|
|
||||||
path="res://.godot/imported/FAN-WHIR-01.wav-95005855ffe41ed39c5614355bed9e5b.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/FAN-WHIR-01.wav"
|
|
||||||
dest_files=["res://.godot/imported/FAN-WHIR-01.wav-95005855ffe41ed39c5614355bed9e5b.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://dhfgv6iuedgax"
|
|
||||||
path="res://.godot/imported/LOUDCLICK-01.wav-74ce36dff26bf8d7a8977e58e7442165.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/LOUDCLICK-01.wav"
|
|
||||||
dest_files=["res://.godot/imported/LOUDCLICK-01.wav-74ce36dff26bf8d7a8977e58e7442165.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://ufi3xj7myijj"
|
|
||||||
path="res://.godot/imported/MAGNET-THWAP-01.wav-841ae8e9cf025cbca12c9a7dd062cbf7.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/MAGNET-THWAP-01.wav"
|
|
||||||
dest_files=["res://.godot/imported/MAGNET-THWAP-01.wav-841ae8e9cf025cbca12c9a7dd062cbf7.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="wav"
|
|
||||||
type="AudioStreamWAV"
|
|
||||||
uid="uid://brpia062tfuw7"
|
|
||||||
path="res://.godot/imported/THUMP-01.wav-96534e39f9abaf0b6479d088cb5d44df.sample"
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/audio/THUMP-01.wav"
|
|
||||||
dest_files=["res://.godot/imported/THUMP-01.wav-96534e39f9abaf0b6479d088cb5d44df.sample"]
|
|
||||||
|
|
||||||
[params]
|
|
||||||
|
|
||||||
force/8_bit=false
|
|
||||||
force/mono=false
|
|
||||||
force/max_rate=false
|
|
||||||
force/max_rate_hz=44100
|
|
||||||
edit/trim=false
|
|
||||||
edit/normalize=false
|
|
||||||
edit/loop_mode=0
|
|
||||||
edit/loop_begin=0
|
|
||||||
edit/loop_end=-1
|
|
||||||
compress/mode=2
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 MiB |
@@ -1,40 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://d3bo8n2dxx6h4"
|
|
||||||
path="res://.godot/imported/lever-sprite-sheet.png-28bddeaacbfc9f3064287f9324158563.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/images/slot/lever-sprite-sheet.png"
|
|
||||||
dest_files=["res://.godot/imported/lever-sprite-sheet.png-28bddeaacbfc9f3064287f9324158563.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
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 601 KiB |
@@ -1,40 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://u5gmnonww4s4"
|
|
||||||
path="res://.godot/imported/numbers.png-f7b7591684a12e158214a353335df9a3.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/images/slot/numbers.png"
|
|
||||||
dest_files=["res://.godot/imported/numbers.png-f7b7591684a12e158214a353335df9a3.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
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -1,40 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://blq8wype071ou"
|
|
||||||
path="res://.godot/imported/reel-frame.png-45e4b5f53fe075b210e4af141b08081c.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/images/slot/reel-frame.png"
|
|
||||||
dest_files=["res://.godot/imported/reel-frame.png-45e4b5f53fe075b210e4af141b08081c.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
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 335 KiB |
@@ -1,40 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://ujbkwj0i8e2v"
|
|
||||||
path="res://.godot/imported/symbols.png-d6e1a2099254ac8842c1e175b464450a.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://assets/ccs_cases_2/images/slot/symbols.png"
|
|
||||||
dest_files=["res://.godot/imported/symbols.png-d6e1a2099254ac8842c1e175b464450a.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
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
[gd_scene format=3 uid="uid://bq3huk364skd3"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/ui/ccs_cases_2/slot_machine.gd" id="1_qtu10"]
|
|
||||||
[ext_resource type="Shader" path="res://shaders/ccs_cases_2/reel_spin.gdshader" id="2_ee4bf"]
|
|
||||||
[ext_resource type="Texture2D" path="res://assets/ccs_cases_2/images/slot/symbols.png" id="3_ee4bf"]
|
|
||||||
[ext_resource type="Texture2D" path="res://assets/ccs_cases_2/images/slot/numbers.png" id="3_kbped"]
|
|
||||||
[ext_resource type="Texture2D" path="res://assets/ccs_cases_2/images/slot/reel-frame.png" id="4_reel_bg"]
|
|
||||||
[ext_resource type="Texture2D" path="res://assets/ccs_cases_2/images/slot/lever-sprite-sheet.png" id="6_lever_sheet"]
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_bvwqe"]
|
|
||||||
shader = ExtResource("2_ee4bf")
|
|
||||||
shader_parameter/tex = ExtResource("3_ee4bf")
|
|
||||||
shader_parameter/n_rows = 6.0
|
|
||||||
shader_parameter/y_offset = 0.0
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_fj5cn"]
|
|
||||||
shader = ExtResource("2_ee4bf")
|
|
||||||
shader_parameter/tex = ExtResource("3_ee4bf")
|
|
||||||
shader_parameter/n_rows = 6.0
|
|
||||||
shader_parameter/y_offset = 0.0
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_iy58a"]
|
|
||||||
shader = ExtResource("2_ee4bf")
|
|
||||||
shader_parameter/tex = ExtResource("3_ee4bf")
|
|
||||||
shader_parameter/n_rows = 6.0
|
|
||||||
shader_parameter/y_offset = 0.0
|
|
||||||
shader_parameter/x_offset = -0.018
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_6a6wx"]
|
|
||||||
shader = ExtResource("2_ee4bf")
|
|
||||||
shader_parameter/tex = ExtResource("3_kbped")
|
|
||||||
shader_parameter/n_rows = 10.0
|
|
||||||
shader_parameter/y_offset = 0.0
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_ee4bf"]
|
|
||||||
shader = ExtResource("2_ee4bf")
|
|
||||||
shader_parameter/tex = ExtResource("3_kbped")
|
|
||||||
shader_parameter/n_rows = 10.0
|
|
||||||
shader_parameter/y_offset = 0.0
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_kbped"]
|
|
||||||
shader = ExtResource("2_ee4bf")
|
|
||||||
shader_parameter/tex = ExtResource("3_kbped")
|
|
||||||
shader_parameter/n_rows = 10.0
|
|
||||||
shader_parameter/y_offset = 0.0
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_sky72"]
|
|
||||||
shader = ExtResource("2_ee4bf")
|
|
||||||
shader_parameter/tex = ExtResource("3_kbped")
|
|
||||||
shader_parameter/n_rows = 10.0
|
|
||||||
shader_parameter/y_offset = 0.0
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_reel_divider"]
|
|
||||||
atlas = ExtResource("4_reel_bg")
|
|
||||||
region = Rect2(24, 72, 56, 880)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_smooth_top"]
|
|
||||||
atlas = ExtResource("4_reel_bg")
|
|
||||||
region = Rect2(100, 26, 824, 44)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_smooth_bottom"]
|
|
||||||
atlas = ExtResource("4_reel_bg")
|
|
||||||
region = Rect2(100, 954, 824, 44)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lever_0"]
|
|
||||||
atlas = ExtResource("6_lever_sheet")
|
|
||||||
region = Rect2(0, 0, 307, 1024)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lever_1"]
|
|
||||||
atlas = ExtResource("6_lever_sheet")
|
|
||||||
region = Rect2(307, 0, 307, 1024)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lever_2"]
|
|
||||||
atlas = ExtResource("6_lever_sheet")
|
|
||||||
region = Rect2(614, 0, 307, 1024)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lever_3"]
|
|
||||||
atlas = ExtResource("6_lever_sheet")
|
|
||||||
region = Rect2(921, 0, 307, 1024)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_lever_4"]
|
|
||||||
atlas = ExtResource("6_lever_sheet")
|
|
||||||
region = Rect2(1228, 0, 308, 1024)
|
|
||||||
|
|
||||||
[sub_resource type="SpriteFrames" id="SpriteFrames_lever"]
|
|
||||||
animations = [{
|
|
||||||
"frames": [{"duration": 1.0, "texture": SubResource("AtlasTexture_lever_0")}, {"duration": 1.0, "texture": SubResource("AtlasTexture_lever_1")}, {"duration": 1.0, "texture": SubResource("AtlasTexture_lever_2")}, {"duration": 1.0, "texture": SubResource("AtlasTexture_lever_3")}, {"duration": 1.0, "texture": SubResource("AtlasTexture_lever_4")}],
|
|
||||||
"loop": false,
|
|
||||||
"name": &"pull",
|
|
||||||
"speed": 12.0
|
|
||||||
}]
|
|
||||||
|
|
||||||
[node name="Control" type="Control" unique_id=1279508523 node_paths=PackedStringArray("icon_reels", "number_reels")]
|
|
||||||
layout_mode = 3
|
|
||||||
anchors_preset = 0
|
|
||||||
offset_right = 1500.0
|
|
||||||
offset_bottom = 800.0
|
|
||||||
script = ExtResource("1_qtu10")
|
|
||||||
icon_reels = [NodePath("Spinner1"), NodePath("Spinner2"), NodePath("Spinner3")]
|
|
||||||
number_reels = [NodePath("Numbers1"), NodePath("Numbers2"), NodePath("Numbers3"), NodePath("Numbers4")]
|
|
||||||
|
|
||||||
[node name="ReelBackground1" type="TextureRect" parent="."]
|
|
||||||
z_index = -2
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 315.0
|
|
||||||
offset_top = 276.0
|
|
||||||
offset_right = 646.0
|
|
||||||
offset_bottom = 607.0
|
|
||||||
mouse_filter = 2
|
|
||||||
texture = ExtResource("4_reel_bg")
|
|
||||||
expand_mode = 1
|
|
||||||
|
|
||||||
[node name="ReelBackground2" type="TextureRect" parent="."]
|
|
||||||
z_index = -2
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 594.0
|
|
||||||
offset_top = 276.0
|
|
||||||
offset_right = 925.0
|
|
||||||
offset_bottom = 607.0
|
|
||||||
mouse_filter = 2
|
|
||||||
texture = ExtResource("4_reel_bg")
|
|
||||||
expand_mode = 1
|
|
||||||
|
|
||||||
[node name="ReelBackground3" type="TextureRect" parent="."]
|
|
||||||
z_index = -2
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 873.0
|
|
||||||
offset_top = 276.0
|
|
||||||
offset_right = 1204.0
|
|
||||||
offset_bottom = 607.0
|
|
||||||
mouse_filter = 2
|
|
||||||
texture = ExtResource("4_reel_bg")
|
|
||||||
expand_mode = 1
|
|
||||||
|
|
||||||
[node name="ReelWhite1" type="ColorRect" parent="."]
|
|
||||||
z_index = -1
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 341.0
|
|
||||||
offset_top = 301.0
|
|
||||||
offset_right = 620.0
|
|
||||||
offset_bottom = 580.0
|
|
||||||
mouse_filter = 2
|
|
||||||
|
|
||||||
[node name="ReelWhite2" type="ColorRect" parent="."]
|
|
||||||
z_index = -1
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 620.0
|
|
||||||
offset_top = 301.0
|
|
||||||
offset_right = 899.0
|
|
||||||
offset_bottom = 580.0
|
|
||||||
mouse_filter = 2
|
|
||||||
|
|
||||||
[node name="ReelWhite3" type="ColorRect" parent="."]
|
|
||||||
z_index = -1
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 899.0
|
|
||||||
offset_top = 301.0
|
|
||||||
offset_right = 1178.0
|
|
||||||
offset_bottom = 580.0
|
|
||||||
mouse_filter = 2
|
|
||||||
|
|
||||||
[node name="Spinner1" type="ColorRect" parent="." unique_id=813668141]
|
|
||||||
texture_filter = 2
|
|
||||||
material = SubResource("ShaderMaterial_bvwqe")
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 341.0
|
|
||||||
offset_top = 301.0
|
|
||||||
offset_right = 620.0
|
|
||||||
offset_bottom = 580.0
|
|
||||||
|
|
||||||
[node name="Spinner2" type="ColorRect" parent="." unique_id=203022686]
|
|
||||||
material = SubResource("ShaderMaterial_fj5cn")
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 620.0
|
|
||||||
offset_top = 301.0
|
|
||||||
offset_right = 899.0
|
|
||||||
offset_bottom = 580.0
|
|
||||||
|
|
||||||
[node name="Spinner3" type="ColorRect" parent="." unique_id=409654364]
|
|
||||||
material = SubResource("ShaderMaterial_iy58a")
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 899.0
|
|
||||||
offset_top = 301.0
|
|
||||||
offset_right = 1178.0
|
|
||||||
offset_bottom = 580.0
|
|
||||||
|
|
||||||
[node name="MiddleReelLeftDivider" type="TextureRect" parent="."]
|
|
||||||
z_index = 1
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 610.0
|
|
||||||
offset_top = 301.0
|
|
||||||
offset_right = 630.0
|
|
||||||
offset_bottom = 580.0
|
|
||||||
mouse_filter = 2
|
|
||||||
texture = SubResource("AtlasTexture_reel_divider")
|
|
||||||
expand_mode = 1
|
|
||||||
|
|
||||||
[node name="MiddleReelRightDivider" type="TextureRect" parent="."]
|
|
||||||
z_index = 1
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 889.0
|
|
||||||
offset_top = 301.0
|
|
||||||
offset_right = 909.0
|
|
||||||
offset_bottom = 580.0
|
|
||||||
mouse_filter = 2
|
|
||||||
texture = SubResource("AtlasTexture_reel_divider")
|
|
||||||
expand_mode = 1
|
|
||||||
|
|
||||||
[node name="ContinuousTopTrim" type="TextureRect" parent="."]
|
|
||||||
z_index = 2
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 323.0
|
|
||||||
offset_top = 276.0
|
|
||||||
offset_right = 1196.0
|
|
||||||
offset_bottom = 301.0
|
|
||||||
mouse_filter = 2
|
|
||||||
texture = SubResource("AtlasTexture_smooth_top")
|
|
||||||
expand_mode = 1
|
|
||||||
|
|
||||||
[node name="ContinuousBottomTrim" type="TextureRect" parent="."]
|
|
||||||
z_index = 2
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 323.0
|
|
||||||
offset_top = 580.0
|
|
||||||
offset_right = 1196.0
|
|
||||||
offset_bottom = 607.0
|
|
||||||
mouse_filter = 2
|
|
||||||
texture = SubResource("AtlasTexture_smooth_bottom")
|
|
||||||
expand_mode = 1
|
|
||||||
|
|
||||||
[node name="LeverPivot" type="Node2D" parent="."]
|
|
||||||
z_index = 5
|
|
||||||
position = Vector2(1202, 592)
|
|
||||||
|
|
||||||
[node name="LeverSprite" type="AnimatedSprite2D" parent="LeverPivot"]
|
|
||||||
position = Vector2(19, -105)
|
|
||||||
scale = Vector2(0.8, 0.8)
|
|
||||||
sprite_frames = SubResource("SpriteFrames_lever")
|
|
||||||
animation = &"pull"
|
|
||||||
|
|
||||||
[node name="LeverButton" type="Button" parent="."]
|
|
||||||
z_index = 6
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 1107.0
|
|
||||||
offset_top = 257.0
|
|
||||||
offset_right = 1287.0
|
|
||||||
offset_bottom = 602.0
|
|
||||||
mouse_default_cursor_shape = 2
|
|
||||||
focus_mode = 0
|
|
||||||
self_modulate = Color(1, 1, 1, 0)
|
|
||||||
flat = true
|
|
||||||
tooltip_text = "Pull lever"
|
|
||||||
|
|
||||||
[node name="Numbers1" type="ColorRect" parent="." unique_id=2125838097]
|
|
||||||
texture_filter = 2
|
|
||||||
material = SubResource("ShaderMaterial_6a6wx")
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 1092.0
|
|
||||||
offset_top = 110.0
|
|
||||||
offset_right = 1167.0
|
|
||||||
offset_bottom = 185.0
|
|
||||||
|
|
||||||
[node name="Numbers2" type="ColorRect" parent="." unique_id=1145238072]
|
|
||||||
texture_filter = 2
|
|
||||||
material = SubResource("ShaderMaterial_ee4bf")
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 1171.0
|
|
||||||
offset_top = 110.0
|
|
||||||
offset_right = 1246.0
|
|
||||||
offset_bottom = 185.0
|
|
||||||
|
|
||||||
[node name="Numbers3" type="ColorRect" parent="." unique_id=1321048024]
|
|
||||||
texture_filter = 2
|
|
||||||
material = SubResource("ShaderMaterial_kbped")
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 1250.0
|
|
||||||
offset_top = 110.0
|
|
||||||
offset_right = 1325.0
|
|
||||||
offset_bottom = 185.0
|
|
||||||
|
|
||||||
[node name="Numbers4" type="ColorRect" parent="." unique_id=1865653938]
|
|
||||||
texture_filter = 2
|
|
||||||
material = SubResource("ShaderMaterial_sky72")
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 1329.0
|
|
||||||
offset_top = 110.0
|
|
||||||
offset_right = 1404.0
|
|
||||||
offset_bottom = 185.0
|
|
||||||
|
|
||||||
[node name="TestCounterButton" type="Button" parent="."]
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 1092.0
|
|
||||||
offset_top = 202.0
|
|
||||||
offset_right = 1353.0
|
|
||||||
offset_bottom = 252.0
|
|
||||||
text = "Test Counter Setting"
|
|
||||||
|
|
||||||
[node name="ContinueButton" type="Button" parent="."]
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 400.0
|
|
||||||
offset_top = 110.0
|
|
||||||
offset_right = 620.0
|
|
||||||
offset_bottom = 160.0
|
|
||||||
mouse_default_cursor_shape = 2
|
|
||||||
text = "Continue"
|
|
||||||
|
|
||||||
[node name="SpinResultLabel" type="Label" parent="."]
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 683.0
|
|
||||||
offset_top = 744.0
|
|
||||||
offset_right = 913.0
|
|
||||||
offset_bottom = 784.0
|
|
||||||
text = "Ready"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
vertical_alignment = 1
|
|
||||||
|
|
||||||
[connection signal="pressed" from="LeverButton" to="." method="pull_lever"]
|
|
||||||
[connection signal="pressed" from="TestCounterButton" to="." method="test_counter_setting"]
|
|
||||||
[connection signal="pressed" from="ContinueButton" to="." method="go_to_doctor_scene"]
|
|
||||||
@@ -1,770 +0,0 @@
|
|||||||
extends Control
|
|
||||||
|
|
||||||
signal number_change_finished(value: int)
|
|
||||||
signal spin_finished(symbols: Array[int], payout_multiplier: float)
|
|
||||||
signal continue_requested
|
|
||||||
|
|
||||||
@export var icon_reels: Array[Control]
|
|
||||||
@export var number_reels: Array[Control]
|
|
||||||
@export_range(-40.0, 0.0, 0.5) var click_volume_db := -11.0
|
|
||||||
@export_range(0.0, 0.2, 0.005) var pitch_variation := 0.055
|
|
||||||
@export_range(-40.0, -10.0, 0.5) var reel_whir_volume_db := -30.0
|
|
||||||
@export_range(0.0, 1.5, 0.05) var extra_icon_reel_stop_delay := 0.4
|
|
||||||
@export_range(0.1, 2.0, 0.05) var number_roll_duration := 0.6
|
|
||||||
@export_category("Slot Math")
|
|
||||||
@export_range(0.0, 1.0, 0.001) var target_rtp := 0.96
|
|
||||||
@export var symbol_names := PackedStringArray([
|
|
||||||
"Star", "Diamond", "Potion", "Shield", "Chips", "Gift"
|
|
||||||
])
|
|
||||||
@export var symbol_payout_min := PackedInt32Array([1, 50, 400, 300, 5, 0])
|
|
||||||
@export var symbol_payout_max := PackedInt32Array([1, 100, 400, 300, 10, 0])
|
|
||||||
@export var symbol_rtp_shares := PackedFloat32Array([
|
|
||||||
0.25686958, 0.15625, 0.16666667, 0.15625, 0.20833333, 0.05563042
|
|
||||||
])
|
|
||||||
@export var loss_symbol_weights := PackedFloat32Array([1.0, 2.0, 4.0, 6.0, 10.0, 3.0])
|
|
||||||
@export_range(0.0, 1.0, 0.01) var near_miss_rate := 0.15
|
|
||||||
@export_category("Early Session Study")
|
|
||||||
@export_range(0, 20, 1) var early_boost_spin_count := 5
|
|
||||||
@export_range(0.0, 1.0, 0.01) var early_blue_stack_chance := 0.20
|
|
||||||
|
|
||||||
const CLICK_SOUNDS: Array[AudioStream] = [
|
|
||||||
preload("res://assets/ccs_cases_2/audio/CLICK-01.wav"),
|
|
||||||
preload("res://assets/ccs_cases_2/audio/CLICK-02.wav"),
|
|
||||||
preload("res://assets/ccs_cases_2/audio/CLICK-03.wav"),
|
|
||||||
preload("res://assets/ccs_cases_2/audio/CLICK-04.wav"),
|
|
||||||
]
|
|
||||||
# The recordings contain different amounts of silence before their transients.
|
|
||||||
# Starting here preserves a tiny lead-in while keeping the impact synchronized.
|
|
||||||
const CLICK_START_OFFSETS: Array[float] = [0.040, 0.039, 0.052, 0.061]
|
|
||||||
const STOP_CLICK: AudioStream = preload("res://assets/ccs_cases_2/audio/LOUDCLICK-01.wav")
|
|
||||||
# This recording has about 167 ms of silence before its impact.
|
|
||||||
const STOP_CLICK_START_OFFSET := 0.167
|
|
||||||
const STOP_FLAP: AudioStream = preload("res://assets/ccs_cases_2/audio/DEEPFLAP-01.wav")
|
|
||||||
# The deep layer has about 159 ms of silence before its transient.
|
|
||||||
const STOP_FLAP_START_OFFSET := 0.159
|
|
||||||
const SPIN_START_THUMP: AudioStream = preload("res://assets/ccs_cases_2/audio/THUMP-01.wav")
|
|
||||||
const LEVER_THWAP: AudioStream = preload("res://assets/ccs_cases_2/audio/MAGNET-THWAP-01.wav")
|
|
||||||
const LEVER_THWAP_START_OFFSET := 0.110
|
|
||||||
const REEL_WHIR: AudioStream = preload("res://assets/ccs_cases_2/audio/FAN-WHIR-01.wav")
|
|
||||||
const REEL_WHIR_START_OFFSET := 1.72
|
|
||||||
const CHIP_SOUNDS: Array[AudioStream] = [
|
|
||||||
preload("res://assets/ccs_cases_2/audio/CHIPS-01.wav"),
|
|
||||||
preload("res://assets/ccs_cases_2/audio/CHIPS-02.wav"),
|
|
||||||
preload("res://assets/ccs_cases_2/audio/CHIPS-03.wav"),
|
|
||||||
preload("res://assets/ccs_cases_2/audio/CHIPS-04.wav"),
|
|
||||||
]
|
|
||||||
const CHIP_START_OFFSETS: Array[float] = [0.224, 0.154, 0.201, 0.261]
|
|
||||||
const AUDIO_POOL_SIZE := 16
|
|
||||||
const SYMBOL_COUNT := 6
|
|
||||||
const SINGLE_COIN_SYMBOL := 0
|
|
||||||
const BLUE_COIN_STACK_SYMBOL := 4
|
|
||||||
const PRESENT_SYMBOL := 5
|
|
||||||
const LEVER_FRAME_POSITIONS: Array[Vector2] = [
|
|
||||||
Vector2(19.0, -105.0),
|
|
||||||
Vector2(23.0, -106.0),
|
|
||||||
Vector2(12.0, -82.0),
|
|
||||||
Vector2(12.0, -90.0),
|
|
||||||
Vector2(5.0, -90.0),
|
|
||||||
]
|
|
||||||
|
|
||||||
var values: Array[int] = []
|
|
||||||
var reel_tweens: Array[Tween] = []
|
|
||||||
var number_tweens: Array[Tween] = []
|
|
||||||
var number_value := 100
|
|
||||||
var number_animation_active := false
|
|
||||||
var audio_pool: Array[AudioStreamPlayer] = []
|
|
||||||
var next_audio_player := 0
|
|
||||||
var last_click_sound := -1
|
|
||||||
var last_chip_sound := -1
|
|
||||||
var chip_count_tween: Tween
|
|
||||||
var reel_whir_player: AudioStreamPlayer
|
|
||||||
var reel_whir_tween: Tween
|
|
||||||
var win_effect_tween: Tween
|
|
||||||
var spin_in_progress := false
|
|
||||||
var stopped_icon_reels := 0
|
|
||||||
var selected_payout := 0.0
|
|
||||||
var selected_near_miss := false
|
|
||||||
var selected_early_session_boost := false
|
|
||||||
var session_spins_started := 0
|
|
||||||
var early_stack_bonus_consumed := false
|
|
||||||
var rng := RandomNumberGenerator.new()
|
|
||||||
var lever_tween: Tween
|
|
||||||
var lever_foley_tween: Tween
|
|
||||||
var lever_animating := false
|
|
||||||
|
|
||||||
@onready var spin_result_label: Label = $SpinResultLabel
|
|
||||||
@onready var lever_pivot: Node2D = $LeverPivot
|
|
||||||
@onready var lever_sprite: AnimatedSprite2D = $LeverPivot/LeverSprite
|
|
||||||
@onready var lever_button: Button = $LeverButton
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
rng.randomize()
|
|
||||||
reel_whir_player = AudioStreamPlayer.new()
|
|
||||||
reel_whir_player.name = "ReelWhirPlayer"
|
|
||||||
reel_whir_player.stream = REEL_WHIR
|
|
||||||
add_child(reel_whir_player)
|
|
||||||
# A small voice pool lets nearby reel clicks overlap without cutting each other off.
|
|
||||||
for i in AUDIO_POOL_SIZE:
|
|
||||||
var player := AudioStreamPlayer.new()
|
|
||||||
player.name = "ReelClickVoice%d" % (i + 1)
|
|
||||||
player.volume_db = click_volume_db
|
|
||||||
add_child(player)
|
|
||||||
audio_pool.append(player)
|
|
||||||
_apply_number_immediately(number_value)
|
|
||||||
|
|
||||||
|
|
||||||
func spin() -> void:
|
|
||||||
if spin_in_progress:
|
|
||||||
return
|
|
||||||
|
|
||||||
var configuration_error := _get_slot_math_error()
|
|
||||||
if not configuration_error.is_empty():
|
|
||||||
push_error(configuration_error)
|
|
||||||
spin_result_label.text = "CONFIG ERROR"
|
|
||||||
return
|
|
||||||
if number_value <= 0:
|
|
||||||
spin_result_label.text = "NO CREDITS"
|
|
||||||
return
|
|
||||||
|
|
||||||
_reset_win_effect()
|
|
||||||
|
|
||||||
for active_tween in reel_tweens:
|
|
||||||
if active_tween and active_tween.is_valid():
|
|
||||||
active_tween.kill()
|
|
||||||
reel_tweens.clear()
|
|
||||||
var early_session_eligible := (
|
|
||||||
not early_stack_bonus_consumed
|
|
||||||
and session_spins_started < early_boost_spin_count
|
|
||||||
)
|
|
||||||
var outcome := _select_spin_outcome(early_session_eligible)
|
|
||||||
session_spins_started += 1
|
|
||||||
values = outcome.symbols
|
|
||||||
if (
|
|
||||||
values[0] == BLUE_COIN_STACK_SYMBOL
|
|
||||||
and values[0] == values[1]
|
|
||||||
and values[1] == values[2]
|
|
||||||
):
|
|
||||||
early_stack_bonus_consumed = true
|
|
||||||
selected_payout = outcome.payout
|
|
||||||
selected_near_miss = outcome.is_near_miss
|
|
||||||
selected_early_session_boost = outcome.early_session_boost
|
|
||||||
stopped_icon_reels = 0
|
|
||||||
spin_in_progress = true
|
|
||||||
lever_button.disabled = true
|
|
||||||
spin_result_label.text = "Spinning..."
|
|
||||||
add_to_number(-1)
|
|
||||||
_play_spin_start()
|
|
||||||
_play_short_reel_whir()
|
|
||||||
|
|
||||||
for i in icon_reels.size():
|
|
||||||
var spinner := icon_reels[i]
|
|
||||||
var n_rows := int(spinner.material.get_shader_parameter("n_rows"))
|
|
||||||
var result := values[i]
|
|
||||||
|
|
||||||
var step := 1.0 / float(n_rows)
|
|
||||||
var start := float(spinner.material.get_shader_parameter("y_offset"))
|
|
||||||
|
|
||||||
# Several full downward rotations plus the predetermined final result.
|
|
||||||
# Negative texture offset moves this vertical strip downward on screen.
|
|
||||||
var rotations := randi_range(4, 7)
|
|
||||||
var target := floorf(start) - rotations + result * step
|
|
||||||
|
|
||||||
# Later reels stop later, with slight randomness.
|
|
||||||
var duration := 1.8 + i * 0.55 + randf_range(0.1, 0.45)
|
|
||||||
# Open up the spacing between the three prominent icon-reel stops without
|
|
||||||
# changing reel 1 or the timing of the smaller number reels.
|
|
||||||
if i < 3:
|
|
||||||
duration += i * extra_icon_reel_stop_delay
|
|
||||||
var tick_state := {
|
|
||||||
"last_row": floori(start * n_rows),
|
|
||||||
"start": start,
|
|
||||||
"distance": target - start,
|
|
||||||
"final_row": floori(target * n_rows),
|
|
||||||
}
|
|
||||||
|
|
||||||
var reel_tween := create_tween()
|
|
||||||
reel_tweens.append(reel_tween)
|
|
||||||
reel_tween.tween_method(
|
|
||||||
func(value: float) -> void:
|
|
||||||
spinner.material.set_shader_parameter("y_offset", value)
|
|
||||||
var current_row := floori(value * n_rows)
|
|
||||||
if current_row != tick_state.last_row:
|
|
||||||
tick_state.last_row = current_row
|
|
||||||
# The final detent is scheduled separately to compensate for
|
|
||||||
# audio-device latency; do not also play it at the endpoint.
|
|
||||||
if current_row != tick_state.final_row:
|
|
||||||
var progress := clampf(
|
|
||||||
(value - tick_state.start) / tick_state.distance,
|
|
||||||
0.0,
|
|
||||||
1.0
|
|
||||||
)
|
|
||||||
_play_reel_click(progress, i),
|
|
||||||
start,
|
|
||||||
target,
|
|
||||||
duration
|
|
||||||
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
|
|
||||||
|
|
||||||
reel_tween.tween_callback(func() -> void:
|
|
||||||
spinner.material.set_shader_parameter("y_offset", fmod(target, 1.0))
|
|
||||||
stopped_icon_reels += 1
|
|
||||||
if stopped_icon_reels == icon_reels.size():
|
|
||||||
_complete_spin()
|
|
||||||
)
|
|
||||||
|
|
||||||
if i < 3:
|
|
||||||
# Audio reaches the speakers after the engine submits it. Start the stop
|
|
||||||
# sound early by the latency reported for the active audio device so its
|
|
||||||
# transient reaches the listener at the visual tween endpoint.
|
|
||||||
var audio_latency := AudioServer.get_output_latency()
|
|
||||||
audio_latency += AudioServer.get_time_to_next_mix()
|
|
||||||
var stop_click_tween := create_tween()
|
|
||||||
reel_tweens.append(stop_click_tween)
|
|
||||||
stop_click_tween.tween_interval(maxf(duration - audio_latency, 0.0))
|
|
||||||
stop_click_tween.tween_callback(_play_stop_click)
|
|
||||||
|
|
||||||
|
|
||||||
func _select_spin_outcome(allow_early_session_boost: bool = false) -> Dictionary:
|
|
||||||
if allow_early_session_boost and rng.randf() < early_blue_stack_chance:
|
|
||||||
return {
|
|
||||||
"symbols": [
|
|
||||||
BLUE_COIN_STACK_SYMBOL,
|
|
||||||
BLUE_COIN_STACK_SYMBOL,
|
|
||||||
BLUE_COIN_STACK_SYMBOL,
|
|
||||||
] as Array[int],
|
|
||||||
"payout": _roll_symbol_payout(BLUE_COIN_STACK_SYMBOL),
|
|
||||||
"is_near_miss": false,
|
|
||||||
"early_session_boost": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var roll := rng.randf()
|
|
||||||
var cumulative_probability := 0.0
|
|
||||||
var share_total := _sum_positive(symbol_rtp_shares)
|
|
||||||
|
|
||||||
for symbol_index in SYMBOL_COUNT:
|
|
||||||
var normalized_share := maxf(symbol_rtp_shares[symbol_index], 0.0) / share_total
|
|
||||||
var win_probability := target_rtp * normalized_share / _get_symbol_model_value(symbol_index)
|
|
||||||
cumulative_probability += win_probability
|
|
||||||
if roll < cumulative_probability:
|
|
||||||
return {
|
|
||||||
"symbols": [symbol_index, symbol_index, symbol_index] as Array[int],
|
|
||||||
"payout": _roll_symbol_payout(symbol_index),
|
|
||||||
"is_near_miss": false,
|
|
||||||
"early_session_boost": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
if rng.randf() < near_miss_rate:
|
|
||||||
var near_miss_symbol := _pick_near_miss_symbol()
|
|
||||||
# With the current downward travel, the preceding symbol on the cyclic
|
|
||||||
# strip stops one position beyond the matching symbol.
|
|
||||||
var adjacent_symbol := posmod(near_miss_symbol - 1, SYMBOL_COUNT)
|
|
||||||
return {
|
|
||||||
"symbols": [near_miss_symbol, near_miss_symbol, adjacent_symbol] as Array[int],
|
|
||||||
"payout": 0.0,
|
|
||||||
"is_near_miss": true,
|
|
||||||
"early_session_boost": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
var losing_symbols: Array[int] = []
|
|
||||||
while true:
|
|
||||||
losing_symbols = [
|
|
||||||
_pick_weighted_symbol(loss_symbol_weights),
|
|
||||||
_pick_weighted_symbol(loss_symbol_weights),
|
|
||||||
_pick_weighted_symbol(loss_symbol_weights),
|
|
||||||
]
|
|
||||||
# First-two matches are reserved for the explicit near-miss outcome so
|
|
||||||
# its configured rate remains exact and measurable.
|
|
||||||
if losing_symbols[0] != losing_symbols[1]:
|
|
||||||
break
|
|
||||||
|
|
||||||
return {
|
|
||||||
"symbols": losing_symbols,
|
|
||||||
"payout": 0.0,
|
|
||||||
"is_near_miss": false,
|
|
||||||
"early_session_boost": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func reset_early_session_study() -> void:
|
|
||||||
session_spins_started = 0
|
|
||||||
early_stack_bonus_consumed = false
|
|
||||||
|
|
||||||
|
|
||||||
func _pick_weighted_symbol(weights: PackedFloat32Array) -> int:
|
|
||||||
var total := _sum_positive(weights)
|
|
||||||
var roll := rng.randf() * total
|
|
||||||
var cumulative := 0.0
|
|
||||||
for i in SYMBOL_COUNT:
|
|
||||||
cumulative += maxf(weights[i], 0.0)
|
|
||||||
if roll < cumulative:
|
|
||||||
return i
|
|
||||||
return SYMBOL_COUNT - 1
|
|
||||||
|
|
||||||
|
|
||||||
func _pick_near_miss_symbol() -> int:
|
|
||||||
var total := 0.0
|
|
||||||
for i in range(1, SYMBOL_COUNT):
|
|
||||||
total += maxf(loss_symbol_weights[i], 0.0)
|
|
||||||
var roll := rng.randf() * total
|
|
||||||
var cumulative := 0.0
|
|
||||||
for i in range(1, SYMBOL_COUNT):
|
|
||||||
cumulative += maxf(loss_symbol_weights[i], 0.0)
|
|
||||||
if roll < cumulative:
|
|
||||||
return i
|
|
||||||
return SYMBOL_COUNT - 1
|
|
||||||
|
|
||||||
|
|
||||||
func _complete_spin() -> void:
|
|
||||||
if values[0] == PRESENT_SYMBOL and values[0] == values[1] and values[1] == values[2]:
|
|
||||||
spin_result_label.text = "PRESENT — no award"
|
|
||||||
elif selected_payout > 0.0:
|
|
||||||
var symbol_name := symbol_names[values[0]]
|
|
||||||
spin_result_label.text = "WIN %sx — %s" % [_format_multiplier(selected_payout), symbol_name.to_upper()]
|
|
||||||
_play_win_celebration()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
spin_result_label.text = "No win"
|
|
||||||
_finish_spin()
|
|
||||||
|
|
||||||
|
|
||||||
func _play_win_celebration() -> void:
|
|
||||||
_reset_win_effect()
|
|
||||||
win_effect_tween = create_tween()
|
|
||||||
win_effect_tween.tween_interval(0.10)
|
|
||||||
win_effect_tween.tween_method(_set_win_intensity, 0.0, 1.0, 0.10).set_trans(
|
|
||||||
Tween.TRANS_QUAD
|
|
||||||
).set_ease(Tween.EASE_OUT)
|
|
||||||
win_effect_tween.tween_method(_set_win_intensity, 1.0, 0.35, 0.13).set_trans(
|
|
||||||
Tween.TRANS_QUAD
|
|
||||||
).set_ease(Tween.EASE_IN)
|
|
||||||
win_effect_tween.tween_method(_set_win_intensity, 0.35, 0.85, 0.14).set_trans(
|
|
||||||
Tween.TRANS_SINE
|
|
||||||
).set_ease(Tween.EASE_OUT)
|
|
||||||
win_effect_tween.tween_callback(_award_selected_win)
|
|
||||||
win_effect_tween.tween_method(_set_win_intensity, 0.85, 0.0, 0.30).set_trans(
|
|
||||||
Tween.TRANS_SINE
|
|
||||||
).set_ease(Tween.EASE_IN_OUT)
|
|
||||||
win_effect_tween.tween_callback(_finish_spin)
|
|
||||||
|
|
||||||
|
|
||||||
func _award_selected_win() -> void:
|
|
||||||
if values[0] == BLUE_COIN_STACK_SYMBOL:
|
|
||||||
_play_chip_count(int(roundf(selected_payout)))
|
|
||||||
_award_counter_for_symbol(values[0], selected_payout)
|
|
||||||
|
|
||||||
|
|
||||||
func _set_win_intensity(intensity: float) -> void:
|
|
||||||
for reel in icon_reels:
|
|
||||||
reel.material.set_shader_parameter("win_intensity", intensity)
|
|
||||||
|
|
||||||
|
|
||||||
func _reset_win_effect() -> void:
|
|
||||||
if win_effect_tween and win_effect_tween.is_valid():
|
|
||||||
win_effect_tween.kill()
|
|
||||||
win_effect_tween = null
|
|
||||||
_set_win_intensity(0.0)
|
|
||||||
|
|
||||||
|
|
||||||
func _finish_spin() -> void:
|
|
||||||
_set_win_intensity(0.0)
|
|
||||||
spin_in_progress = false
|
|
||||||
if not lever_animating:
|
|
||||||
lever_button.disabled = false
|
|
||||||
spin_finished.emit(values.duplicate(), selected_payout)
|
|
||||||
|
|
||||||
|
|
||||||
func pull_lever() -> void:
|
|
||||||
if lever_animating or spin_in_progress:
|
|
||||||
return
|
|
||||||
|
|
||||||
lever_animating = true
|
|
||||||
lever_button.disabled = true
|
|
||||||
if lever_tween and lever_tween.is_valid():
|
|
||||||
lever_tween.kill()
|
|
||||||
if lever_foley_tween and lever_foley_tween.is_valid():
|
|
||||||
lever_foley_tween.kill()
|
|
||||||
var audio_latency := AudioServer.get_output_latency()
|
|
||||||
audio_latency += AudioServer.get_time_to_next_mix()
|
|
||||||
lever_foley_tween = create_tween()
|
|
||||||
lever_foley_tween.tween_interval(maxf(0.28 - audio_latency, 0.0))
|
|
||||||
lever_foley_tween.tween_callback(_play_lever_thwap)
|
|
||||||
lever_tween = create_tween()
|
|
||||||
lever_tween.tween_method(_set_lever_pull_progress, 0.0, 1.0, 0.28).set_trans(
|
|
||||||
Tween.TRANS_CUBIC
|
|
||||||
).set_ease(Tween.EASE_IN)
|
|
||||||
lever_tween.tween_callback(spin)
|
|
||||||
lever_tween.tween_interval(0.08)
|
|
||||||
lever_tween.tween_method(_set_lever_pull_progress, 1.0, 0.0, 0.46).set_trans(
|
|
||||||
Tween.TRANS_BACK
|
|
||||||
).set_ease(Tween.EASE_OUT)
|
|
||||||
lever_tween.tween_callback(func() -> void:
|
|
||||||
lever_animating = false
|
|
||||||
if not spin_in_progress:
|
|
||||||
lever_button.disabled = false
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _play_lever_thwap() -> void:
|
|
||||||
var player := audio_pool[next_audio_player]
|
|
||||||
next_audio_player = (next_audio_player + 1) % audio_pool.size()
|
|
||||||
player.stream = LEVER_THWAP
|
|
||||||
player.pitch_scale = 1.0 + randf_range(-pitch_variation * 0.3, pitch_variation * 0.3)
|
|
||||||
player.volume_db = click_volume_db + 1.5
|
|
||||||
player.play(LEVER_THWAP_START_OFFSET)
|
|
||||||
|
|
||||||
|
|
||||||
func _play_chip_count(point_count: int) -> void:
|
|
||||||
if chip_count_tween and chip_count_tween.is_valid():
|
|
||||||
chip_count_tween.kill()
|
|
||||||
chip_count_tween = create_tween()
|
|
||||||
for point_index in point_count:
|
|
||||||
chip_count_tween.tween_callback(
|
|
||||||
_play_chip_point.bind(point_index, point_count)
|
|
||||||
)
|
|
||||||
if point_index < point_count - 1:
|
|
||||||
var progress := float(point_index) / maxf(float(point_count - 1), 1.0)
|
|
||||||
chip_count_tween.tween_interval(lerpf(0.105, 0.072, progress))
|
|
||||||
|
|
||||||
|
|
||||||
func _play_chip_point(point_index: int, point_count: int) -> void:
|
|
||||||
var sound_index := rng.randi_range(0, CHIP_SOUNDS.size() - 2)
|
|
||||||
if last_chip_sound >= 0 and sound_index >= last_chip_sound:
|
|
||||||
sound_index += 1
|
|
||||||
last_chip_sound = sound_index
|
|
||||||
var player := audio_pool[next_audio_player]
|
|
||||||
next_audio_player = (next_audio_player + 1) % audio_pool.size()
|
|
||||||
player.stream = CHIP_SOUNDS[sound_index]
|
|
||||||
var count_progress := float(point_index) / maxf(float(point_count - 1), 1.0)
|
|
||||||
player.pitch_scale = lerpf(0.96, 1.08, count_progress) + randf_range(-0.012, 0.012)
|
|
||||||
# Individual recordings have long tails, so keep each voice restrained as
|
|
||||||
# the sequence builds into the familiar overlapping chip-count texture.
|
|
||||||
player.volume_db = click_volume_db - 3.0 + randf_range(-0.6, 0.4)
|
|
||||||
player.play(CHIP_START_OFFSETS[sound_index])
|
|
||||||
|
|
||||||
|
|
||||||
func _play_short_reel_whir() -> void:
|
|
||||||
if reel_whir_tween and reel_whir_tween.is_valid():
|
|
||||||
reel_whir_tween.kill()
|
|
||||||
reel_whir_player.stop()
|
|
||||||
reel_whir_player.volume_db = reel_whir_volume_db
|
|
||||||
reel_whir_player.pitch_scale = 0.92 + randf_range(-0.01, 0.01)
|
|
||||||
# Use only the recording's naturally decelerating tail, then fade it before
|
|
||||||
# it can compete with the individual reel clicks.
|
|
||||||
reel_whir_player.play(REEL_WHIR_START_OFFSET)
|
|
||||||
reel_whir_tween = create_tween()
|
|
||||||
reel_whir_tween.tween_interval(0.6)
|
|
||||||
reel_whir_tween.tween_property(reel_whir_player, "volume_db", -60.0, 0.35)
|
|
||||||
reel_whir_tween.tween_callback(reel_whir_player.stop)
|
|
||||||
|
|
||||||
|
|
||||||
func _set_lever_pull_progress(progress: float) -> void:
|
|
||||||
# The generated poses contain real perspective changes. Per-frame offsets keep
|
|
||||||
# their slightly different mounting sockets anchored to the cabinet pivot.
|
|
||||||
lever_pivot.rotation = 0.0
|
|
||||||
var frame_index := clampi(roundi(progress * 4.0), 0, 4)
|
|
||||||
lever_sprite.frame = frame_index
|
|
||||||
lever_sprite.position = LEVER_FRAME_POSITIONS[frame_index]
|
|
||||||
|
|
||||||
|
|
||||||
func _award_counter_for_symbol(symbol_index: int, payout: float) -> void:
|
|
||||||
match symbol_index:
|
|
||||||
SINGLE_COIN_SYMBOL:
|
|
||||||
add_to_number(int(roundf(payout)))
|
|
||||||
BLUE_COIN_STACK_SYMBOL:
|
|
||||||
add_to_number(int(roundf(payout)))
|
|
||||||
|
|
||||||
|
|
||||||
func _roll_symbol_payout(symbol_index: int) -> float:
|
|
||||||
if symbol_index == PRESENT_SYMBOL:
|
|
||||||
return 0.0
|
|
||||||
return float(rng.randi_range(symbol_payout_min[symbol_index], symbol_payout_max[symbol_index]))
|
|
||||||
|
|
||||||
|
|
||||||
func _get_symbol_model_value(symbol_index: int) -> float:
|
|
||||||
if symbol_index == PRESENT_SYMBOL:
|
|
||||||
var other_symbol_total := 0.0
|
|
||||||
for i in PRESENT_SYMBOL:
|
|
||||||
other_symbol_total += _get_symbol_expected_payout(i)
|
|
||||||
return other_symbol_total / float(PRESENT_SYMBOL)
|
|
||||||
return _get_symbol_expected_payout(symbol_index)
|
|
||||||
|
|
||||||
|
|
||||||
func _get_symbol_expected_payout(symbol_index: int) -> float:
|
|
||||||
return (float(symbol_payout_min[symbol_index]) + float(symbol_payout_max[symbol_index])) * 0.5
|
|
||||||
|
|
||||||
|
|
||||||
func _format_multiplier(multiplier: float) -> String:
|
|
||||||
if is_equal_approx(multiplier, roundf(multiplier)):
|
|
||||||
return str(int(roundf(multiplier)))
|
|
||||||
return "%.2f" % multiplier
|
|
||||||
|
|
||||||
|
|
||||||
func get_theoretical_rtp() -> float:
|
|
||||||
if not _get_slot_math_error().is_empty():
|
|
||||||
return 0.0
|
|
||||||
var share_total := _sum_positive(symbol_rtp_shares)
|
|
||||||
var theoretical_rtp := 0.0
|
|
||||||
for i in SYMBOL_COUNT:
|
|
||||||
var normalized_share := maxf(symbol_rtp_shares[i], 0.0) / share_total
|
|
||||||
var model_value := _get_symbol_model_value(i)
|
|
||||||
var probability := target_rtp * normalized_share / model_value
|
|
||||||
theoretical_rtp += probability * model_value
|
|
||||||
return theoretical_rtp
|
|
||||||
|
|
||||||
|
|
||||||
func get_theoretical_credited_rtp() -> float:
|
|
||||||
if not _get_slot_math_error().is_empty():
|
|
||||||
return 0.0
|
|
||||||
var share_total := _sum_positive(symbol_rtp_shares)
|
|
||||||
var credited_rtp := 0.0
|
|
||||||
for i in [SINGLE_COIN_SYMBOL, BLUE_COIN_STACK_SYMBOL]:
|
|
||||||
var normalized_share := maxf(symbol_rtp_shares[i], 0.0) / share_total
|
|
||||||
var model_value := _get_symbol_model_value(i)
|
|
||||||
var probability := target_rtp * normalized_share / model_value
|
|
||||||
credited_rtp += probability * _get_symbol_expected_payout(i)
|
|
||||||
return credited_rtp
|
|
||||||
|
|
||||||
|
|
||||||
func get_theoretical_hit_rate() -> float:
|
|
||||||
if not _get_slot_math_error().is_empty():
|
|
||||||
return 0.0
|
|
||||||
var share_total := _sum_positive(symbol_rtp_shares)
|
|
||||||
var hit_rate := 0.0
|
|
||||||
for i in SYMBOL_COUNT:
|
|
||||||
var normalized_share := maxf(symbol_rtp_shares[i], 0.0) / share_total
|
|
||||||
hit_rate += target_rtp * normalized_share / _get_symbol_model_value(i)
|
|
||||||
return hit_rate
|
|
||||||
|
|
||||||
|
|
||||||
func _get_slot_math_error() -> String:
|
|
||||||
if icon_reels.size() != 3:
|
|
||||||
return "Slot math requires exactly three icon reels."
|
|
||||||
if symbol_names.size() != SYMBOL_COUNT:
|
|
||||||
return "Symbol names must contain exactly six entries."
|
|
||||||
if symbol_payout_min.size() != SYMBOL_COUNT or symbol_payout_max.size() != SYMBOL_COUNT:
|
|
||||||
return "Symbol payout minimums and maximums must contain exactly six entries."
|
|
||||||
if symbol_rtp_shares.size() != SYMBOL_COUNT or loss_symbol_weights.size() != SYMBOL_COUNT:
|
|
||||||
return "RTP shares and loss display weights must contain exactly six entries."
|
|
||||||
for i in SYMBOL_COUNT:
|
|
||||||
if symbol_payout_min[i] < 0 or symbol_payout_max[i] < symbol_payout_min[i]:
|
|
||||||
return "Symbol payout ranges must be nonnegative and minimum cannot exceed maximum."
|
|
||||||
if i != PRESENT_SYMBOL and _get_symbol_expected_payout(i) <= 0.0:
|
|
||||||
return "Every non-present symbol must have a positive expected payout."
|
|
||||||
if _sum_positive(symbol_rtp_shares) <= 0.0:
|
|
||||||
return "At least one RTP share must be positive."
|
|
||||||
if _sum_positive(loss_symbol_weights) <= 0.0:
|
|
||||||
return "At least one loss display weight must be positive."
|
|
||||||
var near_miss_weight_total := 0.0
|
|
||||||
for i in range(1, SYMBOL_COUNT):
|
|
||||||
near_miss_weight_total += maxf(loss_symbol_weights[i], 0.0)
|
|
||||||
if near_miss_rate > 0.0 and near_miss_weight_total <= 0.0:
|
|
||||||
return "Near misses require a positive display weight for a non-coin symbol."
|
|
||||||
if near_miss_rate < 0.0 or near_miss_rate > 1.0:
|
|
||||||
return "Near-miss rate must be between zero and one."
|
|
||||||
|
|
||||||
var share_total := _sum_positive(symbol_rtp_shares)
|
|
||||||
var total_win_probability := 0.0
|
|
||||||
for i in SYMBOL_COUNT:
|
|
||||||
var normalized_share := maxf(symbol_rtp_shares[i], 0.0) / share_total
|
|
||||||
total_win_probability += target_rtp * normalized_share / _get_symbol_model_value(i)
|
|
||||||
if total_win_probability > 1.0:
|
|
||||||
return "Configured payouts and RTP produce a win probability above 100%."
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
func _sum_positive(numbers: PackedFloat32Array) -> float:
|
|
||||||
var total := 0.0
|
|
||||||
for number in numbers:
|
|
||||||
total += maxf(number, 0.0)
|
|
||||||
return total
|
|
||||||
|
|
||||||
|
|
||||||
func _play_reel_click(spin_progress: float, _reel_index: int) -> void:
|
|
||||||
if audio_pool.is_empty():
|
|
||||||
return
|
|
||||||
|
|
||||||
# Avoid the artificial sound of choosing the same sample twice in a row.
|
|
||||||
var sound_index := randi_range(0, CLICK_SOUNDS.size() - 2)
|
|
||||||
if last_click_sound >= 0 and sound_index >= last_click_sound:
|
|
||||||
sound_index += 1
|
|
||||||
last_click_sound = sound_index
|
|
||||||
|
|
||||||
var player := audio_pool[next_audio_player]
|
|
||||||
next_audio_player = (next_audio_player + 1) % audio_pool.size()
|
|
||||||
player.stream = CLICK_SOUNDS[sound_index]
|
|
||||||
# The mechanism loses a little energy as it coasts to a stop. Small random
|
|
||||||
# variation keeps simultaneous reels from sounding cloned.
|
|
||||||
player.pitch_scale = lerpf(1.08, 0.91, spin_progress) + randf_range(
|
|
||||||
-pitch_variation,
|
|
||||||
pitch_variation
|
|
||||||
)
|
|
||||||
player.volume_db = click_volume_db + randf_range(-1.5, 0.8)
|
|
||||||
player.play(CLICK_START_OFFSETS[sound_index])
|
|
||||||
|
|
||||||
|
|
||||||
func _play_stop_click() -> void:
|
|
||||||
if audio_pool.is_empty():
|
|
||||||
return
|
|
||||||
|
|
||||||
var stop_pitch := 0.97 + randf_range(-pitch_variation * 0.5, pitch_variation * 0.5)
|
|
||||||
var player := audio_pool[next_audio_player]
|
|
||||||
next_audio_player = (next_audio_player + 1) % audio_pool.size()
|
|
||||||
player.stream = STOP_CLICK
|
|
||||||
player.pitch_scale = stop_pitch
|
|
||||||
# The source is substantially louder than the four running clicks, so tame it
|
|
||||||
# slightly while retaining a distinct, weighty reel-lock sound.
|
|
||||||
player.volume_db = click_volume_db - 2.0 + randf_range(-0.5, 0.5)
|
|
||||||
player.play(STOP_CLICK_START_OFFSET)
|
|
||||||
|
|
||||||
# Layer the quieter low flap under the detent for mechanical body. Matching
|
|
||||||
# pitch variation makes both recordings read as one physical impact.
|
|
||||||
var flap_player := audio_pool[next_audio_player]
|
|
||||||
next_audio_player = (next_audio_player + 1) % audio_pool.size()
|
|
||||||
flap_player.stream = STOP_FLAP
|
|
||||||
flap_player.pitch_scale = stop_pitch
|
|
||||||
flap_player.volume_db = click_volume_db + 4.0 + randf_range(-0.5, 0.5)
|
|
||||||
flap_player.play(STOP_FLAP_START_OFFSET)
|
|
||||||
|
|
||||||
|
|
||||||
func _play_spin_start() -> void:
|
|
||||||
if audio_pool.is_empty():
|
|
||||||
return
|
|
||||||
|
|
||||||
var player := audio_pool[next_audio_player]
|
|
||||||
next_audio_player = (next_audio_player + 1) % audio_pool.size()
|
|
||||||
player.stream = SPIN_START_THUMP
|
|
||||||
player.pitch_scale = 0.94 + randf_range(-pitch_variation * 0.35, pitch_variation * 0.35)
|
|
||||||
player.volume_db = click_volume_db + 2.0 + randf_range(-0.4, 0.4)
|
|
||||||
player.play()
|
|
||||||
|
|
||||||
|
|
||||||
func set_number(value: int, animate: bool = true) -> void:
|
|
||||||
var clamped_value := clampi(value, 0, 9999)
|
|
||||||
if not animate:
|
|
||||||
_cancel_number_animation()
|
|
||||||
number_value = clamped_value
|
|
||||||
_apply_number_immediately(number_value)
|
|
||||||
number_change_finished.emit(number_value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if clamped_value == number_value and not number_animation_active:
|
|
||||||
number_change_finished.emit(number_value)
|
|
||||||
return
|
|
||||||
|
|
||||||
var previous_target := number_value
|
|
||||||
number_value = clamped_value
|
|
||||||
_animate_number_to(number_value, previous_target, true)
|
|
||||||
|
|
||||||
|
|
||||||
func add_to_number(amount: int, animate: bool = true) -> void:
|
|
||||||
var previous_target := number_value
|
|
||||||
var new_value := clampi(number_value + amount, 0, 9999)
|
|
||||||
if not animate:
|
|
||||||
set_number(new_value, false)
|
|
||||||
return
|
|
||||||
|
|
||||||
if new_value == previous_target and not number_animation_active:
|
|
||||||
number_change_finished.emit(number_value)
|
|
||||||
return
|
|
||||||
|
|
||||||
number_value = new_value
|
|
||||||
_animate_number_to(number_value, previous_target, true)
|
|
||||||
|
|
||||||
|
|
||||||
func get_number() -> int:
|
|
||||||
return number_value
|
|
||||||
|
|
||||||
|
|
||||||
func is_number_animating() -> bool:
|
|
||||||
return number_animation_active
|
|
||||||
|
|
||||||
|
|
||||||
func test_counter_setting() -> void:
|
|
||||||
set_number(randi_range(0, 9999))
|
|
||||||
|
|
||||||
|
|
||||||
func go_to_doctor_scene() -> void:
|
|
||||||
continue_requested.emit()
|
|
||||||
|
|
||||||
|
|
||||||
func _animate_number_to(target_value: int, previous_target: int, is_addition: bool) -> void:
|
|
||||||
_cancel_number_animation()
|
|
||||||
if number_reels.is_empty():
|
|
||||||
number_change_finished.emit(number_value)
|
|
||||||
return
|
|
||||||
|
|
||||||
number_animation_active = true
|
|
||||||
var amount := target_value - previous_target
|
|
||||||
var direction := signi(amount)
|
|
||||||
var duration := number_roll_duration
|
|
||||||
if is_addition:
|
|
||||||
duration += minf(float(absi(amount)) / 1000.0, 1.0) * 0.35
|
|
||||||
|
|
||||||
for i in number_reels.size():
|
|
||||||
var reel := number_reels[i]
|
|
||||||
var start := float(reel.material.get_shader_parameter("y_offset"))
|
|
||||||
var target_digit := _digit_at(target_value, i)
|
|
||||||
var current_digit_position := start * 10.0
|
|
||||||
var current_digit_phase := fposmod(current_digit_position, 10.0)
|
|
||||||
var digit_steps: float
|
|
||||||
|
|
||||||
if is_addition and direction != 0:
|
|
||||||
var place := _place_for_index(i)
|
|
||||||
var transition_count := floori(float(target_value) / place)
|
|
||||||
transition_count -= floori(float(previous_target) / place)
|
|
||||||
var extra_turns := mini(floori(float(absi(transition_count)) / 10.0), 3)
|
|
||||||
if direction > 0:
|
|
||||||
digit_steps = fposmod(float(target_digit) - current_digit_phase, 10.0)
|
|
||||||
digit_steps += extra_turns * 10.0
|
|
||||||
else:
|
|
||||||
digit_steps = -fposmod(current_digit_phase - float(target_digit), 10.0)
|
|
||||||
digit_steps -= extra_turns * 10.0
|
|
||||||
else:
|
|
||||||
# This is only reached when retargeting to the same value from a
|
|
||||||
# fractional position; settle directly without choosing a spin direction.
|
|
||||||
digit_steps = fposmod(float(target_digit) - current_digit_phase + 5.0, 10.0) - 5.0
|
|
||||||
|
|
||||||
var target_offset := start + digit_steps / 10.0
|
|
||||||
var digit_tween := create_tween()
|
|
||||||
number_tweens.append(digit_tween)
|
|
||||||
digit_tween.tween_method(
|
|
||||||
func(offset: float) -> void:
|
|
||||||
reel.material.set_shader_parameter("y_offset", offset),
|
|
||||||
start,
|
|
||||||
target_offset,
|
|
||||||
duration
|
|
||||||
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
|
|
||||||
digit_tween.tween_callback(func() -> void:
|
|
||||||
reel.material.set_shader_parameter("y_offset", float(target_digit) / 10.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
var completion_tween := create_tween()
|
|
||||||
number_tweens.append(completion_tween)
|
|
||||||
completion_tween.tween_interval(duration)
|
|
||||||
completion_tween.tween_callback(func() -> void:
|
|
||||||
number_animation_active = false
|
|
||||||
number_tweens.clear()
|
|
||||||
number_change_finished.emit(number_value)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _cancel_number_animation() -> void:
|
|
||||||
for active_tween in number_tweens:
|
|
||||||
if active_tween and active_tween.is_valid():
|
|
||||||
active_tween.kill()
|
|
||||||
number_tweens.clear()
|
|
||||||
number_animation_active = false
|
|
||||||
|
|
||||||
|
|
||||||
func _apply_number_immediately(value: int) -> void:
|
|
||||||
for i in mini(number_reels.size(), 4):
|
|
||||||
var digit := _digit_at(value, i)
|
|
||||||
number_reels[i].material.set_shader_parameter("y_offset", float(digit) / 10.0)
|
|
||||||
|
|
||||||
|
|
||||||
func _digit_at(value: int, index: int) -> int:
|
|
||||||
return floori(float(value) / _place_for_index(index)) % 10
|
|
||||||
|
|
||||||
|
|
||||||
func _place_for_index(index: int) -> int:
|
|
||||||
return int(pow(10, 3 - index))
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://dv7jjfmghs0rg
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
const SLOT_SCENE := preload("res://scenes/ccs_cases_2/slot_machine.tscn")
|
|
||||||
const DOCTOR_SCENE := preload("res://scenes/ccs_cases_2/doctor_office.tscn")
|
const DOCTOR_SCENE := preload("res://scenes/ccs_cases_2/doctor_office.tscn")
|
||||||
const DESIGN_SIZE := Vector2(1500.0, 800.0)
|
const DESIGN_SIZE := Vector2(1500.0, 800.0)
|
||||||
|
|
||||||
@@ -21,19 +20,13 @@ func _ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func on_show(_payload: Variant = null) -> void:
|
func on_show(_payload: Variant = null) -> void:
|
||||||
_show_slot_machine()
|
_show_doctor_office()
|
||||||
|
|
||||||
|
|
||||||
func on_hide() -> void:
|
func on_hide() -> void:
|
||||||
_clear_active_scene()
|
_clear_active_scene()
|
||||||
|
|
||||||
|
|
||||||
func _show_slot_machine() -> void:
|
|
||||||
_set_active_scene(SLOT_SCENE.instantiate(), "slot_machine")
|
|
||||||
if active_scene.has_signal("continue_requested"):
|
|
||||||
active_scene.continue_requested.connect(_show_doctor_office)
|
|
||||||
|
|
||||||
|
|
||||||
func _show_doctor_office() -> void:
|
func _show_doctor_office() -> void:
|
||||||
_set_active_scene(DOCTOR_SCENE.instantiate(), "doctor_office")
|
_set_active_scene(DOCTOR_SCENE.instantiate(), "doctor_office")
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
shader_type canvas_item;
|
|
||||||
|
|
||||||
uniform sampler2D tex : repeat_enable;
|
|
||||||
uniform float n_rows = 6.0;
|
|
||||||
uniform float y_offset = 0.0;
|
|
||||||
uniform float x_offset = 0.0;
|
|
||||||
uniform float win_intensity : hint_range(0.0, 1.0) = 0.0;
|
|
||||||
uniform vec4 win_glow_color : source_color = vec4(1.0, 0.62, 0.08, 1.0);
|
|
||||||
|
|
||||||
void fragment() {
|
|
||||||
vec2 uv = UV;
|
|
||||||
uv.x += x_offset;
|
|
||||||
uv.y /= n_rows;
|
|
||||||
uv.y += y_offset;
|
|
||||||
|
|
||||||
vec4 base = texture(tex, uv);
|
|
||||||
if (win_intensity <= 0.001) {
|
|
||||||
COLOR = base;
|
|
||||||
} else {
|
|
||||||
// Derive a compact halo from the existing symbol alpha so no replacement
|
|
||||||
// sprite or second texture is needed.
|
|
||||||
vec2 pixel = 1.0 / vec2(textureSize(tex, 0));
|
|
||||||
vec2 radius = pixel * 5.0;
|
|
||||||
float nearby_alpha = 0.0;
|
|
||||||
nearby_alpha = max(nearby_alpha, texture(tex, uv + vec2(radius.x, 0.0)).a);
|
|
||||||
nearby_alpha = max(nearby_alpha, texture(tex, uv - vec2(radius.x, 0.0)).a);
|
|
||||||
nearby_alpha = max(nearby_alpha, texture(tex, uv + vec2(0.0, radius.y)).a);
|
|
||||||
nearby_alpha = max(nearby_alpha, texture(tex, uv - vec2(0.0, radius.y)).a);
|
|
||||||
nearby_alpha = max(nearby_alpha, texture(tex, uv + radius).a);
|
|
||||||
nearby_alpha = max(nearby_alpha, texture(tex, uv - radius).a);
|
|
||||||
nearby_alpha = max(nearby_alpha, texture(tex, uv + vec2(radius.x, -radius.y)).a);
|
|
||||||
nearby_alpha = max(nearby_alpha, texture(tex, uv + vec2(-radius.x, radius.y)).a);
|
|
||||||
|
|
||||||
float halo = max(nearby_alpha - base.a, 0.0) * win_intensity;
|
|
||||||
float flash = smoothstep(0.82, 1.0, win_intensity);
|
|
||||||
vec3 rich_color = min(base.rgb * 1.18 + win_glow_color.rgb * 0.12, vec3(1.0));
|
|
||||||
vec3 symbol_color = mix(base.rgb, rich_color, win_intensity);
|
|
||||||
symbol_color = mix(symbol_color, vec3(1.0), flash * 0.34 * base.a);
|
|
||||||
vec3 final_rgb = symbol_color * base.a + win_glow_color.rgb * halo;
|
|
||||||
COLOR = vec4(final_rgb, max(base.a, halo * 0.9));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://l6j3wkyf1ygq
|
|
||||||
@@ -28,12 +28,9 @@ func _run() -> void:
|
|||||||
await process_frame
|
await process_frame
|
||||||
screen.on_show()
|
screen.on_show()
|
||||||
await process_frame
|
await process_frame
|
||||||
_assert(screen.current_view == "slot_machine", "CCS Cases 2 should open on the slot machine")
|
_assert(screen.current_view == "doctor_office", "CCS Cases 2 should open directly in the doctor office")
|
||||||
_assert(screen.active_scene != null and screen.active_scene.has_signal("continue_requested"), "slot machine should expose an embedded continue signal")
|
_assert(screen.active_scene != null, "CCS Cases 2 should instantiate the doctor-office scene")
|
||||||
_assert(is_equal_approx(screen.stage_root.scale.x, screen.stage_root.scale.y), "the graphical stage should preserve its aspect ratio")
|
_assert(is_equal_approx(screen.stage_root.scale.x, screen.stage_root.scale.y), "the graphical stage should preserve its aspect ratio")
|
||||||
screen.active_scene.continue_requested.emit()
|
|
||||||
await process_frame
|
|
||||||
_assert(screen.current_view == "doctor_office", "Continue should open the doctor office without replacing the app scene")
|
|
||||||
screen.on_hide()
|
screen.on_hide()
|
||||||
await process_frame
|
await process_frame
|
||||||
_assert(screen.active_scene == null and screen.current_view.is_empty(), "hiding CCS Cases 2 should release the prototype scene")
|
_assert(screen.active_scene == null and screen.current_view.is_empty(), "hiding CCS Cases 2 should release the prototype scene")
|
||||||
|
|||||||
Reference in New Issue
Block a user