Open CCS Cases 2 in doctor scene

This commit is contained in:
hz
2026-08-05 18:53:00 -05:00
parent d76b2c809e
commit 31093ab80d
41 changed files with 3 additions and 1618 deletions
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
-319
View File
@@ -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"]
-770
View File
@@ -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 -8
View File
@@ -1,6 +1,5 @@
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 DESIGN_SIZE := Vector2(1500.0, 800.0)
@@ -21,19 +20,13 @@ func _ready() -> void:
func on_show(_payload: Variant = null) -> void:
_show_slot_machine()
_show_doctor_office()
func on_hide() -> void:
_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:
_set_active_scene(DOCTOR_SCENE.instantiate(), "doctor_office")
-42
View File
@@ -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
+2 -5
View File
@@ -28,12 +28,9 @@ func _run() -> void:
await process_frame
screen.on_show()
await process_frame
_assert(screen.current_view == "slot_machine", "CCS Cases 2 should open on the slot machine")
_assert(screen.active_scene != null and screen.active_scene.has_signal("continue_requested"), "slot machine should expose an embedded continue signal")
_assert(screen.current_view == "doctor_office", "CCS Cases 2 should open directly in the doctor office")
_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")
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()
await process_frame
_assert(screen.active_scene == null and screen.current_view.is_empty(), "hiding CCS Cases 2 should release the prototype scene")