diff --git a/scenes/Iron.tscn b/scenes/Iron.tscn new file mode 100644 index 0000000..33965f6 --- /dev/null +++ b/scenes/Iron.tscn @@ -0,0 +1,27 @@ +[gd_scene format=3 uid="uid://g7iron5etp8u"] + +[ext_resource type="Script" uid="uid://0wdmbocpe2ir" path="res://scripts/Iron.gd" id="1_iron"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"] +albedo_color = Color(0.55, 0.58, 0.62, 1) +metallic = 0.7 +metallic_specular = 0.8 +roughness = 0.4 + +[sub_resource type="BoxMesh" id="BoxMesh_1"] +size = Vector3(0.38, 0.22, 0.5) + +[sub_resource type="BoxShape3D" id="BoxShape3D_1"] +size = Vector3(0.38, 0.22, 0.5) + +[node name="Iron" type="CharacterBody3D" unique_id=611454609] +script = ExtResource("1_iron") + +[node name="IronMesh" type="MeshInstance3D" parent="." unique_id=1832879739] +transform = Transform3D(3.5611715, 0, 0, 0, 2.7614622, 0, 0, 0, 1, 0, 0.2, 0) +material_override = SubResource("StandardMaterial3D_1") +mesh = SubResource("BoxMesh_1") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=665808333] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.2, 0) +shape = SubResource("BoxShape3D_1") diff --git a/scenes/Level.tscn b/scenes/Level.tscn index c8ec1da..39a8ff3 100644 --- a/scenes/Level.tscn +++ b/scenes/Level.tscn @@ -62,6 +62,22 @@ radius = 1.0966797 [sub_resource type="BoxShape3D" id="BoxShape3D_qifjx"] size = Vector3(2.2670898, 1, 3.7305298) +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_forge"] +albedo_color = Color(0.28, 0.24, 0.2, 1) +roughness = 0.95 + +[sub_resource type="BoxMesh" id="BoxMesh_forge"] +size = Vector3(2, 1.8, 2) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_chimney"] +albedo_color = Color(0.2, 0.18, 0.16, 1) + +[sub_resource type="BoxMesh" id="BoxMesh_chimney"] +size = Vector3(0.6, 1, 0.6) + +[sub_resource type="BoxShape3D" id="BoxShape3D_forge"] +size = Vector3(2, 1.8, 2) + [node name="Level" type="Node3D" unique_id=696519] script = ExtResource("1_ppgk2") show_grid = false @@ -210,3 +226,19 @@ shape = SubResource("CylinderShape3D_6vs76") [node name="CollisionShape3D3" type="CollisionShape3D" parent="stone_big_static_body" unique_id=1397239926] transform = Transform3D(1.9385185, 0, -0.69275445, 0, 2.0585828, 0, 0.69275445, 0, 1.9385185, 60.670692, 1.6238549, 36.420376) shape = SubResource("CylinderShape3D_6vs76") + +[node name="Forge" type="StaticBody3D" parent="." unique_id=734179063] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0.9, -8) +metadata/is_forge = true + +[node name="ForgeMesh" type="MeshInstance3D" parent="Forge" unique_id=1062255408] +material_override = SubResource("StandardMaterial3D_forge") +mesh = SubResource("BoxMesh_forge") + +[node name="ChimneyMesh" type="MeshInstance3D" parent="Forge" unique_id=1649367044] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 1.4, -0.4) +material_override = SubResource("StandardMaterial3D_chimney") +mesh = SubResource("BoxMesh_chimney") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Forge" unique_id=726531191] +shape = SubResource("BoxShape3D_forge") diff --git a/scenes/MetalArmor.tscn b/scenes/MetalArmor.tscn new file mode 100644 index 0000000..e099a27 --- /dev/null +++ b/scenes/MetalArmor.tscn @@ -0,0 +1,28 @@ +[gd_scene format=3 uid="uid://h8armor4fura"] + +[ext_resource type="Script" path="res://scripts/MetalArmor.gd" id="1_armor"] + +[sub_resource type="BoxMesh" id="BoxMesh_1"] +size = Vector3(0.55, 0.7, 0.25) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"] +albedo_color = Color(0.55, 0.60, 0.68, 1) +roughness = 0.3 +metallic = 0.85 +metallic_specular = 1.0 + +[node name="MetalArmor" type="Node3D"] +script = ExtResource("1_armor") + +[node name="ArmorMesh" type="MeshInstance3D" parent="."] +mesh = SubResource("BoxMesh_1") +material_override = SubResource("StandardMaterial3D_1") + +[node name="Tooltip" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0, 0) +billboard = 1 +double_sided = true +text = "[E] Metal Armor ++1 Tier" +font_size = 32 +outline_size = 6 diff --git a/scripts/Enemy.gd b/scripts/Enemy.gd index a533a41..067324a 100644 --- a/scripts/Enemy.gd +++ b/scripts/Enemy.gd @@ -3,6 +3,7 @@ extends CharacterBody3D const PICKUP_SCENE := preload("res://scenes/Pickup.tscn") const LEATHER_SCENE := preload("res://scenes/Leather.tscn") +const IRON_SCENE := preload("res://scenes/Iron.tscn") signal died(points: int) signal merged(upgrade: bool) @@ -10,6 +11,7 @@ signal merged(upgrade: bool) enum State { CHASING, FLYING, STUNNED, DEAD, MERGING } static var first_leather_spawned: bool = false +static var first_iron_spawned: bool = false var kickable_type: String = "" var tier: int = 1 @@ -54,7 +56,6 @@ func setup(type: String, wave: int) -> void: enemy_type = type kickable_type = type wave_num = wave - tier = enemy_level match type: "slime": move_speed = 2.8 + wave * 0.12 @@ -82,6 +83,7 @@ func setup(type: String, wave: int) -> void: COLOR_CHASE = Color(0.3, 0.7, 0.3) mat.albedo_color = COLOR_CHASE enemy_level = 3 + tier = enemy_level func _physics_process(delta: float) -> void: match state: @@ -267,6 +269,14 @@ func _try_drop_pickup() -> void: get_parent().add_child(leather) leather.global_position = global_position + if enemy_level == 2: + var drop_iron := not first_iron_spawned or randf() < 0.20 + if drop_iron: + first_iron_spawned = true + var iron := IRON_SCENE.instantiate() as Node3D + get_parent().add_child(iron) + iron.global_position = global_position + var roll := randf() var p_type := "" var p_heal := 0 diff --git a/scripts/Iron.gd b/scripts/Iron.gd new file mode 100644 index 0000000..29477ab --- /dev/null +++ b/scripts/Iron.gd @@ -0,0 +1,134 @@ +extends CharacterBody3D + +const METAL_ARMOR_SCENE := preload("res://scenes/MetalArmor.tscn") + +signal destroyed + +enum State { IDLE, FLYING } + +const AIR_FRICTION := 0.86 +const MIN_SPEED := 0.5 +const WALL_BOUNCE := 0.45 +const WALL_SELF_DMG := 0.5 + +var kickable_type: String = "iron" +var tier: int = 2 +var state: State = State.IDLE +var fly_vel: Vector3 = Vector3.ZERO +var health: float = 80.0 +var dead: bool = false +var damage_modifier: float = 0.9 + +@onready var mesh_node: MeshInstance3D = $IronMesh +var iron_mat: StandardMaterial3D + +const COLOR_IDLE := Color(0.55, 0.58, 0.62) +const COLOR_IMPACT := Color(1.0, 1.0, 1.0) + +func _ready() -> void: + add_to_group("kickable") + iron_mat = mesh_node.material_override.duplicate() as StandardMaterial3D + mesh_node.material_override = iron_mat + +func apply_collision_damage(dmg: float) -> void: + _take_damage(dmg) + +func receive_kick(direction: Vector3, force: float) -> void: + fly_vel = direction * force + fly_vel.y = 0.0 + state = State.FLYING + +func _physics_process(delta: float) -> void: + if state == State.IDLE: + return + _fly(delta) + +func _fly(delta: float) -> void: + var speed_now := Vector2(fly_vel.x, fly_vel.z).length() + velocity = fly_vel + velocity.y = 0.0 + move_and_slide() + + var handled := false + for i in get_slide_collision_count(): + var col := get_slide_collision(i) + var col3d := col.get_collider() as Node3D + if col3d == null: + continue + if col3d.has_meta("is_forge"): + _hit_forge(col3d) + return + if col3d.has_meta("is_wall"): + var normal := col.get_normal() + normal.y = 0.0 + if normal.length() > 0.01: + fly_vel = fly_vel.bounce(normal.normalized()) * WALL_BOUNCE + else: + fly_vel = Vector3.ZERO + _take_damage(speed_now * WALL_SELF_DMG) + handled = true + break + elif col3d.is_in_group("enemies") or col3d.is_in_group("kickable"): + if col3d == self: + continue + KickSystem.resolve(self, col3d, fly_vel) + if not dead and is_instance_valid(col3d): + var kick_dir := col3d.global_position - global_position + kick_dir.y = 0.0 + if kick_dir.length() > 0.01: + col3d.call("receive_kick", kick_dir.normalized(), speed_now * 0.65) + fly_vel *= 0.45 + handled = true + break + + if not handled: + fly_vel = velocity + fly_vel.y = 0.0 + + fly_vel *= pow(AIR_FRICTION, delta * 60.0) + + if Vector2(fly_vel.x, fly_vel.z).length() < MIN_SPEED: + fly_vel = Vector3.ZERO + velocity = Vector3.ZERO + state = State.IDLE + + mesh_node.rotation.y += delta * speed_now * 0.3 + +func _hit_forge(forge: Node3D) -> void: + if dead: + return + dead = true + state = State.IDLE + set_physics_process(false) + var parent := get_parent() + var spawn_pos := forge.global_position + Vector3(5, 0, 0) + queue_free() + if parent == null: + return + var armor := METAL_ARMOR_SCENE.instantiate() as Node3D + parent.add_child(armor) + armor.global_position = spawn_pos + +func _take_damage(dmg: float) -> void: + if dead: + return + health -= dmg + _flash() + if health <= 0.0: + _die() + +func _die() -> void: + dead = true + state = State.IDLE + set_physics_process(false) + emit_signal("destroyed") + var tw := create_tween() + tw.tween_property(self, "scale", Vector3(1.6, 0.1, 1.6), 0.12) + tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.1) + tw.tween_callback(queue_free) + +func _flash() -> void: + var tw := create_tween() + tw.tween_property(iron_mat, "albedo_color", COLOR_IMPACT, 0.04) + var target_color := COLOR_IDLE.lerp(Color.RED, clampf(1.0 - health / 80.0, 0.0, 0.6)) + tw.tween_property(iron_mat, "albedo_color", target_color, 0.18) diff --git a/scripts/Iron.gd.uid b/scripts/Iron.gd.uid new file mode 100644 index 0000000..c899564 --- /dev/null +++ b/scripts/Iron.gd.uid @@ -0,0 +1 @@ +uid://0wdmbocpe2ir diff --git a/scripts/Main.gd b/scripts/Main.gd index 481e34a..acb912c 100644 --- a/scripts/Main.gd +++ b/scripts/Main.gd @@ -171,6 +171,7 @@ func _start_game() -> void: kills = 0 kills_for_next = 10 Enemy.first_leather_spawned = false + Enemy.first_iron_spawned = false _update_labels() spawn_timer.wait_time = 1.4 spawn_timer.connect("timeout", _on_spawn_timer) diff --git a/scripts/MetalArmor.gd b/scripts/MetalArmor.gd new file mode 100644 index 0000000..fa5c49d --- /dev/null +++ b/scripts/MetalArmor.gd @@ -0,0 +1,23 @@ +extends Node3D + +@onready var tooltip: Label3D = $Tooltip + +func _ready() -> void: + add_to_group("interactable") + tooltip.visible = false + var tw := create_tween().set_loops() + tw.tween_property(self, "position:y", 0.5, 0.7) + tw.tween_property(self, "position:y", 0.2, 0.7) + +func _process(delta: float) -> void: + rotation.y += delta * 1.4 + var players := get_tree().get_nodes_in_group("player") + if players.is_empty(): + tooltip.visible = false + return + var p := players[0] as Node3D + tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5 + +func interact(player: Node) -> void: + player.call("apply_upgrade_armor") + queue_free() diff --git a/scripts/MetalArmor.gd.uid b/scripts/MetalArmor.gd.uid new file mode 100644 index 0000000..af7061e --- /dev/null +++ b/scripts/MetalArmor.gd.uid @@ -0,0 +1 @@ +uid://cqp1ucyvnno7i diff --git a/scripts/Player.gd b/scripts/Player.gd index 478df9d..e82133d 100644 --- a/scripts/Player.gd +++ b/scripts/Player.gd @@ -235,3 +235,9 @@ func apply_upgrade_boots(speed_bonus: float, _tier: int) -> void: var tw := create_tween() tw.tween_property(player_mat, "albedo_color", Color(1.0, 0.85, 0.2), 0.1) tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.4) + +func apply_upgrade_armor() -> void: + tier += 1 + var tw := create_tween() + tw.tween_property(player_mat, "albedo_color", Color(0.7, 0.8, 1.0), 0.1) + tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.5)