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}'" diff --git a/scripts/Enemy.gd b/scripts/Enemy.gd index 3c8c93c..45f4cfb 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/GameSettings.gd b/scripts/GameSettings.gd new file mode 100644 index 0000000..da6d3a9 --- /dev/null +++ b/scripts/GameSettings.gd @@ -0,0 +1,11 @@ +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/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/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"]) diff --git a/scripts/Main.gd b/scripts/Main.gd index ba1afe1..d116e4a 100644 --- a/scripts/Main.gd +++ b/scripts/Main.gd @@ -177,7 +177,8 @@ func _on_stick_destroyed() -> void: return if sticks_on_field + sticks_pending < STICK_LIMIT: sticks_pending += 1 - await get_tree().create_timer(10.0).timeout + await get_tree().create_timer(GameSettings.item_respawn_delay).timeout + sticks_pending -= 1 if game_active: _spawn_single_stick() @@ -213,7 +214,7 @@ func _on_rock_destroyed() -> void: return if rocks_on_field + rocks_pending < _get_rock_limit(): rocks_pending += 1 - await get_tree().create_timer(10.0).timeout + await get_tree().create_timer(GameSettings.item_respawn_delay).timeout rocks_pending -= 1 if game_active: _spawn_single_rock() @@ -244,7 +245,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 +253,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 @@ -306,6 +307,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 +320,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() diff --git a/scripts/MainMenu.gd b/scripts/MainMenu.gd index 1c80668..5bb3fac 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,63 @@ func _build_ui() -> void: vbox.add_child(_btn("Выход", _on_exit)) _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 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 +152,89 @@ 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 = -310 + difficulty_panel.offset_right = 310 + difficulty_panel.offset_top = -240 + difficulty_panel.offset_bottom = 240 + 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) + + 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(_big_btn("Выживание\nПолучаешь урон · фиксированная сила пинка", 540, func() -> void: + GameSettings.difficulty = "survival" + get_tree().change_scene_to_file("res://scenes/Main.tscn") + )) + + # 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)) + 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 5590dec..37087b4 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) @@ -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