From 20eaa513c3546a6ccb6297233ea0f9e5e977c167 Mon Sep 17 00:00:00 2001 From: Nikolai Fedorov Date: Thu, 23 Apr 2026 15:42:14 +0300 Subject: [PATCH 1/6] fix stick and rock spawn --- scripts/KickSystem.gd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/KickSystem.gd b/scripts/KickSystem.gd index 90ca8c4..8196957 100644 --- a/scripts/KickSystem.gd +++ b/scripts/KickSystem.gd @@ -52,6 +52,11 @@ static func resolve(owner: Node3D, other: Node3D, owner_vel: Vector3) -> bool: static func _execute_recipe(a: Node3D, b: Node3D, recipe: Dictionary) -> void: var pos := (a.global_position + b.global_position) * 0.5 var parent := a.get_parent() + # Emit before freeing so respawn counters in Main decrement correctly. + if a.has_signal("destroyed"): + a.emit_signal("destroyed") + if b.has_signal("destroyed"): + b.emit_signal("destroyed") a.queue_free() b.queue_free() var scene: PackedScene = load(recipe["result_scene"]) From f37c27887e5e5e132e6e542062d73fa3e6727c82 Mon Sep 17 00:00:00 2001 From: Nikolai Fedorov Date: Thu, 23 Apr 2026 15:52:36 +0300 Subject: [PATCH 2/6] upgrade mesh merge --- scripts/Enemy.gd | 16 ++++++++++++++++ scripts/Main.gd | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/scripts/Enemy.gd b/scripts/Enemy.gd index 4222aae..20d9f31 100644 --- a/scripts/Enemy.gd +++ b/scripts/Enemy.gd @@ -120,6 +120,22 @@ func setup(type: String, wave: int) -> void: kick_tier = enemy_level toughness_tier = enemy_level _update_label() + _apply_mesh(enemy_level) + +func _apply_mesh(level: int) -> void: + var idx := clampi(level, 1, 3) + var mesh_res := load("res://assets/gnome%d.obj" % idx) as Mesh + if mesh_res == null: + return + var new_mat := StandardMaterial3D.new() + var tex := load("res://assets/gnome%d.png" % idx) as Texture2D + if tex != null: + new_mat.albedo_texture = tex + new_mat.albedo_color = Color.WHITE + mesh_node.mesh = mesh_res + mesh_node.material_override = new_mat + mat = new_mat + COLOR_CHASE = Color.WHITE func _physics_process(delta: float) -> void: match state: diff --git a/scripts/Main.gd b/scripts/Main.gd index 3a46cac..52da3d3 100644 --- a/scripts/Main.gd +++ b/scripts/Main.gd @@ -306,6 +306,7 @@ func _spawn_upgraded_enemy(pos: Vector3, type: String, level: int, w: int) -> Ch enemy.kick_tier = level enemy.toughness_tier = level enemy.call("_update_label") + enemy.call("_apply_mesh", level) enemy.global_position = pos enemy.connect("died", _on_enemy_died) enemy.connect("merged", _on_enemy_merged) @@ -318,8 +319,6 @@ func _spawn_upgraded_enemy(pos: Vector3, type: String, level: int, w: int) -> Ch var old_size: Vector3 = s3d.size col_shape.shape = BoxShape3D.new() (col_shape.shape as BoxShape3D).size = old_size * s - var color := Color(1.0, 1.0, 0.5) if level > 2 else Color(1.0, 0.9, 0.3) - tw.tween_property(enemy.mat, "albedo_color", color, 0.25) if level >= 3 and not first_boss_spawned: first_boss_spawned = true _start_boss_phase() From 3542c328c01fe4d5a734cbef800f9e8dbc81ddb2 Mon Sep 17 00:00:00 2001 From: Nikolay Fedorov Date: Thu, 23 Apr 2026 15:59:14 +0300 Subject: [PATCH 3/6] fix export shield tutor --- assets/Tutorial_shield.jpeg.import | 6 +-- export_presets.cfg | 71 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 export_presets.cfg diff --git a/assets/Tutorial_shield.jpeg.import b/assets/Tutorial_shield.jpeg.import index 0434bc8..2721869 100644 --- a/assets/Tutorial_shield.jpeg.import +++ b/assets/Tutorial_shield.jpeg.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://baeea1yfs0cnn" -path="res://.godot/imported/Tutorial_shield.jpeg-d9b563c9ca34dc1a19e82a0ec964c1b9.ctex" +path="res://.godot/imported/Tutorial_Shield.jpeg-6b96e6b9716aced42153397e827ce868.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://assets/Tutorial_shield.jpeg" -dest_files=["res://.godot/imported/Tutorial_shield.jpeg-d9b563c9ca34dc1a19e82a0ec964c1b9.ctex"] +source_file="res://assets/Tutorial_Shield.jpeg" +dest_files=["res://.godot/imported/Tutorial_Shield.jpeg-6b96e6b9716aced42153397e827ce868.ctex"] [params] diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 0000000..a9fdb53 --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,71 @@ +[preset.0] + +name="Windows Desktop" +platform="Windows Desktop" +runnable=true +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="*jpeg" +exclude_filter="" +export_path="../Kick/KickSurvivors.exe" +patches=PackedStringArray() +patch_delta_encoding=false +patch_delta_compression_level_zstd=19 +patch_delta_min_reduction=0.1 +patch_delta_include_filters="*" +patch_delta_exclude_filters="" +encryption_include_filters="" +encryption_exclude_filters="" +seed=0 +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false +shader_baker/enabled=false +binary_format/architecture="x86_64" +codesign/enable=false +codesign/timestamp=true +codesign/timestamp_server_url="" +codesign/digest_algorithm=1 +codesign/description="" +codesign/custom_options=PackedStringArray() +application/modify_resources=true +application/icon="" +application/console_wrapper_icon="" +application/icon_interpolation=4 +application/file_version="" +application/product_version="" +application/company_name="" +application/product_name="" +application/file_description="" +application/copyright="" +application/trademarks="" +application/export_angle=0 +application/export_d3d12=0 +application/d3d12_agility_sdk_multiarch=true +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}' +$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}' +$trigger = New-ScheduledTaskTrigger -Once -At 00:00 +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries +$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings +Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true +Start-ScheduledTask -TaskName godot_remote_debug +while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 } +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" +ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue +Remove-Item -Recurse -Force '{temp_dir}'" From 9acbc49084a6aaaf1c189fdd4a1db9490d014247 Mon Sep 17 00:00:00 2001 From: Nikolai Fedorov Date: Thu, 23 Apr 2026 16:05:25 +0300 Subject: [PATCH 4/6] difficulity --- assets/Tutorial_shield.jpeg.import | 6 +- scripts/GameSettings.gd | 5 ++ scripts/GameSettings.gd.uid | 1 + scripts/MainMenu.gd | 99 +++++++++++++++++++++++++++++- scripts/Player.gd | 6 +- 5 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 scripts/GameSettings.gd create mode 100644 scripts/GameSettings.gd.uid diff --git a/assets/Tutorial_shield.jpeg.import b/assets/Tutorial_shield.jpeg.import index 2721869..0434bc8 100644 --- a/assets/Tutorial_shield.jpeg.import +++ b/assets/Tutorial_shield.jpeg.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://baeea1yfs0cnn" -path="res://.godot/imported/Tutorial_Shield.jpeg-6b96e6b9716aced42153397e827ce868.ctex" +path="res://.godot/imported/Tutorial_shield.jpeg-d9b563c9ca34dc1a19e82a0ec964c1b9.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://assets/Tutorial_Shield.jpeg" -dest_files=["res://.godot/imported/Tutorial_Shield.jpeg-6b96e6b9716aced42153397e827ce868.ctex"] +source_file="res://assets/Tutorial_shield.jpeg" +dest_files=["res://.godot/imported/Tutorial_shield.jpeg-d9b563c9ca34dc1a19e82a0ec964c1b9.ctex"] [params] diff --git a/scripts/GameSettings.gd b/scripts/GameSettings.gd new file mode 100644 index 0000000..7cdbd57 --- /dev/null +++ b/scripts/GameSettings.gd @@ -0,0 +1,5 @@ +class_name GameSettings + +## "immortal" — no damage, tier-based kick force +## "survival" — takes damage, fixed kick force +static var difficulty: String = "immortal" diff --git a/scripts/GameSettings.gd.uid b/scripts/GameSettings.gd.uid new file mode 100644 index 0000000..d8c41b2 --- /dev/null +++ b/scripts/GameSettings.gd.uid @@ -0,0 +1 @@ +uid://deu62njwuhjd3 diff --git a/scripts/MainMenu.gd b/scripts/MainMenu.gd index 1c80668..56a450b 100644 --- a/scripts/MainMenu.gd +++ b/scripts/MainMenu.gd @@ -3,6 +3,7 @@ extends Control static var volume: float = 100.0 var settings_panel: Panel +var difficulty_panel: Panel func _ready() -> void: process_mode = Node.PROCESS_MODE_ALWAYS @@ -46,17 +47,57 @@ func _build_ui() -> void: vbox.add_child(_btn("Выход", _on_exit)) _build_settings_panel() + _build_difficulty_panel() func _btn(text: String, cb: Callable) -> Button: var b := Button.new() b.text = text b.custom_minimum_size = Vector2(220, 54) b.add_theme_font_size_override("font_size", 20) + for state in ["normal", "hover", "pressed", "focus", "disabled"]: + var sb := StyleBoxFlat.new() + sb.bg_color = Color(0.18, 0.13, 0.30) if state == "normal" else ( + Color(0.28, 0.20, 0.46) if state == "hover" else + Color(0.12, 0.09, 0.22)) + sb.border_width_left = 1 + sb.border_width_right = 1 + sb.border_width_top = 1 + sb.border_width_bottom = 1 + sb.border_color = Color(0.50, 0.38, 0.75) + sb.corner_radius_top_left = 6 + sb.corner_radius_top_right = 6 + sb.corner_radius_bottom_left = 6 + sb.corner_radius_bottom_right = 6 + sb.content_margin_left = 20 + sb.content_margin_right = 20 + sb.content_margin_top = 12 + sb.content_margin_bottom = 12 + b.add_theme_stylebox_override(state, sb) b.connect("pressed", cb) return b +static func _make_opaque_panel() -> Panel: + var p := Panel.new() + var sb := StyleBoxFlat.new() + sb.bg_color = Color(0.08, 0.06, 0.14) + sb.border_width_left = 2 + sb.border_width_right = 2 + sb.border_width_top = 2 + sb.border_width_bottom = 2 + sb.border_color = Color(0.35, 0.28, 0.55) + sb.corner_radius_top_left = 8 + sb.corner_radius_top_right = 8 + sb.corner_radius_bottom_left = 8 + sb.corner_radius_bottom_right = 8 + sb.content_margin_left = 24 + sb.content_margin_right = 24 + sb.content_margin_top = 20 + sb.content_margin_bottom = 20 + p.add_theme_stylebox_override("panel", sb) + return p + func _build_settings_panel() -> void: - settings_panel = Panel.new() + settings_panel = _make_opaque_panel() settings_panel.visible = false settings_panel.anchor_left = 0.5 settings_panel.anchor_right = 0.5 @@ -105,8 +146,62 @@ func _apply_volume(v: float) -> void: var db := linear_to_db(v / 100.0) if v > 0.0 else -80.0 AudioServer.set_bus_volume_db(0, db) +func _build_difficulty_panel() -> void: + difficulty_panel = _make_opaque_panel() + difficulty_panel.visible = false + difficulty_panel.anchor_left = 0.5 + difficulty_panel.anchor_right = 0.5 + difficulty_panel.anchor_top = 0.5 + difficulty_panel.anchor_bottom = 0.5 + difficulty_panel.offset_left = -230 + difficulty_panel.offset_right = 230 + difficulty_panel.offset_top = -160 + difficulty_panel.offset_bottom = 160 + add_child(difficulty_panel) + + var margin := MarginContainer.new() + margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + margin.add_theme_constant_override("margin_left", 24) + margin.add_theme_constant_override("margin_right", 24) + margin.add_theme_constant_override("margin_top", 20) + margin.add_theme_constant_override("margin_bottom", 20) + difficulty_panel.add_child(margin) + + var vbox := VBoxContainer.new() + vbox.alignment = BoxContainer.ALIGNMENT_CENTER + vbox.add_theme_constant_override("separation", 16) + margin.add_child(vbox) + + var title := Label.new() + title.text = "Выберите сложность" + title.add_theme_font_size_override("font_size", 26) + title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + vbox.add_child(title) + + var immortal_btn := Button.new() + immortal_btn.text = "Бессмертный\nНет урона · тир пинков от мелких врагов" + immortal_btn.custom_minimum_size = Vector2(380, 72) + immortal_btn.add_theme_font_size_override("font_size", 18) + immortal_btn.connect("pressed", func() -> void: + GameSettings.difficulty = "immortal" + get_tree().change_scene_to_file("res://scenes/Main.tscn") + ) + vbox.add_child(immortal_btn) + + var survival_btn := Button.new() + survival_btn.text = "Выживание\nПолучаешь урон · фиксированная сила пинка" + survival_btn.custom_minimum_size = Vector2(380, 72) + survival_btn.add_theme_font_size_override("font_size", 18) + survival_btn.connect("pressed", func() -> void: + GameSettings.difficulty = "survival" + get_tree().change_scene_to_file("res://scenes/Main.tscn") + ) + vbox.add_child(survival_btn) + + vbox.add_child(_btn("Назад", func(): difficulty_panel.visible = false)) + func _on_play() -> void: - get_tree().change_scene_to_file("res://scenes/Main.tscn") + difficulty_panel.visible = true func _on_settings() -> void: settings_panel.visible = true diff --git a/scripts/Player.gd b/scripts/Player.gd index a1e3d59..a0b3ca5 100644 --- a/scripts/Player.gd +++ b/scripts/Player.gd @@ -180,7 +180,9 @@ func _do_kick() -> void: var obj_toughness: int = best.get("toughness_tier") if best.get("toughness_tier") != null else 0 var diff_tier := kick_tier - obj_toughness var force: float - if diff_tier < 0: + if GameSettings.difficulty == "survival": + force = 50.0 + elif diff_tier < 0: force = 15.0 elif diff_tier == 0: force = 50.0 @@ -229,6 +231,8 @@ func receive_kick(direction: Vector3, force: float) -> void: _squish_effect() func take_damage(amount: int, attacker_toughness: int = 0) -> void: + if GameSettings.difficulty == "immortal": + return if not is_alive or invincible_timer > 0.0: return invincible_timer = IFRAMES_DURATION From 3c2afefc95caf9a908d23a38dfbff10ec521ef5d Mon Sep 17 00:00:00 2001 From: Nikolai Fedorov Date: Thu, 23 Apr 2026 16:17:40 +0300 Subject: [PATCH 5/6] spawn settings --- scripts/GameSettings.gd | 6 ++++ scripts/Main.gd | 8 ++--- scripts/MainMenu.gd | 69 ++++++++++++++++++++++++++++++----------- 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/scripts/GameSettings.gd b/scripts/GameSettings.gd index 7cdbd57..da6d3a9 100644 --- a/scripts/GameSettings.gd +++ b/scripts/GameSettings.gd @@ -3,3 +3,9 @@ class_name GameSettings ## "immortal" — no damage, tier-based kick force ## "survival" — takes damage, fixed kick force static var difficulty: String = "immortal" + +## enemy spawn interval in seconds +static var enemy_spawn_interval: float = 10.0 + +## item (rock/stick) respawn delay in seconds +static var item_respawn_delay: float = 20.0 diff --git a/scripts/Main.gd b/scripts/Main.gd index 52da3d3..1080478 100644 --- a/scripts/Main.gd +++ b/scripts/Main.gd @@ -177,7 +177,7 @@ func _on_stick_destroyed() -> void: return if sticks_on_field + sticks_pending < STICK_LIMIT: sticks_pending += 1 - await get_tree().create_timer(20.0).timeout + await get_tree().create_timer(GameSettings.item_respawn_delay).timeout sticks_pending -= 1 if game_active: _spawn_single_stick() @@ -213,7 +213,7 @@ func _on_rock_destroyed() -> void: return if rocks_on_field + rocks_pending < _get_rock_limit(): rocks_pending += 1 - await get_tree().create_timer(20.0).timeout + await get_tree().create_timer(GameSettings.item_respawn_delay).timeout rocks_pending -= 1 if game_active: _spawn_single_rock() @@ -244,7 +244,7 @@ func _start_game() -> void: Enemy.first_iron_spawned = false Enemy.first_essence_spawned = false _update_labels() - spawn_timer.wait_time = 1.4 + spawn_timer.wait_time = GameSettings.enemy_spawn_interval spawn_timer.connect("timeout", _on_spawn_timer) spawn_timer.start() @@ -252,7 +252,7 @@ func _on_spawn_timer() -> void: if not game_active or upgrading: return _spawn_enemy() - spawn_timer.wait_time = SPAWN_TIME # max(0.25, 1.4 - wave * 0.07) + spawn_timer.wait_time = GameSettings.enemy_spawn_interval func _spawn_enemy() -> void: var enemy := ENEMY_SCENE.instantiate() as CharacterBody3D diff --git a/scripts/MainMenu.gd b/scripts/MainMenu.gd index 56a450b..5bb3fac 100644 --- a/scripts/MainMenu.gd +++ b/scripts/MainMenu.gd @@ -49,6 +49,12 @@ func _build_ui() -> void: _build_settings_panel() _build_difficulty_panel() +func _big_btn(text: String, width: float, cb: Callable) -> Button: + var b := _btn(text, cb) + b.custom_minimum_size = Vector2(width, 72) + b.add_theme_font_size_override("font_size", 18) + return b + func _btn(text: String, cb: Callable) -> Button: var b := Button.new() b.text = text @@ -153,10 +159,10 @@ func _build_difficulty_panel() -> void: difficulty_panel.anchor_right = 0.5 difficulty_panel.anchor_top = 0.5 difficulty_panel.anchor_bottom = 0.5 - difficulty_panel.offset_left = -230 - difficulty_panel.offset_right = 230 - difficulty_panel.offset_top = -160 - difficulty_panel.offset_bottom = 160 + difficulty_panel.offset_left = -310 + difficulty_panel.offset_right = 310 + difficulty_panel.offset_top = -240 + difficulty_panel.offset_bottom = 240 add_child(difficulty_panel) var margin := MarginContainer.new() @@ -178,25 +184,52 @@ func _build_difficulty_panel() -> void: title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER vbox.add_child(title) - var immortal_btn := Button.new() - immortal_btn.text = "Бессмертный\nНет урона · тир пинков от мелких врагов" - immortal_btn.custom_minimum_size = Vector2(380, 72) - immortal_btn.add_theme_font_size_override("font_size", 18) - immortal_btn.connect("pressed", func() -> void: + vbox.add_child(_big_btn("Бессмертный\nНет урона · тир пинков от мелких врагов", 540, func() -> void: GameSettings.difficulty = "immortal" get_tree().change_scene_to_file("res://scenes/Main.tscn") - ) - vbox.add_child(immortal_btn) + )) - var survival_btn := Button.new() - survival_btn.text = "Выживание\nПолучаешь урон · фиксированная сила пинка" - survival_btn.custom_minimum_size = Vector2(380, 72) - survival_btn.add_theme_font_size_override("font_size", 18) - survival_btn.connect("pressed", func() -> void: + vbox.add_child(_big_btn("Выживание\nПолучаешь урон · фиксированная сила пинка", 540, func() -> void: GameSettings.difficulty = "survival" get_tree().change_scene_to_file("res://scenes/Main.tscn") - ) - vbox.add_child(survival_btn) + )) + + # Spawn speed row + var spawn_lbl := Label.new() + spawn_lbl.text = "Скорость спавна:" + spawn_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + spawn_lbl.add_theme_font_size_override("font_size", 16) + spawn_lbl.add_theme_color_override("font_color", Color(0.85, 0.78, 1.0)) + vbox.add_child(spawn_lbl) + + var hbox := HBoxContainer.new() + hbox.alignment = BoxContainer.ALIGNMENT_CENTER + hbox.add_theme_constant_override("separation", 10) + vbox.add_child(hbox) + + var spawn_options := [ + ["Медленно", 15.0, 35.0], + ["Нормально", 10.0, 20.0], + ["Быстро", 5.0, 8.0], + ] + var spawn_btns: Array[Button] = [] + for opt in spawn_options: + var sb_btn := _btn(opt[0] as String, Callable()) + sb_btn.custom_minimum_size = Vector2(110, 40) + sb_btn.add_theme_font_size_override("font_size", 15) + var enemy_iv: float = opt[1] + var item_iv: float = opt[2] + sb_btn.connect("pressed", func() -> void: + GameSettings.enemy_spawn_interval = enemy_iv + GameSettings.item_respawn_delay = item_iv + for b in spawn_btns: + b.modulate = Color(1, 1, 1) + sb_btn.modulate = Color(0.7, 1.0, 0.5) + ) + hbox.add_child(sb_btn) + spawn_btns.append(sb_btn) + # default: normal + spawn_btns[1].modulate = Color(0.7, 1.0, 0.5) vbox.add_child(_btn("Назад", func(): difficulty_panel.visible = false)) From 7d6d96e499d13f257c54a9d72c6b3e28fcbe8f4f Mon Sep 17 00:00:00 2001 From: Nikolai Fedorov Date: Thu, 23 Apr 2026 16:18:35 +0300 Subject: [PATCH 6/6] shield on spacebar --- scripts/Player.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Player.gd b/scripts/Player.gd index a0b3ca5..413a820 100644 --- a/scripts/Player.gd +++ b/scripts/Player.gd @@ -85,7 +85,7 @@ func _input(event: InputEvent) -> void: func _physics_process(delta: float) -> void: if not is_alive: return - is_shielding = shield_tier > 0 and Input.is_key_pressed(KEY_SHIFT) + is_shielding = shield_tier > 0 and Input.is_key_pressed(KEY_SPACE) _handle_movement(delta) _handle_kick(delta) _handle_iframes(delta)