diff --git a/actors/bosses/textures/emperor_boss.png b/actors/bosses/textures/emperor_boss.png new file mode 100644 index 0000000..d1eec0b Binary files /dev/null and b/actors/bosses/textures/emperor_boss.png differ diff --git a/actors/bosses/textures/emperor_boss.png.import b/actors/bosses/textures/emperor_boss.png.import new file mode 100644 index 0000000..21ed8aa --- /dev/null +++ b/actors/bosses/textures/emperor_boss.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://lmm4g4d4vc5n" +path="res://.godot/imported/emperor_boss.png-36d3468ebde9fb5384c0aa62bc0a2a80.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://actors/bosses/textures/emperor_boss.png" +dest_files=["res://.godot/imported/emperor_boss.png-36d3468ebde9fb5384c0aa62bc0a2a80.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 diff --git a/actors/bosses/textures/judicator_boss.png b/actors/bosses/textures/judicator_boss.png new file mode 100644 index 0000000..d273e26 Binary files /dev/null and b/actors/bosses/textures/judicator_boss.png differ diff --git a/actors/bosses/textures/judicator_boss.png.import b/actors/bosses/textures/judicator_boss.png.import new file mode 100644 index 0000000..c8a9e7c --- /dev/null +++ b/actors/bosses/textures/judicator_boss.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dn1d4lih1pe7c" +path="res://.godot/imported/judicator_boss.png-42e0bb4e04a7c71fc33d3d3383d0f631.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://actors/bosses/textures/judicator_boss.png" +dest_files=["res://.godot/imported/judicator_boss.png-42e0bb4e04a7c71fc33d3d3383d0f631.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 diff --git a/actors/bosses/textures/mage_boss.png b/actors/bosses/textures/mage_boss.png new file mode 100644 index 0000000..488a624 Binary files /dev/null and b/actors/bosses/textures/mage_boss.png differ diff --git a/actors/bosses/textures/mage_boss.png.import b/actors/bosses/textures/mage_boss.png.import new file mode 100644 index 0000000..49be4ec --- /dev/null +++ b/actors/bosses/textures/mage_boss.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxmtts38sqvyy" +path="res://.godot/imported/mage_boss.png-7a2a8e313f52e339df41074a436b0275.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://actors/bosses/textures/mage_boss.png" +dest_files=["res://.godot/imported/mage_boss.png-7a2a8e313f52e339df41074a436b0275.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 diff --git a/actors/bosses/textures/ranger_boss.png b/actors/bosses/textures/ranger_boss.png new file mode 100644 index 0000000..6e82865 Binary files /dev/null and b/actors/bosses/textures/ranger_boss.png differ diff --git a/actors/bosses/textures/ranger_boss.png.import b/actors/bosses/textures/ranger_boss.png.import new file mode 100644 index 0000000..47cb8a3 --- /dev/null +++ b/actors/bosses/textures/ranger_boss.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xwfdwt5lgjwd" +path="res://.godot/imported/ranger_boss.png-6b75dcc9537a889bc0387e5965640d42.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://actors/bosses/textures/ranger_boss.png" +dest_files=["res://.godot/imported/ranger_boss.png-6b75dcc9537a889bc0387e5965640d42.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 diff --git a/actors/bosses/textures/twin_first_boss.png b/actors/bosses/textures/twin_first_boss.png new file mode 100644 index 0000000..222df00 Binary files /dev/null and b/actors/bosses/textures/twin_first_boss.png differ diff --git a/actors/bosses/textures/twin_first_boss.png.import b/actors/bosses/textures/twin_first_boss.png.import new file mode 100644 index 0000000..1675ed6 --- /dev/null +++ b/actors/bosses/textures/twin_first_boss.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8380u0oso47q" +path="res://.godot/imported/twin_first_boss.png-8bdcb8cc8e4f2affd83e719fd9cbabfa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://actors/bosses/textures/twin_first_boss.png" +dest_files=["res://.godot/imported/twin_first_boss.png-8bdcb8cc8e4f2affd83e719fd9cbabfa.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 diff --git a/actors/bosses/textures/twin_second_boss.png b/actors/bosses/textures/twin_second_boss.png new file mode 100644 index 0000000..5052c0e Binary files /dev/null and b/actors/bosses/textures/twin_second_boss.png differ diff --git a/actors/bosses/textures/twin_second_boss.png.import b/actors/bosses/textures/twin_second_boss.png.import new file mode 100644 index 0000000..f6e4eef --- /dev/null +++ b/actors/bosses/textures/twin_second_boss.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dw0226ekwre7s" +path="res://.godot/imported/twin_second_boss.png-28b2cd4b623c15faa7ada0e6c0bbd071.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://actors/bosses/textures/twin_second_boss.png" +dest_files=["res://.godot/imported/twin_second_boss.png-28b2cd4b623c15faa7ada0e6c0bbd071.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 diff --git a/actors/bosses/town/emperor_boss.gd b/actors/bosses/town/emperor_boss.gd new file mode 100644 index 0000000..8fd0a4a --- /dev/null +++ b/actors/bosses/town/emperor_boss.gd @@ -0,0 +1,421 @@ +extends Node2D + +signal defeated + +const DAMAGE_NUMBER_SCENE := preload("res://effects/damage_number.tscn") +const ROYAL_BOLT_SCENE := preload("res://effects/projectiles/royal_bolt.tscn") +const MELEE_UTILS := preload("res://combat/melee_utils.gd") +const TEXTURE_LOADER := preload("res://combat/runtime_texture_loader.gd") +const EMPEROR_BODY_TEXTURE_PATH := "res://actors/bosses/textures/emperor_boss.png" +const EMPEROR_WEAPON_TEXTURE_PATH := "res://art/final_materials/weapons/boss_weapon_emperor_sword.png" + +@export var max_hp: float = 5000.0 +@export var defense_value: float = 260.0 +@export var move_speed: float = 165.0 +@export var attack_damage: float = 42.0 +@export var attack_range: float = 96.0 +@export var attack_arc_degrees: float = 120.0 +@export var attack_interval: float = 1.05 +@export var phase_two_threshold: float = 2000.0 +@export var phase_transition_duration: float = 0.9 +@export var skill_interval_phase_one: float = 5.0 +@export var skill_interval_phase_two: float = 3.0 +@export var shadow_step_duration: float = 0.55 +@export var shadow_step_slash_damage: float = 70.0 +@export var charge_skill_damage: float = 88.0 +@export var charge_distance: float = 280.0 +@export var arcane_burst_damage: float = 66.0 +@export var arcane_burst_radius: float = 130.0 +@export var volley_damage: float = 24.0 + +@onready var body: Polygon2D = $Body +@onready var weapon: Node2D = $Weapon +@onready var burst_ring: Line2D = $BurstRing +@onready var phase_ring: Line2D = $PhaseRing +@onready var projectile_spawner: Node2D = $ProjectileSpawner +@onready var health_component: Node = $HealthComponent +@onready var effects_layer: Node2D = $EffectsLayer + +var target: Node2D = null +var hp: float = 0.0 +var state: StringName = &"idle" +var state_time: float = 0.0 +var attack_cooldown: float = 0.0 +var skill_cooldown: float = skill_interval_phase_one +var current_skill: StringName = &"" +var line_direction: Vector2 = Vector2.RIGHT +var action_committed: bool = false +var phase_two: bool = false +var invulnerable: bool = false +var charge_start_position: Vector2 = Vector2.ZERO +var charge_direction: Vector2 = Vector2.RIGHT +var burst_center: Vector2 = Vector2.ZERO +var shadow_reposition_point: Vector2 = Vector2.ZERO +var body_sprite: Sprite2D = null +var weapon_sprite: Sprite2D = null +var weapon_angle_offset: float = deg_to_rad(64.0) + +func _ready() -> void: + add_to_group("damageable") + health_component.setup(max_hp, defense_value) + health_component.damaged.connect(_on_damaged) + health_component.died.connect(_on_died) + hp = max_hp + _setup_body_visual() + _setup_weapon_visual() + burst_ring.visible = false + phase_ring.visible = false + _update_visuals() + +func bind_player(player: Node2D) -> void: + target = player + +func get_status_title() -> String: + return "Emperor" + +func get_status_text() -> String: + return "HP %d / %d\nSkill cadence %.1fs\nState: %s" % [ + int(round(hp)), + int(round(max_hp)), + skill_interval_phase_two if phase_two else skill_interval_phase_one, + String(state) + ] + +func _physics_process(delta: float) -> void: + if state == &"dead": + return + if target == null or not _is_targetable_player(target): + _find_target() + attack_cooldown = maxf(attack_cooldown - delta, 0.0) + skill_cooldown = maxf(skill_cooldown - delta, 0.0) + state_time += delta + if not phase_two and hp > 0.0 and hp <= phase_two_threshold and state != &"phase_transition": + _start_phase_transition() + _update_state(delta) + _update_visuals() + +func receive_hit(payload: Dictionary) -> void: + if invulnerable or state == &"dead": + return + var result: Dictionary = health_component.receive_hit(payload) + var final_damage := float(result.get("damage", 0.0)) + if final_damage > 0.0: + _spawn_damage_number(final_damage, bool(result.get("is_critical", false))) + +func _find_target() -> void: + for player in get_tree().get_nodes_in_group("player"): + if _is_targetable_player(player): + target = player + return + target = null + +func _is_targetable_player(candidate: Variant) -> bool: + if candidate == null or not (candidate is Node2D): + return false + var actor: Node2D = candidate + if not is_instance_valid(actor): + return false + if actor.has_method("is_detectable") and not bool(actor.is_detectable()): + return false + var hp_value: Variant = actor.get("hp") + return hp_value == null or float(hp_value) > 0.0 + +func _update_state(delta: float) -> void: + match state: + &"idle": + _process_idle(delta) + &"basic_attack": + _process_basic_attack() + &"skill_shadow": + _process_skill_shadow() + &"skill_charge": + _process_skill_charge(delta) + &"skill_burst": + _process_skill_burst() + &"skill_volley": + _process_skill_volley() + &"phase_transition": + _process_phase_transition() + +func _process_idle(delta: float) -> void: + if target == null or not is_instance_valid(target): + return + var to_target := target.global_position - global_position + var distance := to_target.length() + if to_target != Vector2.ZERO: + line_direction = to_target.normalized() + if skill_cooldown <= 0.0: + _start_skill_cast() + return + if attack_cooldown <= 0.0 and distance <= attack_range: + _start_basic_attack() + return + if distance > attack_range * 0.86: + global_position += line_direction * move_speed * delta + +func _start_basic_attack() -> void: + state = &"basic_attack" + state_time = 0.0 + action_committed = false + attack_cooldown = attack_interval + _animate_weapon_swing(-112.0, 38.0, 0.26) + +func _process_basic_attack() -> void: + if not action_committed and state_time >= 0.24: + action_committed = true + _hit_target_in_arc(attack_range, attack_damage, attack_arc_degrees) + _spawn_slash_effect(attack_range, Color(1.0, 0.84, 0.58, 0.92)) + if state_time >= 0.58: + _return_to_idle() + +func _start_skill_cast() -> void: + current_skill = _pick_skill() + action_committed = false + state_time = 0.0 + skill_cooldown = skill_interval_phase_two if phase_two else skill_interval_phase_one + match current_skill: + &"shadow": + state = &"skill_shadow" + shadow_reposition_point = global_position + if target != null and is_instance_valid(target): + var side := Vector2(64.0, 0.0) + side.x *= -1.0 if int(Time.get_ticks_msec() / 100) % 2 == 0 else 1.0 + shadow_reposition_point = target.global_position + side + &"charge": + state = &"skill_charge" + charge_start_position = global_position + charge_direction = (target.global_position - global_position).normalized() if target != null else line_direction + if charge_direction == Vector2.ZERO: + charge_direction = Vector2.RIGHT + _animate_weapon_swing(-124.0, -18.0, 0.32) + &"burst": + state = &"skill_burst" + burst_center = target.global_position if target != null else global_position + burst_ring.visible = true + burst_ring.global_position = burst_center + burst_ring.scale = Vector2.ONE * 0.4 + burst_ring.modulate = Color(0.82, 0.9, 1.0, 0.92) + &"volley": + state = &"skill_volley" + _animate_weapon_swing(-44.0, 18.0, 0.24) + +func _pick_skill() -> StringName: + var pool: Array[StringName] = [&"shadow", &"charge", &"burst", &"volley"] + return pool[randi() % pool.size()] + +func _process_skill_shadow() -> void: + invulnerable = true + body.modulate = Color(0.72, 0.8, 1.0, 0.34) + if state_time >= shadow_step_duration * 0.6 and not action_committed: + action_committed = true + global_position = shadow_reposition_point + if target != null and is_instance_valid(target): + line_direction = (target.global_position - global_position).normalized() + if line_direction == Vector2.ZERO: + line_direction = Vector2.RIGHT + _animate_weapon_swing(-138.0, 46.0, 0.18) + _hit_target_in_arc(112.0, shadow_step_slash_damage, 145.0) + _spawn_slash_effect(112.0, Color(0.8, 0.9, 1.0, 0.95)) + if state_time >= shadow_step_duration: + invulnerable = false + _return_to_idle() + +func _process_skill_charge(delta: float) -> void: + if state_time < 0.26: + return + global_position += charge_direction * (charge_distance / 0.26) * delta + if not action_committed: + action_committed = true + _hit_target_in_arc(130.0, charge_skill_damage, 95.0) + _spawn_slash_effect(130.0, Color(1.0, 0.78, 0.56, 0.95)) + if state_time >= 0.56: + _return_to_idle() + +func _process_skill_burst() -> void: + if burst_ring.visible: + burst_ring.scale = Vector2.ONE * (0.4 + minf(state_time * 1.5, 0.75)) + if not action_committed and state_time >= 0.42: + action_committed = true + _apply_burst_damage() + if state_time >= 0.58: + burst_ring.visible = false + _return_to_idle() + +func _apply_burst_damage() -> void: + _spawn_burst_effect() + if target == null or not is_instance_valid(target): + return + if burst_center.distance_to(target.global_position) > arcane_burst_radius: + return + target.receive_hit({ + "source": self, + "damage": arcane_burst_damage, + "crit_rate": 0.0 + }) + +func _process_skill_volley() -> void: + if not action_committed and state_time >= 0.24: + action_committed = true + _fire_volley() + if state_time >= 0.52: + _return_to_idle() + +func _fire_volley() -> void: + if target == null or not is_instance_valid(target): + return + var base_direction := (target.global_position - projectile_spawner.global_position).normalized() + for angle_offset in [-10.0, 0.0, 10.0]: + var bolt := ROYAL_BOLT_SCENE.instantiate() + bolt.global_position = projectile_spawner.global_position + get_tree().current_scene.add_child(bolt) + bolt.setup(self, base_direction.rotated(deg_to_rad(angle_offset)), volley_damage) + +func _start_phase_transition() -> void: + phase_two = true + invulnerable = true + state = &"phase_transition" + state_time = 0.0 + action_committed = false + phase_ring.visible = true + phase_ring.scale = Vector2.ONE * 0.6 + +func _process_phase_transition() -> void: + phase_ring.rotation += 0.16 + phase_ring.scale = Vector2.ONE * (0.6 + minf(state_time * 0.55, 0.5)) + if state_time >= phase_transition_duration: + invulnerable = false + phase_ring.visible = false + skill_cooldown = 0.4 + _return_to_idle() + +func _return_to_idle() -> void: + state = &"idle" + state_time = 0.0 + current_skill = &"" + burst_ring.visible = false + +func _hit_target_in_arc(radius: float, damage: float, arc_degrees: float) -> void: + if target == null or not is_instance_valid(target): + return + if not MELEE_UTILS.is_point_in_arc(global_position, line_direction, target.global_position, radius, arc_degrees): + return + target.receive_hit({ + "source": self, + "damage": damage, + "crit_rate": 0.0 + }) + +func _spawn_slash_effect(radius: float, color: Color) -> void: + var slash := Line2D.new() + slash.width = 12.0 + slash.default_color = color + slash.position = line_direction * 14.0 + slash.rotation = line_direction.angle() + slash.points = PackedVector2Array([ + Vector2(-8.0, -24.0), + Vector2(radius * 0.3, -radius * 0.18), + Vector2(radius * 0.78, 0.0), + Vector2(radius * 0.3, radius * 0.18), + Vector2(-8.0, 24.0) + ]) + effects_layer.add_child(slash) + var tween := create_tween() + tween.tween_property(slash, "modulate:a", 0.0, 0.16) + tween.parallel().tween_property(slash, "scale", Vector2.ONE * 1.1, 0.16) + tween.finished.connect(slash.queue_free) + +func _spawn_burst_effect() -> void: + var ring := Line2D.new() + ring.width = 4.0 + ring.closed = true + ring.default_color = Color(0.82, 0.92, 1.0, 0.9) + ring.points = _build_ring_points(arcane_burst_radius, 20) + ring.global_position = burst_center + get_tree().current_scene.add_child(ring) + var tween := create_tween() + tween.tween_property(ring, "scale", Vector2.ONE * 1.18, 0.16) + tween.parallel().tween_property(ring, "modulate:a", 0.0, 0.16) + tween.finished.connect(ring.queue_free) + +func _build_ring_points(radius: float, steps: int) -> PackedVector2Array: + var points := PackedVector2Array() + for index in range(steps): + var angle := TAU * float(index) / float(steps) + points.append(Vector2.RIGHT.rotated(angle) * radius) + return points + +func _setup_body_visual() -> void: + body_sprite = Sprite2D.new() + body_sprite.texture = TEXTURE_LOADER.load_texture(EMPEROR_BODY_TEXTURE_PATH) + body_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + body_sprite.centered = true + body_sprite.scale = Vector2.ONE * 0.40 + body.add_child(body_sprite) + body.color = Color(1.0, 1.0, 1.0, 0.0) + +func _set_body_tint(color: Color) -> void: + if body_sprite != null: + body_sprite.self_modulate = color + else: + body.color = color + +func _setup_weapon_visual() -> void: + weapon_sprite = Sprite2D.new() + weapon_sprite.texture = TEXTURE_LOADER.load_texture(EMPEROR_WEAPON_TEXTURE_PATH) + weapon_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + weapon_sprite.centered = true + weapon_sprite.scale = Vector2.ONE * 0.42 + weapon_sprite.position = Vector2(-40.0, 8.0) + weapon.add_child(weapon_sprite) + +func _animate_weapon_swing(start_degrees: float, end_degrees: float, duration: float) -> void: + weapon_angle_offset = deg_to_rad(start_degrees) + var tween := create_tween() + tween.tween_property(self, "weapon_angle_offset", deg_to_rad(end_degrees), duration) + +func _update_visuals() -> void: + if target != null and is_instance_valid(target): + var to_target := target.global_position - global_position + if to_target != Vector2.ZERO: + line_direction = to_target.normalized() + _set_body_tint(Color(1.0, 0.94, 0.88, 1.0) if phase_two else Color.WHITE) + if invulnerable: + body.modulate = Color(1.0, 1.0, 1.0, 0.8) + else: + body.modulate = Color.WHITE + phase_ring.default_color = Color(1.0, 0.84, 0.52, 0.88) + weapon.position = line_direction * 22.0 + Vector2(0.0, 0.0) + weapon.rotation = line_direction.angle() + weapon_angle_offset + projectile_spawner.position = line_direction * 24.0 + +func _spawn_damage_number(amount: float, is_critical: bool) -> void: + var damage_number := DAMAGE_NUMBER_SCENE.instantiate() + damage_number.position = Vector2(0.0, -42.0) + damage_number.setup(amount, is_critical) + effects_layer.add_child(damage_number) + +func _on_damaged(_amount: float, remaining_hp: float, _source: Node) -> void: + hp = remaining_hp + _set_body_tint(Color(1.0, 0.8, 0.8, 1.0)) + var timer := get_tree().create_timer(0.12) + timer.timeout.connect(func() -> void: + if is_instance_valid(self) and state != &"dead": + _update_visuals() + ) + +func _on_died() -> void: + if state == &"dead": + return + hp = 0.0 + invulnerable = true + state = &"dead" + burst_ring.visible = false + phase_ring.visible = false + weapon.visible = false + Sfx.play_event(&"boss_generic_dead", global_position) + var timer := get_tree().create_timer(0.6) + timer.timeout.connect(func() -> void: + if is_instance_valid(self): + defeated.emit() + queue_free() + ) diff --git a/actors/bosses/town/emperor_boss.gd.uid b/actors/bosses/town/emperor_boss.gd.uid new file mode 100644 index 0000000..efb427f --- /dev/null +++ b/actors/bosses/town/emperor_boss.gd.uid @@ -0,0 +1 @@ +uid://bxbkwd1ddgc1y diff --git a/actors/bosses/town/emperor_boss.tscn b/actors/bosses/town/emperor_boss.tscn new file mode 100644 index 0000000..b62345a --- /dev/null +++ b/actors/bosses/town/emperor_boss.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://actors/bosses/town/emperor_boss.gd" id="1"] +[ext_resource type="Script" path="res://combat/health_component.gd" id="2"] + +[node name="EmperorBoss" type="Node2D"] +script = ExtResource("1") + +[node name="Body" type="Polygon2D" parent="."] +color = Color(0.84, 0.86, 0.95, 1) +polygon = PackedVector2Array(-28, 30, -22, -10, -10, -36, 10, -36, 22, -10, 28, 30, 0, 42) + +[node name="Weapon" type="Node2D" parent="."] +position = Vector2(16, -8) + +[node name="BurstRing" type="Line2D" parent="."] +visible = false +default_color = Color(0.82, 0.9, 1, 0.92) +width = 3.0 +closed = true +points = PackedVector2Array(70, 0, 50, 50, 0, 70, -50, 50, -70, 0, -50, -50, 0, -70, 50, -50) + +[node name="PhaseRing" type="Line2D" parent="."] +visible = false +default_color = Color(1, 0.84, 0.52, 0.88) +width = 4.0 +closed = true +points = PackedVector2Array(84, 0, 60, 60, 0, 84, -60, 60, -84, 0, -60, -60, 0, -84, 60, -60) + +[node name="ProjectileSpawner" type="Node2D" parent="."] +position = Vector2(24, -6) + +[node name="HealthComponent" type="Node" parent="."] +script = ExtResource("2") + +[node name="EffectsLayer" type="Node2D" parent="."] diff --git a/actors/bosses/town/judicator_boss.gd b/actors/bosses/town/judicator_boss.gd index 3fff290..1e8a3b4 100644 --- a/actors/bosses/town/judicator_boss.gd +++ b/actors/bosses/town/judicator_boss.gd @@ -4,6 +4,9 @@ signal defeated const DAMAGE_NUMBER_SCENE := preload("res://effects/damage_number.tscn") const ENEMY_BOLT_SCENE := preload("res://effects/projectiles/enemy_bolt.tscn") +const TEXTURE_LOADER := preload("res://combat/runtime_texture_loader.gd") +const JUDICATOR_BODY_TEXTURE_PATH := "res://actors/bosses/textures/judicator_boss.png" +const JUDICATOR_WEAPON_TEXTURE_PATH := "res://art/final_materials/weapons/boss_weapon_judicator_sword.png" @export var max_hp: float = 1500.0 @export var defense_value: float = 180.0 @@ -61,6 +64,9 @@ var silenced_time_remaining: float = 0.0 var root_time_remaining: float = 0.0 var slow_time_remaining: float = 0.0 var slow_factor: float = 1.0 +var body_sprite: Sprite2D = null +var sword_sprite: Sprite2D = null +var sword_angle_offset: float = deg_to_rad(56.0) var enraged: bool = false var slam_aftershock_committed: bool = false var barrage_wave_index: int = 0 @@ -73,6 +79,8 @@ func _ready() -> void: health_component.defense_changed.connect(_on_defense_changed) health_component.died.connect(_on_died) hp = max_hp + _setup_body_visual() + _setup_weapon_visual() landing_ring.visible = false slash_line.visible = false _update_visuals() @@ -400,6 +408,31 @@ func _update_enrage_state() -> void: _show_intent_text("Enraged", Color(1.0, 0.70, 0.48, 1.0), global_position, 0.94) Sfx.play_event(&"boss_judicator_skill2", global_position, 2.0) +func _setup_body_visual() -> void: + body_sprite = Sprite2D.new() + body_sprite.texture = TEXTURE_LOADER.load_texture(JUDICATOR_BODY_TEXTURE_PATH) + body_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + body_sprite.centered = true + body_sprite.scale = Vector2.ONE * 0.40 + body.add_child(body_sprite) + body.color = Color(1.0, 1.0, 1.0, 0.0) + +func _set_body_tint(color: Color) -> void: + if body_sprite != null: + body_sprite.self_modulate = color + else: + body.color = color + +func _setup_weapon_visual() -> void: + sword_sprite = Sprite2D.new() + sword_sprite.texture = TEXTURE_LOADER.load_texture(JUDICATOR_WEAPON_TEXTURE_PATH) + sword_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + sword_sprite.centered = true + sword_sprite.scale = Vector2.ONE * 0.44 + sword_sprite.position = Vector2(-36.0, 4.0) + sword.add_child(sword_sprite) + sword.color = Color(1.0, 1.0, 1.0, 0.0) + func _update_visuals() -> void: var body_color := Color(0.68, 0.7, 0.78, 1.0) if state == &"skill_1_jump_start" or state == &"skill_2_charge" or state == &"skill_3_barrage_mark": @@ -410,10 +443,16 @@ func _update_visuals() -> void: body_color = Color(0.72, 0.64, 0.92, 1.0) elif enraged: body_color = Color(0.92, 0.62, 0.42, 1.0) - body.color = body_color - sword.color = Color(0.92, 0.84, 0.72, 1.0) + _set_body_tint(body_color) + if sword_sprite == null: + sword.color = Color(0.92, 0.84, 0.72, 1.0) + var sword_direction := line_direction if line_direction != Vector2.ZERO else Vector2.RIGHT if target != null and is_instance_valid(target): - sword.rotation = (target.global_position - global_position).angle() + var to_target := target.global_position - global_position + if to_target != Vector2.ZERO: + sword_direction = to_target.normalized() + sword.position = sword_direction * 18.0 + Vector2(0.0, 2.0) + sword.rotation = sword_direction.angle() + sword_angle_offset var pulse := 0.82 + 0.18 * sin(Time.get_ticks_msec() * 0.01) if landing_ring.visible: landing_ring.scale = Vector2.ONE * (0.94 + 0.08 * pulse) @@ -485,7 +524,7 @@ func _show_intent_text(label_text: String, color_value: Color, world_position: V func _on_damaged(_amount: float, remaining_hp: float, _source: Node) -> void: hp = remaining_hp - body.color = Color(1.0, 0.58, 0.58, 1.0) + _set_body_tint(Color(1.0, 0.8, 0.8, 1.0)) var timer := get_tree().create_timer(0.14) timer.timeout.connect(func() -> void: if is_instance_valid(self) and hp > 0.0: diff --git a/actors/bosses/town/mage_boss.gd b/actors/bosses/town/mage_boss.gd new file mode 100644 index 0000000..39907c0 --- /dev/null +++ b/actors/bosses/town/mage_boss.gd @@ -0,0 +1,501 @@ +extends Node2D + +signal defeated + +const DAMAGE_NUMBER_SCENE := preload("res://effects/damage_number.tscn") +const ARCANE_BOLT_SCENE := preload("res://effects/projectiles/arcane_bolt.tscn") +const TEXTURE_LOADER := preload("res://combat/runtime_texture_loader.gd") +const MAGE_BODY_TEXTURE_PATH := "res://actors/bosses/textures/mage_boss.png" +const MAGE_WEAPON_TEXTURE_PATH := "res://art/final_materials/weapons/boss_weapon_mage_staff.png" + +@export var max_hp: float = 3300.0 +@export var defense_value: float = 205.0 +@export var move_speed: float = 180.0 +@export var attack_damage: float = 44.0 +@export var attack_interval: float = 1.0 +@export var attack_targeting_range: float = 560.0 +@export var skill1_damage: float = 28.0 +@export var skill1_cooldown: float = 8.2 +@export var blade_duration: float = 7.0 +@export var blade_radius: float = 96.0 +@export var blade_tick_interval: float = 0.8 +@export var skill1_shield_value: float = 130.0 +@export var skill2_damage: float = 92.0 +@export var skill2_cooldown: float = 7.0 +@export var skill2_cast_duration: float = 0.42 +@export var burst_radius: float = 128.0 +@export var burst_targeting_range: float = 520.0 +@export var chain_burst_damage: float = 56.0 +@export var skill3_cooldown: float = 6.2 +@export var skill3_cast_duration: float = 0.26 +@export var silence_duration: float = 2.2 +@export var slow_duration: float = 3.5 +@export var slow_multiplier: float = 0.55 +@export var root_duration: float = 1.0 + +@onready var body: Polygon2D = $Body +@onready var focus_ring: Line2D = $FocusRing +@onready var burst_ring: Line2D = $BurstRing +@onready var enchant_sigil: Line2D = $EnchantSigil +@onready var blade_orbit: Node2D = $BladeOrbit +@onready var weapon: Node2D = $Weapon +@onready var projectile_spawner: Node2D = $ProjectileSpawner +@onready var health_component: Node = $HealthComponent +@onready var effects_layer: Node2D = $EffectsLayer + +var target: Node2D = null +var hp: float = 0.0 +var shield: float = 0.0 +var state: StringName = &"idle" +var state_time: float = 0.0 +var recover_duration: float = 0.0 +var attack_cooldown: float = 0.0 +var skill1_cooldown_remaining: float = 1.2 +var skill2_cooldown_remaining: float = 3.0 +var skill3_cooldown_remaining: float = 4.8 +var line_direction: Vector2 = Vector2.RIGHT +var action_committed: bool = false +var blades_active: bool = false +var blade_time_remaining: float = 0.0 +var blade_hit_cooldowns: Dictionary = {} +var blade_nodes: Array[Polygon2D] = [] +var enchant_active: bool = false +var current_skill_target: Node2D = null +var silenced_time_remaining: float = 0.0 +var root_time_remaining: float = 0.0 +var slow_time_remaining: float = 0.0 +var slow_factor: float = 1.0 +var body_sprite: Sprite2D = null +var weapon_sprite: Sprite2D = null +var weapon_angle_offset: float = deg_to_rad(98.0) +var orbit_direction: float = 1.0 + +func _ready() -> void: + add_to_group("damageable") + health_component.setup(max_hp, defense_value) + health_component.damaged.connect(_on_damaged) + health_component.healed.connect(_on_healed) + health_component.shield_changed.connect(_on_shield_changed) + health_component.died.connect(_on_died) + hp = max_hp + _setup_body_visual() + _setup_weapon_visual() + for child in blade_orbit.get_children(): + if child is Polygon2D: + blade_nodes.append(child) + focus_ring.visible = false + burst_ring.visible = false + enchant_sigil.visible = false + blade_orbit.visible = false + _update_visuals() + +func bind_player(player: Node2D) -> void: + target = player + +func get_status_title() -> String: + return "Grand Arcanist" + +func get_status_text() -> String: + var shield_text := " | Shield %d" % int(round(shield)) if shield > 0.0 else "" + return "HP %d / %d%s\nState: %s" % [ + int(round(hp)), + int(round(max_hp)), + shield_text, + String(state) + ] + +func _physics_process(delta: float) -> void: + if state == &"dead": + return + if target == null or not _is_targetable_player(target): + _find_target() + _update_status_timers(delta) + attack_cooldown = maxf(attack_cooldown - delta, 0.0) + skill1_cooldown_remaining = maxf(skill1_cooldown_remaining - delta, 0.0) + skill2_cooldown_remaining = maxf(skill2_cooldown_remaining - delta, 0.0) + skill3_cooldown_remaining = maxf(skill3_cooldown_remaining - delta, 0.0) + state_time += delta + _update_blades(delta) + _update_state(delta) + _update_visuals() + +func receive_hit(payload: Dictionary) -> void: + if state == &"dead": + return + var result: Dictionary = health_component.receive_hit(payload) + var final_damage := float(result.get("damage", 0.0)) + if final_damage > 0.0: + apply_control_effects(payload) + _spawn_damage_number(final_damage, bool(result.get("is_critical", false))) + +func apply_control_effects(payload: Dictionary) -> void: + if payload.has("silence_duration"): + silenced_time_remaining = maxf(silenced_time_remaining, float(payload["silence_duration"])) + if payload.has("root_duration"): + root_time_remaining = maxf(root_time_remaining, float(payload["root_duration"])) + if payload.has("slow_duration"): + slow_time_remaining = maxf(slow_time_remaining, float(payload["slow_duration"])) + slow_factor = minf(slow_factor, float(payload.get("slow_multiplier", 1.0))) + +func _find_target() -> void: + for player in get_tree().get_nodes_in_group("player"): + if _is_targetable_player(player): + target = player + return + target = null + +func _is_targetable_player(candidate: Variant) -> bool: + if candidate == null or not (candidate is Node2D): + return false + var actor: Node2D = candidate + if not is_instance_valid(actor): + return false + if actor.has_method("is_detectable") and not bool(actor.is_detectable()): + return false + var hp_value: Variant = actor.get("hp") + return hp_value == null or float(hp_value) > 0.0 + +func _update_status_timers(delta: float) -> void: + silenced_time_remaining = maxf(silenced_time_remaining - delta, 0.0) + root_time_remaining = maxf(root_time_remaining - delta, 0.0) + if slow_time_remaining > 0.0: + slow_time_remaining = maxf(slow_time_remaining - delta, 0.0) + else: + slow_factor = 1.0 + +func _update_state(delta: float) -> void: + match state: + &"idle": + _process_idle(delta) + &"basic_attack": + _process_basic_attack() + &"skill1_cast": + _process_skill1_cast() + &"skill2_cast": + _process_skill2_cast() + &"skill3_cast": + _process_skill3_cast() + &"recover": + _process_recover() + +func _process_idle(delta: float) -> void: + if target == null or not is_instance_valid(target): + return + var to_target := target.global_position - global_position + var distance := to_target.length() + if to_target != Vector2.ZERO: + line_direction = to_target.normalized() + if _can_use_skills() and not enchant_active and skill3_cooldown_remaining <= 0.0: + _start_skill3_cast() + return + if _can_use_skills() and skill2_cooldown_remaining <= 0.0 and distance <= burst_targeting_range: + _start_skill2_cast() + return + if _can_use_skills() and not blades_active and skill1_cooldown_remaining <= 0.0 and distance <= 240.0: + _start_skill1_cast() + return + if attack_cooldown <= 0.0 and distance <= attack_targeting_range: + _start_basic_attack() + return + if root_time_remaining > 0.0: + return + var tangent := Vector2(-line_direction.y, line_direction.x) * orbit_direction + var speed := move_speed * slow_factor + if distance < 180.0: + global_position -= line_direction * speed * 0.9 * delta + elif distance > 340.0: + global_position += line_direction * speed * 0.75 * delta + else: + if state_time >= 0.56: + state_time = 0.0 + orbit_direction *= -1.0 + tangent = Vector2(-line_direction.y, line_direction.x) * orbit_direction + global_position += tangent.normalized() * speed * 0.5 * delta + +func _start_basic_attack() -> void: + state = &"basic_attack" + state_time = 0.0 + action_committed = false + attack_cooldown = attack_interval + focus_ring.visible = true + _animate_weapon_swing(-56.0, 18.0, 0.24) + Sfx.play_event(&"mage_attack", global_position) + +func _process_basic_attack() -> void: + if not action_committed and state_time >= 0.24: + action_committed = true + _fire_arcane_bolt(attack_damage) + if state_time >= 0.48: + _enter_recover(0.16) + +func _start_skill1_cast() -> void: + state = &"skill1_cast" + state_time = 0.0 + action_committed = false + skill1_cooldown_remaining = skill1_cooldown + _animate_weapon_swing(-34.0, 14.0, 0.28) + Sfx.play_event(&"mage_skill1_blades", global_position) + +func _process_skill1_cast() -> void: + if not action_committed and state_time >= 0.28: + action_committed = true + _activate_arcane_blades() + if state_time >= 0.46: + _enter_recover(0.16) + +func _activate_arcane_blades() -> void: + blades_active = true + blade_time_remaining = blade_duration + blade_hit_cooldowns.clear() + blade_orbit.visible = true + health_component.set_shield(skill1_shield_value) + +func _start_skill2_cast() -> void: + state = &"skill2_cast" + state_time = 0.0 + action_committed = false + skill2_cooldown_remaining = skill2_cooldown + current_skill_target = target + burst_ring.visible = true + burst_ring.scale = Vector2.ONE * 0.4 + _animate_weapon_swing(-26.0, 12.0, skill2_cast_duration) + Sfx.play_event(&"mage_skill2_burst", global_position) + +func _process_skill2_cast() -> void: + if current_skill_target != null and is_instance_valid(current_skill_target): + burst_ring.global_position = current_skill_target.global_position + if burst_ring.visible: + burst_ring.scale = Vector2.ONE * (0.4 + minf(state_time * 1.35, 0.72)) + if not action_committed and state_time >= skill2_cast_duration * 0.62: + action_committed = true + var center := current_skill_target.global_position if current_skill_target != null and is_instance_valid(current_skill_target) else global_position + line_direction * 110.0 + _release_arcane_burst(center) + if state_time >= skill2_cast_duration + 0.2: + burst_ring.visible = false + current_skill_target = null + _enter_recover(0.18) + +func _release_arcane_burst(center: Vector2) -> void: + var extra_payload := _consume_enchant_payload() + _apply_burst_damage(center, burst_radius, skill2_damage, extra_payload) + _show_burst_effect(center, burst_radius) + if hp <= max_hp * 0.5: + var timer := get_tree().create_timer(0.18) + timer.timeout.connect(func() -> void: + if is_instance_valid(self) and state != &"dead": + _apply_burst_damage(center, burst_radius * 0.72, chain_burst_damage, {}) + _show_burst_effect(center, burst_radius * 0.72) + ) + +func _start_skill3_cast() -> void: + state = &"skill3_cast" + state_time = 0.0 + action_committed = false + skill3_cooldown_remaining = skill3_cooldown + _animate_weapon_swing(-42.0, 10.0, skill3_cast_duration) + Sfx.play_event(&"mage_skill3_enchant", global_position) + +func _process_skill3_cast() -> void: + if not action_committed and state_time >= skill3_cast_duration * 0.55: + action_committed = true + enchant_active = true + enchant_sigil.visible = true + if state_time >= skill3_cast_duration + 0.12: + _enter_recover(0.12) + +func _enter_recover(duration: float) -> void: + state = &"recover" + state_time = 0.0 + recover_duration = duration + focus_ring.visible = false + +func _process_recover() -> void: + if state_time >= recover_duration: + state = &"idle" + state_time = 0.0 + +func _can_use_skills() -> bool: + return silenced_time_remaining <= 0.0 + +func _fire_arcane_bolt(damage: float) -> void: + var direction := line_direction if line_direction != Vector2.ZERO else Vector2.RIGHT + var bolt := ARCANE_BOLT_SCENE.instantiate() + bolt.global_position = projectile_spawner.global_position + get_tree().current_scene.add_child(bolt) + bolt.setup(self, direction, damage, 0.0, &"attack", _consume_enchant_payload()) + +func _consume_enchant_payload() -> Dictionary: + if not enchant_active: + return {} + enchant_active = false + enchant_sigil.visible = false + return _build_control_payload() + +func _apply_burst_damage(center: Vector2, radius: float, damage: float, extra_payload: Dictionary) -> void: + for player in get_tree().get_nodes_in_group("player"): + if not _is_targetable_player(player): + continue + var node_2d: Node2D = player + if center.distance_to(node_2d.global_position) > radius: + continue + var payload := { + "source": self, + "damage": damage, + "crit_rate": 0.0 + } + for key in extra_payload.keys(): + payload[key] = extra_payload[key] + node_2d.receive_hit(payload) + +func _build_control_payload() -> Dictionary: + return { + "silence_duration": silence_duration, + "slow_duration": slow_duration, + "slow_multiplier": slow_multiplier, + "root_duration": root_duration + } + +func _update_blades(delta: float) -> void: + if not blades_active: + return + blade_time_remaining = maxf(blade_time_remaining - delta, 0.0) + blade_orbit.visible = true + blade_orbit.rotation += delta * 2.8 + for key in blade_hit_cooldowns.keys(): + blade_hit_cooldowns[key] = maxf(float(blade_hit_cooldowns[key]) - delta, 0.0) + for index in range(blade_nodes.size()): + var angle := blade_orbit.rotation + TAU * float(index) / float(max(blade_nodes.size(), 1)) + blade_nodes[index].position = Vector2.RIGHT.rotated(angle) * 34.0 + for player in get_tree().get_nodes_in_group("player"): + if not _is_targetable_player(player): + continue + var node_2d: Node2D = player + if global_position.distance_to(node_2d.global_position) > blade_radius: + continue + var key := node_2d.get_instance_id() + if float(blade_hit_cooldowns.get(key, 0.0)) > 0.0: + continue + blade_hit_cooldowns[key] = blade_tick_interval + node_2d.receive_hit({ + "source": self, + "damage": skill1_damage, + "crit_rate": 0.0 + }) + if blade_time_remaining <= 0.0: + blades_active = false + blade_orbit.visible = false + blade_hit_cooldowns.clear() + +func _show_burst_effect(center: Vector2, radius: float) -> void: + var ring := Line2D.new() + ring.width = 4.0 + ring.closed = true + ring.default_color = Color(0.78, 0.9, 1.0, 0.88) + ring.points = _build_ring_points(radius, 18) + ring.global_position = center + get_tree().current_scene.add_child(ring) + var tween := create_tween() + tween.tween_property(ring, "scale", Vector2.ONE * 1.18, 0.16) + tween.parallel().tween_property(ring, "modulate:a", 0.0, 0.16) + tween.finished.connect(ring.queue_free) + +func _setup_body_visual() -> void: + body_sprite = Sprite2D.new() + body_sprite.texture = TEXTURE_LOADER.load_texture(MAGE_BODY_TEXTURE_PATH) + body_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + body_sprite.centered = true + body_sprite.scale = Vector2.ONE * 0.38 + body.add_child(body_sprite) + body.color = Color(1.0, 1.0, 1.0, 0.0) + +func _set_body_tint(color: Color) -> void: + if body_sprite != null: + body_sprite.self_modulate = color + else: + body.color = color + +func _setup_weapon_visual() -> void: + weapon_sprite = Sprite2D.new() + weapon_sprite.texture = TEXTURE_LOADER.load_texture(MAGE_WEAPON_TEXTURE_PATH) + weapon_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + weapon_sprite.centered = true + weapon_sprite.scale = Vector2.ONE * 0.42 + weapon_sprite.position = Vector2(-34.0, 4.0) + weapon.add_child(weapon_sprite) + +func _animate_weapon_swing(start_degrees: float, end_degrees: float, duration: float) -> void: + weapon_angle_offset = deg_to_rad(start_degrees) + var tween := create_tween() + tween.tween_property(self, "weapon_angle_offset", deg_to_rad(end_degrees), maxf(duration, 0.01)) + +func _update_visuals() -> void: + if target != null and is_instance_valid(target): + var to_target := target.global_position - global_position + if to_target != Vector2.ZERO: + line_direction = to_target.normalized() + var body_tint := Color.WHITE + if silenced_time_remaining > 0.0: + body_tint = Color(0.9, 0.82, 1.0, 1.0) + elif enchant_active: + body_tint = Color(1.0, 0.95, 0.86, 1.0) + elif blades_active: + body_tint = Color(0.9, 0.96, 1.0, 1.0) + _set_body_tint(body_tint) + focus_ring.visible = state == &"basic_attack" + if focus_ring.visible: + focus_ring.rotation = line_direction.angle() + focus_ring.scale = Vector2.ONE * (0.9 + 0.08 * sin(Time.get_ticks_msec() * 0.008)) + burst_ring.default_color = Color(0.78, 0.9, 1.0, 0.9) + enchant_sigil.visible = enchant_active + if enchant_sigil.visible: + enchant_sigil.rotation += 0.08 + enchant_sigil.scale = Vector2.ONE * (0.96 + 0.08 * sin(Time.get_ticks_msec() * 0.008)) + weapon.position = line_direction * 18.0 + Vector2(0.0, -4.0) + weapon.rotation = line_direction.angle() + weapon_angle_offset + projectile_spawner.position = line_direction * 26.0 + +func _build_ring_points(radius: float, steps: int = 16) -> PackedVector2Array: + var points := PackedVector2Array() + for index in range(steps): + var angle := TAU * float(index) / float(steps) + points.append(Vector2.RIGHT.rotated(angle) * radius) + return points + +func _spawn_damage_number(amount: float, is_critical: bool) -> void: + var damage_number := DAMAGE_NUMBER_SCENE.instantiate() + damage_number.position = Vector2(0.0, -40.0) + damage_number.setup(amount, is_critical) + effects_layer.add_child(damage_number) + +func _on_damaged(_amount: float, remaining_hp: float, _source: Node) -> void: + hp = remaining_hp + _set_body_tint(Color(1.0, 0.8, 0.8, 1.0)) + var timer := get_tree().create_timer(0.12) + timer.timeout.connect(func() -> void: + if is_instance_valid(self) and state != &"dead": + _update_visuals() + ) + +func _on_healed(_amount: float, current_hp: float) -> void: + hp = current_hp + +func _on_shield_changed(current_shield: float) -> void: + shield = current_shield + +func _on_died() -> void: + if state == &"dead": + return + hp = 0.0 + state = &"dead" + focus_ring.visible = false + burst_ring.visible = false + enchant_sigil.visible = false + blade_orbit.visible = false + weapon.visible = false + Sfx.play_event(&"boss_generic_dead", global_position) + var timer := get_tree().create_timer(0.55) + timer.timeout.connect(func() -> void: + if is_instance_valid(self): + defeated.emit() + queue_free() + ) diff --git a/actors/bosses/town/mage_boss.gd.uid b/actors/bosses/town/mage_boss.gd.uid new file mode 100644 index 0000000..4acd2d4 --- /dev/null +++ b/actors/bosses/town/mage_boss.gd.uid @@ -0,0 +1 @@ +uid://b2oqo420554hr diff --git a/actors/bosses/town/mage_boss.tscn b/actors/bosses/town/mage_boss.tscn new file mode 100644 index 0000000..c8e6cca --- /dev/null +++ b/actors/bosses/town/mage_boss.tscn @@ -0,0 +1,61 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://actors/bosses/town/mage_boss.gd" id="1"] +[ext_resource type="Script" path="res://combat/health_component.gd" id="2"] + +[node name="MageBoss" type="Node2D"] +script = ExtResource("1") + +[node name="Body" type="Polygon2D" parent="."] +color = Color(0.76, 0.86, 1, 1) +polygon = PackedVector2Array(-24, 30, -18, -6, -8, -34, 8, -34, 18, -6, 24, 30, 0, 42) + +[node name="FocusRing" type="Line2D" parent="."] +visible = false +default_color = Color(0.74, 0.88, 1, 0.72) +width = 3.0 +closed = true +points = PackedVector2Array(64, 0, 46, 46, 0, 64, -46, 46, -64, 0, -46, -46, 0, -64, 46, -46) + +[node name="BurstRing" type="Line2D" parent="."] +visible = false +default_color = Color(0.78, 0.9, 1, 0.9) +width = 3.0 +closed = true +points = PackedVector2Array(72, 0, 52, 52, 0, 72, -52, 52, -72, 0, -52, -52, 0, -72, 52, -52) + +[node name="EnchantSigil" type="Line2D" parent="."] +visible = false +default_color = Color(1, 0.88, 0.68, 0.9) +width = 3.0 +closed = true +points = PackedVector2Array(34, 0, 24, 24, 0, 34, -24, 24, -34, 0, -24, -24, 0, -34, 24, -24) + +[node name="BladeOrbit" type="Node2D" parent="."] +visible = false + +[node name="Blade1" type="Polygon2D" parent="BladeOrbit"] +color = Color(0.82, 0.9, 1, 0.95) +position = Vector2(34, 0) +polygon = PackedVector2Array(-4, 8, 4, 8, 6, -8, 0, -18, -6, -8) + +[node name="Blade2" type="Polygon2D" parent="BladeOrbit"] +color = Color(0.82, 0.9, 1, 0.95) +position = Vector2(-17, 29) +polygon = PackedVector2Array(-4, 8, 4, 8, 6, -8, 0, -18, -6, -8) + +[node name="Blade3" type="Polygon2D" parent="BladeOrbit"] +color = Color(0.82, 0.9, 1, 0.95) +position = Vector2(-17, -29) +polygon = PackedVector2Array(-4, 8, 4, 8, 6, -8, 0, -18, -6, -8) + +[node name="Weapon" type="Node2D" parent="."] +position = Vector2(16, -12) + +[node name="ProjectileSpawner" type="Node2D" parent="."] +position = Vector2(26, -8) + +[node name="HealthComponent" type="Node" parent="."] +script = ExtResource("2") + +[node name="EffectsLayer" type="Node2D" parent="."] diff --git a/actors/bosses/town/ranger_boss.gd b/actors/bosses/town/ranger_boss.gd new file mode 100644 index 0000000..9ffb689 --- /dev/null +++ b/actors/bosses/town/ranger_boss.gd @@ -0,0 +1,532 @@ +extends Node2D + +signal defeated + +const DAMAGE_NUMBER_SCENE := preload("res://effects/damage_number.tscn") +const PIERCING_ARROW_SCENE := preload("res://effects/projectiles/piercing_arrow.tscn") +const MELEE_UTILS := preload("res://combat/melee_utils.gd") +const TEXTURE_LOADER := preload("res://combat/runtime_texture_loader.gd") +const RANGER_BODY_TEXTURE_PATH := "res://actors/bosses/textures/ranger_boss.png" +const RANGER_WEAPON_TEXTURE_PATH := "res://art/final_materials/weapons/boss_weapon_ranger_blade.png" + +@export var max_hp: float = 3600.0 +@export var defense_value: float = 190.0 +@export var move_speed: float = 210.0 +@export var attack_damage: float = 42.0 +@export var attack_range: float = 92.0 +@export var attack_arc_degrees: float = 112.0 +@export var attack_interval: float = 0.82 +@export var skill1_damage: float = 68.0 +@export var skill1_cooldown: float = 5.8 +@export var skill1_cast_duration: float = 0.26 +@export var shadow_step_duration: float = 1.18 +@export var shadow_step_speed: float = 370.0 +@export var shadow_step_slash_damage: float = 58.0 +@export var shadow_step_heal: float = 120.0 +@export var shadow_step_attack_speed_gain: float = 0.28 +@export var shadow_step_speed_gain: float = 0.22 +@export var shadow_step_buff_duration: float = 5.0 +@export var skill2_cooldown: float = 8.4 +@export var assassination_range: float = 340.0 +@export var assassination_dash_speed: float = 980.0 +@export var assassination_stop_distance: float = 48.0 +@export var assassination_damage: float = 96.0 +@export var assassination_strike_duration: float = 0.26 +@export var bleed_damage_per_second: float = 18.0 +@export var bleed_duration: float = 4.0 +@export var execute_health_threshold: float = 0.12 +@export var skill3_cooldown: float = 9.6 + +@onready var body: Polygon2D = $Body +@onready var weapon: Node2D = $Weapon +@onready var aim_ring: Line2D = $AimRing +@onready var assassination_mark: Line2D = $AssassinationMark +@onready var projectile_spawner: Node2D = $ProjectileSpawner +@onready var health_component: Node = $HealthComponent +@onready var effects_layer: Node2D = $EffectsLayer + +var target: Node2D = null +var hp: float = 0.0 +var state: StringName = &"idle" +var state_time: float = 0.0 +var recover_duration: float = 0.0 +var attack_cooldown: float = 0.0 +var skill1_cooldown_remaining: float = 1.8 +var skill2_cooldown_remaining: float = 4.0 +var skill3_cooldown_remaining: float = 6.0 +var action_committed: bool = false +var invulnerable: bool = false +var line_direction: Vector2 = Vector2.RIGHT +var shadow_orbit_direction: float = 1.0 +var shadow_afterimage_timer: float = 0.0 +var shadow_step_buff_time_remaining: float = 0.0 +var current_attack_speed_bonus: float = 0.0 +var current_speed_bonus: float = 0.0 +var active_skill_target: Node2D = null +var damage_applied: bool = false +var silenced_time_remaining: float = 0.0 +var root_time_remaining: float = 0.0 +var slow_time_remaining: float = 0.0 +var slow_factor: float = 1.0 +var body_sprite: Sprite2D = null +var weapon_sprite: Sprite2D = null +var weapon_angle_offset: float = deg_to_rad(56.0) + +func _ready() -> void: + add_to_group("damageable") + health_component.setup(max_hp, defense_value) + health_component.damaged.connect(_on_damaged) + health_component.healed.connect(_on_healed) + health_component.died.connect(_on_died) + hp = max_hp + _setup_body_visual() + _setup_weapon_visual() + aim_ring.visible = false + assassination_mark.visible = false + _update_visuals() + +func bind_player(player: Node2D) -> void: + target = player + +func get_status_title() -> String: + return "Shadow Huntress" + +func get_status_text() -> String: + var buff_text := " | Buffed" if shadow_step_buff_time_remaining > 0.0 else "" + return "HP %d / %d\nATK %.2fs%s\nState: %s" % [ + int(round(hp)), + int(round(max_hp)), + _get_attack_interval(), + buff_text, + String(state) + ] + +func _physics_process(delta: float) -> void: + if state == &"dead": + return + if target == null or not _is_targetable_player(target): + _find_target() + _update_status_timers(delta) + attack_cooldown = maxf(attack_cooldown - delta, 0.0) + skill1_cooldown_remaining = maxf(skill1_cooldown_remaining - delta, 0.0) + skill2_cooldown_remaining = maxf(skill2_cooldown_remaining - delta, 0.0) + skill3_cooldown_remaining = maxf(skill3_cooldown_remaining - delta, 0.0) + if shadow_step_buff_time_remaining > 0.0: + shadow_step_buff_time_remaining = maxf(shadow_step_buff_time_remaining - delta, 0.0) + if shadow_step_buff_time_remaining <= 0.0: + current_attack_speed_bonus = 0.0 + current_speed_bonus = 0.0 + state_time += delta + _update_state(delta) + _update_visuals() + +func receive_hit(payload: Dictionary) -> void: + if invulnerable or state == &"dead": + return + var result: Dictionary = health_component.receive_hit(payload) + var final_damage := float(result.get("damage", 0.0)) + if final_damage > 0.0: + apply_control_effects(payload) + _spawn_damage_number(final_damage, bool(result.get("is_critical", false))) + +func apply_control_effects(payload: Dictionary) -> void: + if payload.has("silence_duration"): + silenced_time_remaining = maxf(silenced_time_remaining, float(payload["silence_duration"])) + if payload.has("root_duration"): + root_time_remaining = maxf(root_time_remaining, float(payload["root_duration"])) + if payload.has("slow_duration"): + slow_time_remaining = maxf(slow_time_remaining, float(payload["slow_duration"])) + slow_factor = minf(slow_factor, float(payload.get("slow_multiplier", 1.0))) + +func _find_target() -> void: + for player in get_tree().get_nodes_in_group("player"): + if _is_targetable_player(player): + target = player + return + target = null + +func _is_targetable_player(candidate: Variant) -> bool: + if candidate == null or not (candidate is Node2D): + return false + var actor: Node2D = candidate + if not is_instance_valid(actor): + return false + if actor.has_method("is_detectable") and not bool(actor.is_detectable()): + return false + var hp_value: Variant = actor.get("hp") + return hp_value == null or float(hp_value) > 0.0 + +func _update_status_timers(delta: float) -> void: + silenced_time_remaining = maxf(silenced_time_remaining - delta, 0.0) + root_time_remaining = maxf(root_time_remaining - delta, 0.0) + if slow_time_remaining > 0.0: + slow_time_remaining = maxf(slow_time_remaining - delta, 0.0) + else: + slow_factor = 1.0 + +func _update_state(delta: float) -> void: + match state: + &"idle": + _process_idle(delta) + &"basic_attack": + _process_basic_attack() + &"skill1_cast": + _process_skill1_cast() + &"skill2_shadow": + _process_skill2_shadow(delta) + &"skill3_dash": + _process_skill3_dash(delta) + &"skill3_strike": + _process_skill3_strike() + &"recover": + _process_recover() + +func _process_idle(delta: float) -> void: + if target == null or not is_instance_valid(target): + return + var to_target := target.global_position - global_position + var distance := to_target.length() + if to_target != Vector2.ZERO: + line_direction = to_target.normalized() + if _can_use_skills() and skill3_cooldown_remaining <= 0.0 and distance <= assassination_range: + _start_skill3() + return + if _can_use_skills() and skill2_cooldown_remaining <= 0.0 and distance <= 220.0: + _start_skill2() + return + if _can_use_skills() and skill1_cooldown_remaining <= 0.0 and distance >= 110.0: + _start_skill1() + return + if attack_cooldown <= 0.0 and distance <= attack_range: + _start_basic_attack() + return + if root_time_remaining > 0.0: + return + var speed := move_speed * (1.0 + current_speed_bonus) * slow_factor + var tangent := Vector2(-line_direction.y, line_direction.x) * shadow_orbit_direction + if distance > attack_range * 0.86: + global_position += (line_direction + tangent * 0.22).normalized() * speed * delta + elif distance < attack_range * 0.58: + global_position -= line_direction * speed * 0.7 * delta + else: + if state_time >= 0.44: + state_time = 0.0 + shadow_orbit_direction *= -1.0 + tangent = Vector2(-line_direction.y, line_direction.x) * shadow_orbit_direction + global_position += tangent.normalized() * speed * 0.42 * delta + +func _start_basic_attack() -> void: + state = &"basic_attack" + state_time = 0.0 + action_committed = false + attack_cooldown = _get_attack_interval() + _animate_weapon_swing(-94.0, 34.0, 0.2) + Sfx.play_event(&"ranger_attack", global_position) + +func _process_basic_attack() -> void: + if not action_committed and state_time >= 0.18: + action_committed = true + _hit_target_in_arc(attack_range, attack_damage, attack_arc_degrees) + _spawn_slash_effect(attack_range, Color(0.88, 1.0, 0.76, 0.92)) + if state_time >= 0.42: + _enter_recover(0.16) + +func _start_skill1() -> void: + state = &"skill1_cast" + state_time = 0.0 + action_committed = false + skill1_cooldown_remaining = skill1_cooldown + aim_ring.visible = true + aim_ring.scale = Vector2.ONE * 0.42 + aim_ring.modulate = Color(0.72, 1.0, 0.86, 0.88) + _animate_weapon_swing(-74.0, 24.0, skill1_cast_duration) + +func _process_skill1_cast() -> void: + if not action_committed and state_time >= skill1_cast_duration * 0.65: + action_committed = true + _fire_piercing_arrow_combo() + if state_time >= skill1_cast_duration + 0.18: + aim_ring.visible = false + _enter_recover(0.18) + +func _fire_piercing_arrow_combo() -> void: + var facing_direction := line_direction if line_direction != Vector2.ZERO else Vector2.RIGHT + _spawn_piercing_arrow(facing_direction, skill1_damage) + if hp <= max_hp * 0.6: + _spawn_piercing_arrow(facing_direction.rotated(deg_to_rad(-14.0)), skill1_damage * 0.6) + _spawn_piercing_arrow(facing_direction.rotated(deg_to_rad(14.0)), skill1_damage * 0.6) + if hp <= max_hp * 0.3: + var timer := get_tree().create_timer(0.12) + timer.timeout.connect(func() -> void: + if is_instance_valid(self) and state != &"dead": + _spawn_piercing_arrow(facing_direction, skill1_damage * 0.85) + ) + +func _start_skill2() -> void: + state = &"skill2_shadow" + state_time = 0.0 + action_committed = false + invulnerable = true + shadow_afterimage_timer = 0.0 + skill2_cooldown_remaining = skill2_cooldown + shadow_orbit_direction *= -1.0 + Sfx.play_event(&"ranger_skill2_roll", global_position) + +func _process_skill2_shadow(delta: float) -> void: + if target == null or not is_instance_valid(target): + invulnerable = false + _enter_recover(0.12) + return + var progress := clampf(state_time / maxf(shadow_step_duration, 0.001), 0.0, 1.0) + var angle := lerpf(-1.1, 1.1, progress) * shadow_orbit_direction + var desired_position := target.global_position + Vector2.RIGHT.rotated(angle) * 126.0 + global_position = global_position.move_toward(desired_position, shadow_step_speed * delta) + line_direction = (target.global_position - global_position).normalized() + if line_direction == Vector2.ZERO: + line_direction = Vector2.RIGHT + shadow_afterimage_timer -= delta + if shadow_afterimage_timer <= 0.0: + shadow_afterimage_timer = 0.05 + _spawn_afterimage() + if not action_committed and state_time >= shadow_step_duration * 0.72: + action_committed = true + _hit_target_in_arc(114.0, shadow_step_slash_damage, 142.0) + _spawn_slash_effect(114.0, Color(0.76, 0.94, 1.0, 0.95)) + if state_time >= shadow_step_duration: + invulnerable = false + current_attack_speed_bonus = shadow_step_attack_speed_gain + current_speed_bonus = shadow_step_speed_gain + shadow_step_buff_time_remaining = shadow_step_buff_duration + health_component.heal(shadow_step_heal) + _enter_recover(0.12) + +func _start_skill3() -> void: + state = &"skill3_dash" + state_time = 0.0 + action_committed = false + damage_applied = false + active_skill_target = target + skill3_cooldown_remaining = skill3_cooldown + assassination_mark.visible = true + assassination_mark.scale = Vector2.ONE * 0.76 + _animate_weapon_swing(-104.0, 12.0, 0.18) + Sfx.play_event(&"ranger_skill3_assassinate", global_position) + +func _process_skill3_dash(delta: float) -> void: + if active_skill_target == null or not is_instance_valid(active_skill_target): + assassination_mark.visible = false + _enter_recover(0.12) + return + var to_target := active_skill_target.global_position - global_position + var distance := to_target.length() + if distance <= assassination_stop_distance or state_time >= 0.42: + state = &"skill3_strike" + state_time = 0.0 + damage_applied = false + return + line_direction = to_target.normalized() + if line_direction == Vector2.ZERO: + line_direction = Vector2.RIGHT + global_position += line_direction * assassination_dash_speed * delta + _spawn_afterimage() + +func _process_skill3_strike() -> void: + if not damage_applied and state_time >= assassination_strike_duration * 0.42: + damage_applied = true + _apply_assassination_damage() + _spawn_slash_effect(124.0, Color(1.0, 0.74, 0.68, 0.96)) + if state_time >= assassination_strike_duration: + active_skill_target = null + assassination_mark.visible = false + _enter_recover(0.18) + +func _apply_assassination_damage() -> void: + if active_skill_target == null or not is_instance_valid(active_skill_target): + return + var damage := assassination_damage + if _can_execute_target(active_skill_target): + damage = 999999.0 + active_skill_target.receive_hit({ + "source": self, + "damage": damage, + "crit_rate": 0.18 + }) + if damage < 999999.0: + _apply_bleed(active_skill_target) + +func _apply_bleed(target_node: Node) -> void: + var ticks := int(floor(bleed_duration)) + for tick in range(ticks): + var timer := get_tree().create_timer(float(tick + 1)) + timer.timeout.connect(func() -> void: + if is_instance_valid(self) and is_instance_valid(target_node) and target_node.has_method("receive_hit"): + target_node.receive_hit({ + "source": self, + "damage": bleed_damage_per_second, + "crit_rate": 0.0 + }) + ) + +func _can_execute_target(target_node: Node) -> bool: + if target_node == null: + return false + var target_hp := float(target_node.get("hp")) + var target_max_hp := float(target_node.get("max_hp")) + return target_max_hp > 0.0 and target_hp <= target_max_hp * execute_health_threshold + +func _enter_recover(duration: float) -> void: + state = &"recover" + state_time = 0.0 + recover_duration = duration + aim_ring.visible = false + +func _process_recover() -> void: + if state_time >= recover_duration: + state = &"idle" + state_time = 0.0 + +func _can_use_skills() -> bool: + return silenced_time_remaining <= 0.0 + +func _get_attack_interval() -> float: + return attack_interval / (1.0 + current_attack_speed_bonus) + +func _hit_target_in_arc(radius: float, damage: float, arc_degrees: float) -> void: + if target == null or not is_instance_valid(target): + return + if not MELEE_UTILS.is_point_in_arc(global_position, line_direction, target.global_position, radius, arc_degrees): + return + target.receive_hit({ + "source": self, + "damage": damage, + "crit_rate": 0.12 + }) + +func _spawn_piercing_arrow(direction: Vector2, damage: float) -> void: + var arrow := PIERCING_ARROW_SCENE.instantiate() + arrow.global_position = projectile_spawner.global_position + get_tree().current_scene.add_child(arrow) + arrow.setup(self, direction, damage, 0.1) + +func _spawn_slash_effect(radius: float, color: Color) -> void: + var slash := Line2D.new() + slash.width = 12.0 + slash.antialiased = true + slash.default_color = color + slash.position = line_direction * 12.0 + slash.rotation = line_direction.angle() + slash.points = PackedVector2Array([ + Vector2(-6.0, -20.0), + Vector2(radius * 0.28, -radius * 0.16), + Vector2(radius * 0.82, 0.0), + Vector2(radius * 0.28, radius * 0.16), + Vector2(-6.0, 20.0) + ]) + effects_layer.add_child(slash) + var tween := create_tween() + tween.tween_property(slash, "scale", Vector2.ONE * 1.14, 0.12) + tween.parallel().tween_property(slash, "modulate:a", 0.0, 0.12) + tween.finished.connect(slash.queue_free) + +func _spawn_afterimage() -> void: + var ghost := Polygon2D.new() + ghost.polygon = body.polygon + ghost.color = Color(0.64, 1.0, 0.86, 0.18) + ghost.global_position = global_position + ghost.rotation = body.rotation + ghost.scale = body.scale + get_tree().current_scene.add_child(ghost) + var tween := create_tween() + tween.tween_property(ghost, "modulate:a", 0.0, 0.18) + tween.parallel().tween_property(ghost, "scale", body.scale * 0.92, 0.18) + tween.finished.connect(ghost.queue_free) + +func _setup_body_visual() -> void: + body_sprite = Sprite2D.new() + body_sprite.texture = TEXTURE_LOADER.load_texture(RANGER_BODY_TEXTURE_PATH) + body_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + body_sprite.centered = true + body_sprite.scale = Vector2.ONE * 0.38 + body.add_child(body_sprite) + body.color = Color(1.0, 1.0, 1.0, 0.0) + +func _set_body_tint(color: Color) -> void: + if body_sprite != null: + body_sprite.self_modulate = color + else: + body.color = color + +func _setup_weapon_visual() -> void: + weapon_sprite = Sprite2D.new() + weapon_sprite.texture = TEXTURE_LOADER.load_texture(RANGER_WEAPON_TEXTURE_PATH) + weapon_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + weapon_sprite.centered = true + weapon_sprite.scale = Vector2.ONE * 0.46 + weapon_sprite.position = Vector2(-32.0, 5.0) + weapon.add_child(weapon_sprite) + +func _animate_weapon_swing(start_degrees: float, end_degrees: float, duration: float) -> void: + weapon_angle_offset = deg_to_rad(start_degrees) + var tween := create_tween() + tween.tween_property(self, "weapon_angle_offset", deg_to_rad(end_degrees), maxf(duration, 0.01)) + +func _update_visuals() -> void: + if target != null and is_instance_valid(target): + var to_target := target.global_position - global_position + if to_target != Vector2.ZERO: + line_direction = to_target.normalized() + var body_tint := Color.WHITE + if silenced_time_remaining > 0.0: + body_tint = Color(0.9, 0.82, 1.0, 1.0) + elif shadow_step_buff_time_remaining > 0.0: + body_tint = Color(1.0, 0.94, 0.84, 1.0) + _set_body_tint(body_tint) + body.modulate = Color(1.0, 1.0, 1.0, 0.36) if invulnerable else Color.WHITE + aim_ring.visible = state == &"skill1_cast" + if aim_ring.visible: + aim_ring.rotation = line_direction.angle() + aim_ring.scale = Vector2.ONE * (0.82 + 0.18 * minf(state_time / maxf(skill1_cast_duration, 0.01), 1.0)) + aim_ring.modulate = Color(0.72, 1.0, 0.86, 0.78) + assassination_mark.visible = active_skill_target != null and is_instance_valid(active_skill_target) and state != &"dead" + if assassination_mark.visible: + assassination_mark.global_position = active_skill_target.global_position + assassination_mark.rotation += 0.16 + assassination_mark.modulate = Color(1.0, 0.48, 0.42, 0.9) + weapon.position = line_direction * 18.0 + Vector2(0.0, -2.0) + weapon.rotation = line_direction.angle() + weapon_angle_offset + projectile_spawner.position = line_direction * 28.0 + +func _spawn_damage_number(amount: float, is_critical: bool) -> void: + var damage_number := DAMAGE_NUMBER_SCENE.instantiate() + damage_number.position = Vector2(0.0, -38.0) + damage_number.setup(amount, is_critical) + effects_layer.add_child(damage_number) + +func _on_damaged(_amount: float, remaining_hp: float, _source: Node) -> void: + hp = remaining_hp + _set_body_tint(Color(1.0, 0.8, 0.8, 1.0)) + var timer := get_tree().create_timer(0.12) + timer.timeout.connect(func() -> void: + if is_instance_valid(self) and state != &"dead": + _update_visuals() + ) + +func _on_healed(_amount: float, current_hp: float) -> void: + hp = current_hp + +func _on_died() -> void: + if state == &"dead": + return + hp = 0.0 + invulnerable = true + state = &"dead" + aim_ring.visible = false + assassination_mark.visible = false + weapon.visible = false + Sfx.play_event(&"boss_generic_dead", global_position) + var timer := get_tree().create_timer(0.5) + timer.timeout.connect(func() -> void: + if is_instance_valid(self): + defeated.emit() + queue_free() + ) diff --git a/actors/bosses/town/ranger_boss.gd.uid b/actors/bosses/town/ranger_boss.gd.uid new file mode 100644 index 0000000..258b706 --- /dev/null +++ b/actors/bosses/town/ranger_boss.gd.uid @@ -0,0 +1 @@ +uid://c5bnjp2q4x2r2 diff --git a/actors/bosses/town/ranger_boss.tscn b/actors/bosses/town/ranger_boss.tscn new file mode 100644 index 0000000..229b5ed --- /dev/null +++ b/actors/bosses/town/ranger_boss.tscn @@ -0,0 +1,37 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://actors/bosses/town/ranger_boss.gd" id="1"] +[ext_resource type="Script" path="res://combat/health_component.gd" id="2"] + +[node name="RangerBoss" type="Node2D"] +script = ExtResource("1") + +[node name="Body" type="Polygon2D" parent="."] +color = Color(0.74, 0.96, 0.82, 1) +polygon = PackedVector2Array(-24, 28, -16, -8, -6, -32, 8, -36, 20, -10, 24, 26, 2, 40) + +[node name="Weapon" type="Node2D" parent="."] +position = Vector2(14, -7) + +[node name="AimRing" type="Line2D" parent="."] +visible = false +default_color = Color(0.72, 1, 0.86, 0.78) +width = 3.0 +closed = true +points = PackedVector2Array(76, 0, 54, 54, 0, 76, -54, 54, -76, 0, -54, -54, 0, -76, 54, -54) + +[node name="AssassinationMark" type="Line2D" parent="."] +visible = false +default_color = Color(1, 0.48, 0.42, 0.9) +width = 3.0 +closed = true +points = PackedVector2Array(32, 0, 22, 22, 0, 32, -22, 22, -32, 0, -22, -22, 0, -32, 22, -22) + +[node name="ProjectileSpawner" type="Node2D" parent="."] +position = Vector2(28, -6) + +[node name="HealthComponent" type="Node" parent="."] +script = ExtResource("2") + +[node name="EffectsLayer" type="Node2D" parent="."] + diff --git a/actors/bosses/town/twin_princes_boss.gd b/actors/bosses/town/twin_princes_boss.gd index 14a7206..33f8bdc 100644 --- a/actors/bosses/town/twin_princes_boss.gd +++ b/actors/bosses/town/twin_princes_boss.gd @@ -4,6 +4,11 @@ signal defeated const DAMAGE_NUMBER_SCENE := preload("res://effects/damage_number.tscn") const ROYAL_BOLT_SCENE := preload("res://effects/projectiles/royal_bolt.tscn") +const TEXTURE_LOADER := preload("res://combat/runtime_texture_loader.gd") +const TWIN_PHASE1_BODY_TEXTURE_PATH := "res://actors/bosses/textures/twin_first_boss.png" +const TWIN_PHASE2_BODY_TEXTURE_PATH := "res://actors/bosses/textures/twin_second_boss.png" +const TWIN_PHASE1_WEAPON_TEXTURE_PATH := "res://art/final_materials/weapons/boss_weapon_twin_first_sword.png" +const TWIN_PHASE2_WEAPON_TEXTURE_PATH := "res://art/final_materials/weapons/boss_weapon_twin_second_sword.png" @export var phase1_hp: float = 4000.0 @export var phase2_hp: float = 4000.0 @@ -42,6 +47,9 @@ var root_time_remaining: float = 0.0 var slow_time_remaining: float = 0.0 var slow_factor: float = 1.0 var desperation_active: bool = false +var body_sprite: Sprite2D = null +var spear_sprite: Sprite2D = null +var spear_angle_offset: float = deg_to_rad(66.0) func _ready() -> void: add_to_group("damageable") @@ -50,6 +58,9 @@ func _ready() -> void: health_component.setup(max_hp, defense_value) health_component.damaged.connect(_on_damaged) health_component.died.connect(_on_died) + _setup_body_visual() + _setup_weapon_visual() + _refresh_phase_visuals() teleport_marker.visible = false charge_line.visible = false phase_ring.visible = false @@ -360,16 +371,50 @@ func _update_desperation_state() -> void: _show_intent_text("Desperate", Color(1.0, 0.66, 0.50, 1.0), global_position, 0.94) Sfx.play_event(&"boss_twin_barrage", global_position, 2.0) +func _setup_body_visual() -> void: + body_sprite = Sprite2D.new() + body_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + body_sprite.centered = true + body_sprite.scale = Vector2.ONE * 0.39 + body.add_child(body_sprite) + body.color = Color(1.0, 1.0, 1.0, 0.0) + +func _set_body_tint(color: Color) -> void: + if body_sprite != null: + body_sprite.self_modulate = color + else: + body.color = color + +func _setup_weapon_visual() -> void: + spear_sprite = Sprite2D.new() + spear_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + spear_sprite.centered = true + spear_sprite.scale = Vector2.ONE * 0.46 + spear_sprite.position = Vector2(-40.0, 2.0) + spear.add_child(spear_sprite) + spear.color = Color(1.0, 1.0, 1.0, 0.0) + +func _refresh_phase_visuals() -> void: + if body_sprite != null: + body_sprite.texture = TEXTURE_LOADER.load_texture(TWIN_PHASE1_BODY_TEXTURE_PATH if current_phase == 1 else TWIN_PHASE2_BODY_TEXTURE_PATH) + if spear_sprite != null: + spear_sprite.texture = TEXTURE_LOADER.load_texture(TWIN_PHASE1_WEAPON_TEXTURE_PATH if current_phase == 1 else TWIN_PHASE2_WEAPON_TEXTURE_PATH) + func _update_visuals() -> void: var base_color := Color(0.84, 0.82, 0.88, 1.0) if current_phase == 1 else Color(0.95, 0.76, 0.58, 1.0) if silenced_time_remaining > 0.0: base_color = Color(0.72, 0.64, 0.92, 1.0) elif desperation_active: base_color = Color(1.0, 0.62, 0.46, 1.0) - body.color = base_color + _set_body_tint(base_color) phase_ring.default_color = Color(1.0, 0.64, 0.42, 0.9) if desperation_active else (Color(1.0, 0.82, 0.56, 0.85) if current_phase == 2 else Color(0.82, 0.86, 1.0, 0.8)) + var aim_direction := charge_direction if charge_direction != Vector2.ZERO else Vector2.RIGHT if target != null and is_instance_valid(target): - spear.rotation = (target.global_position - global_position).angle() + var to_target := target.global_position - global_position + if to_target != Vector2.ZERO: + aim_direction = to_target.normalized() + spear.position = aim_direction * 20.0 + Vector2(0.0, 4.0) + spear.rotation = aim_direction.angle() + spear_angle_offset var pulse := 0.82 + 0.18 * sin(Time.get_ticks_msec() * 0.01) if teleport_marker.visible: teleport_marker.scale = Vector2.ONE * (0.92 + 0.08 * pulse) @@ -398,7 +443,7 @@ func _show_intent_text(label_text: String, color_value: Color, world_position: V func _on_damaged(_amount: float, remaining_hp: float, _source: Node) -> void: hp = remaining_hp - body.color = Color(1.0, 0.58, 0.58, 1.0) + _set_body_tint(Color(1.0, 0.8, 0.8, 1.0)) var timer := get_tree().create_timer(0.12) timer.timeout.connect(func() -> void: if is_instance_valid(self) and state != &"dead": @@ -412,6 +457,7 @@ func _on_died() -> void: desperation_active = false max_hp = phase2_hp hp = max_hp + _refresh_phase_visuals() invulnerable = true teleport_cooldown = 1.0 spear_cooldown = 2.4 diff --git a/art/final_materials/deaths/judicator_boss_dead.png b/art/final_materials/deaths/judicator_boss_dead.png new file mode 100644 index 0000000..9187d85 Binary files /dev/null and b/art/final_materials/deaths/judicator_boss_dead.png differ diff --git a/art/final_materials/deaths/judicator_boss_dead.png.import b/art/final_materials/deaths/judicator_boss_dead.png.import new file mode 100644 index 0000000..a08206d --- /dev/null +++ b/art/final_materials/deaths/judicator_boss_dead.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bq462325rs6xv" +path="res://.godot/imported/judicator_boss_dead.png-712d195f35f003c9ccbf2bb499f863a5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/final_materials/deaths/judicator_boss_dead.png" +dest_files=["res://.godot/imported/judicator_boss_dead.png-712d195f35f003c9ccbf2bb499f863a5.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 diff --git a/art/final_materials/deaths/twin_first_boss_dead.png b/art/final_materials/deaths/twin_first_boss_dead.png new file mode 100644 index 0000000..d454d43 Binary files /dev/null and b/art/final_materials/deaths/twin_first_boss_dead.png differ diff --git a/art/final_materials/deaths/twin_first_boss_dead.png.import b/art/final_materials/deaths/twin_first_boss_dead.png.import new file mode 100644 index 0000000..9b83131 --- /dev/null +++ b/art/final_materials/deaths/twin_first_boss_dead.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djvbu0o2r6ney" +path="res://.godot/imported/twin_first_boss_dead.png-7fe53ff134a64a8dd7b8e0925c216085.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/final_materials/deaths/twin_first_boss_dead.png" +dest_files=["res://.godot/imported/twin_first_boss_dead.png-7fe53ff134a64a8dd7b8e0925c216085.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 diff --git a/art/final_materials/deaths/twin_second_boss_dead.png b/art/final_materials/deaths/twin_second_boss_dead.png new file mode 100644 index 0000000..c357a57 Binary files /dev/null and b/art/final_materials/deaths/twin_second_boss_dead.png differ diff --git a/art/final_materials/deaths/twin_second_boss_dead.png.import b/art/final_materials/deaths/twin_second_boss_dead.png.import new file mode 100644 index 0000000..389eddd --- /dev/null +++ b/art/final_materials/deaths/twin_second_boss_dead.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8yjwhq6xjha3" +path="res://.godot/imported/twin_second_boss_dead.png-e78bd9bfa6e95412845bb8acffc10dde.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/final_materials/deaths/twin_second_boss_dead.png" +dest_files=["res://.godot/imported/twin_second_boss_dead.png-e78bd9bfa6e95412845bb8acffc10dde.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 diff --git a/art/final_materials/weapons/boss_weapon_emperor_sword.png b/art/final_materials/weapons/boss_weapon_emperor_sword.png new file mode 100644 index 0000000..58cd53e Binary files /dev/null and b/art/final_materials/weapons/boss_weapon_emperor_sword.png differ diff --git a/art/final_materials/weapons/boss_weapon_emperor_sword.png.import b/art/final_materials/weapons/boss_weapon_emperor_sword.png.import new file mode 100644 index 0000000..fa8dea1 --- /dev/null +++ b/art/final_materials/weapons/boss_weapon_emperor_sword.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ct7c32mjlwje4" +path="res://.godot/imported/boss_weapon_emperor_sword.png-499e3c39bd565a426762ad2537ccc7ec.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/final_materials/weapons/boss_weapon_emperor_sword.png" +dest_files=["res://.godot/imported/boss_weapon_emperor_sword.png-499e3c39bd565a426762ad2537ccc7ec.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 diff --git a/art/final_materials/weapons/boss_weapon_judicator_sword.png b/art/final_materials/weapons/boss_weapon_judicator_sword.png new file mode 100644 index 0000000..5cc54fa Binary files /dev/null and b/art/final_materials/weapons/boss_weapon_judicator_sword.png differ diff --git a/art/final_materials/weapons/boss_weapon_judicator_sword.png.import b/art/final_materials/weapons/boss_weapon_judicator_sword.png.import new file mode 100644 index 0000000..cbe4f9b --- /dev/null +++ b/art/final_materials/weapons/boss_weapon_judicator_sword.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qs0jev6tnbmj" +path="res://.godot/imported/boss_weapon_judicator_sword.png-7d80b160e5c1cddb37fd7f9a29bcf2a1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/final_materials/weapons/boss_weapon_judicator_sword.png" +dest_files=["res://.godot/imported/boss_weapon_judicator_sword.png-7d80b160e5c1cddb37fd7f9a29bcf2a1.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 diff --git a/art/final_materials/weapons/boss_weapon_mage_staff.png b/art/final_materials/weapons/boss_weapon_mage_staff.png new file mode 100644 index 0000000..a61d6d4 Binary files /dev/null and b/art/final_materials/weapons/boss_weapon_mage_staff.png differ diff --git a/art/final_materials/weapons/boss_weapon_mage_staff.png.import b/art/final_materials/weapons/boss_weapon_mage_staff.png.import new file mode 100644 index 0000000..19047f6 --- /dev/null +++ b/art/final_materials/weapons/boss_weapon_mage_staff.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://pvi4hrpnsbih" +path="res://.godot/imported/boss_weapon_mage_staff.png-ca5d154d89137774fe65ebdd40b71354.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/final_materials/weapons/boss_weapon_mage_staff.png" +dest_files=["res://.godot/imported/boss_weapon_mage_staff.png-ca5d154d89137774fe65ebdd40b71354.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 diff --git a/art/final_materials/weapons/boss_weapon_ranger_blade.png b/art/final_materials/weapons/boss_weapon_ranger_blade.png new file mode 100644 index 0000000..996d9ad Binary files /dev/null and b/art/final_materials/weapons/boss_weapon_ranger_blade.png differ diff --git a/art/final_materials/weapons/boss_weapon_ranger_blade.png.import b/art/final_materials/weapons/boss_weapon_ranger_blade.png.import new file mode 100644 index 0000000..bbaf4e6 --- /dev/null +++ b/art/final_materials/weapons/boss_weapon_ranger_blade.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1s8t7eig3tgu" +path="res://.godot/imported/boss_weapon_ranger_blade.png-68934ed8a3175870869d78cd4ae68a63.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/final_materials/weapons/boss_weapon_ranger_blade.png" +dest_files=["res://.godot/imported/boss_weapon_ranger_blade.png-68934ed8a3175870869d78cd4ae68a63.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 diff --git a/art/final_materials/weapons/boss_weapon_twin_first_sword.png b/art/final_materials/weapons/boss_weapon_twin_first_sword.png new file mode 100644 index 0000000..abebf9e Binary files /dev/null and b/art/final_materials/weapons/boss_weapon_twin_first_sword.png differ diff --git a/art/final_materials/weapons/boss_weapon_twin_first_sword.png.import b/art/final_materials/weapons/boss_weapon_twin_first_sword.png.import new file mode 100644 index 0000000..a972cab --- /dev/null +++ b/art/final_materials/weapons/boss_weapon_twin_first_sword.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcj6cte1sfyy3" +path="res://.godot/imported/boss_weapon_twin_first_sword.png-1a012c4de2074dfcf3f91601279aea01.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/final_materials/weapons/boss_weapon_twin_first_sword.png" +dest_files=["res://.godot/imported/boss_weapon_twin_first_sword.png-1a012c4de2074dfcf3f91601279aea01.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 diff --git a/art/final_materials/weapons/boss_weapon_twin_second_sword.png b/art/final_materials/weapons/boss_weapon_twin_second_sword.png new file mode 100644 index 0000000..3579497 Binary files /dev/null and b/art/final_materials/weapons/boss_weapon_twin_second_sword.png differ diff --git a/art/final_materials/weapons/boss_weapon_twin_second_sword.png.import b/art/final_materials/weapons/boss_weapon_twin_second_sword.png.import new file mode 100644 index 0000000..d07fb4c --- /dev/null +++ b/art/final_materials/weapons/boss_weapon_twin_second_sword.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://i58gq10jufcw" +path="res://.godot/imported/boss_weapon_twin_second_sword.png-485f59bf40dc7b2dfa00301c6b916fea.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/final_materials/weapons/boss_weapon_twin_second_sword.png" +dest_files=["res://.godot/imported/boss_weapon_twin_second_sword.png-485f59bf40dc7b2dfa00301c6b916fea.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 diff --git a/effects/projectiles/arcane_bolt.gd b/effects/projectiles/arcane_bolt.gd index 997cda7..03f0a1a 100644 --- a/effects/projectiles/arcane_bolt.gd +++ b/effects/projectiles/arcane_bolt.gd @@ -12,6 +12,7 @@ var damage: float = 90.0 var crit_rate: float = 0.0 var source: Node = null var attack_name: StringName = &"attack" +var extra_payload: Dictionary = {} var expired: bool = false var pulse_time: float = 0.0 var trail_timer: float = 0.0 @@ -21,12 +22,13 @@ func _ready() -> void: body_entered.connect(_on_body_entered) area_entered.connect(_on_area_entered) -func setup(owner_actor: Node, travel_direction: Vector2, hit_damage: float, hit_crit_rate: float, attack_label: StringName = &"attack") -> void: +func setup(owner_actor: Node, travel_direction: Vector2, hit_damage: float, hit_crit_rate: float, attack_label: StringName = &"attack", hit_extra_payload: Dictionary = {}) -> void: source = owner_actor direction = travel_direction.normalized() if travel_direction != Vector2.ZERO else Vector2.RIGHT damage = hit_damage crit_rate = hit_crit_rate attack_name = attack_label + extra_payload = hit_extra_payload.duplicate(true) bolt.polygon = _pixel_bolt_polygon() rotation = direction.angle() var timer := get_tree().create_timer(lifetime) @@ -62,7 +64,10 @@ func _try_hit(target: Variant) -> void: if not target.has_method("receive_hit"): return expired = true - target.receive_hit(AccessoryManager.build_hit_payload(source, attack_name, damage, crit_rate)) + var payload := AccessoryManager.build_hit_payload(source, attack_name, damage, crit_rate) + for key in extra_payload.keys(): + payload[key] = extra_payload[key] + target.receive_hit(payload) if source != null and source.has_method("on_attack_landed"): source.on_attack_landed(attack_name, target) _spawn_hit_flash() diff --git a/tests/smoke_run_flow.gd b/tests/smoke_run_flow.gd index d2f1a18..b6f7409 100644 --- a/tests/smoke_run_flow.gd +++ b/tests/smoke_run_flow.gd @@ -212,7 +212,8 @@ func _run() -> void: quit(1) return - world.encounter_index = world.ENCOUNTER_SCENES.size() - 1 + # The final chamber is an extra encounter slot backed by FINAL_BOSS_SCENES. + world.encounter_index = world._encounter_count() - 1 var stub_encounter := Node.new() world.add_child(stub_encounter) world.current_encounter = stub_encounter diff --git a/tools/boss_preview_capture.gd b/tools/boss_preview_capture.gd new file mode 100644 index 0000000..ae53169 --- /dev/null +++ b/tools/boss_preview_capture.gd @@ -0,0 +1,125 @@ +extends Node2D + +const PREVIEW_SIZE := Vector2i(960, 960) +const PREVIEW_BG := Color(0.08, 0.09, 0.11, 1.0) +const OUTPUT_DIR := "res://tools/generated_previews" +const CAMERA_ZOOM := Vector2.ONE * 0.12 +const BOSS_SPECS := [ + {"id": "judicator", "scene": preload("res://actors/bosses/town/judicator_boss.tscn"), "target_offset": Vector2(220.0, -40.0)}, + {"id": "twin_princes", "scene": preload("res://actors/bosses/town/twin_princes_boss.tscn"), "target_offset": Vector2(220.0, -30.0)}, + {"id": "emperor", "scene": preload("res://actors/bosses/town/emperor_boss.tscn"), "target_offset": Vector2(220.0, -20.0)}, + {"id": "ranger", "scene": preload("res://actors/bosses/town/ranger_boss.tscn"), "target_offset": Vector2(220.0, -20.0)}, + {"id": "mage", "scene": preload("res://actors/bosses/town/mage_boss.tscn"), "target_offset": Vector2(220.0, -20.0)} +] + +class PreviewTarget: + extends Node2D + + var hp: float = 100.0 + var max_hp: float = 100.0 + + func is_detectable() -> bool: + return true + + func receive_hit(_payload: Dictionary) -> void: + pass + +var capture_root: Node2D +var background: ColorRect +var camera: Camera2D + +func _ready() -> void: + _setup_view() + await get_tree().process_frame + await _capture_all() + +func _setup_view() -> void: + background = ColorRect.new() + background.color = PREVIEW_BG + background.size = Vector2(PREVIEW_SIZE) + add_child(background) + + capture_root = Node2D.new() + capture_root.position = Vector2(PREVIEW_SIZE) * 0.5 + Vector2(0.0, 72.0) + add_child(capture_root) + + camera = Camera2D.new() + camera.enabled = true + camera.position = Vector2(PREVIEW_SIZE) * 0.5 + camera.zoom = CAMERA_ZOOM + add_child(camera) + +func _capture_all() -> void: + var absolute_output_dir := ProjectSettings.globalize_path(OUTPUT_DIR) + print("Boss preview output: ", absolute_output_dir) + DirAccess.make_dir_recursive_absolute(absolute_output_dir) + for spec in BOSS_SPECS: + await _capture_spec(spec) + get_tree().quit() + +func _capture_spec(spec: Dictionary) -> void: + for child in capture_root.get_children(): + child.queue_free() + await get_tree().process_frame + + var boss := (spec["scene"] as PackedScene).instantiate() + var target := PreviewTarget.new() + target.position = Vector2(spec.get("target_offset", Vector2(220.0, -20.0))) + target.add_to_group("player") + capture_root.add_child(target) + capture_root.add_child(boss) + + if boss.has_method("bind_player"): + boss.bind_player(target) + if boss.has_method("_update_visuals"): + boss.call("_update_visuals") + + await get_tree().process_frame + await RenderingServer.frame_post_draw + + _save_capture(String(spec["id"])) + _configure_weapon_debug(boss) + await get_tree().process_frame + await RenderingServer.frame_post_draw + _save_capture("%s_weapon_debug" % String(spec["id"])) + +func _save_capture(name: String) -> void: + var image := get_viewport().get_texture().get_image() + var output_path := "%s/%s.png" % [OUTPUT_DIR, name] + var absolute_output_path := ProjectSettings.globalize_path(output_path) + print("Saving boss preview: ", absolute_output_path) + image.save_png(absolute_output_path) + +func _configure_weapon_debug(boss: Node) -> void: + var body_node := boss.get_node_or_null("Body") + if body_node is CanvasItem: + (body_node as CanvasItem).modulate = Color(1.0, 1.0, 1.0, 0.18) + for child in body_node.get_children(): + if child is CanvasItem: + (child as CanvasItem).modulate = Color(1.0, 1.0, 1.0, 0.18) + + for weapon_name in ["Weapon", "Sword", "Spear"]: + var weapon_node := boss.get_node_or_null(weapon_name) + if weapon_node == null: + continue + if weapon_node is CanvasItem: + (weapon_node as CanvasItem).modulate = Color(1.0, 0.34, 0.34, 1.0) + for child in weapon_node.get_children(): + if child is CanvasItem: + (child as CanvasItem).modulate = Color(1.0, 0.34, 0.34, 1.0) + _add_pivot_cross(weapon_node) + +func _add_pivot_cross(parent_node: Node) -> void: + if not (parent_node is Node2D): + return + var cross_a := Line2D.new() + cross_a.default_color = Color(0.42, 1.0, 1.0, 1.0) + cross_a.width = 2.0 + cross_a.points = PackedVector2Array([Vector2(-10.0, 0.0), Vector2(10.0, 0.0)]) + parent_node.add_child(cross_a) + + var cross_b := Line2D.new() + cross_b.default_color = Color(0.42, 1.0, 1.0, 1.0) + cross_b.width = 2.0 + cross_b.points = PackedVector2Array([Vector2(0.0, -10.0), Vector2(0.0, 10.0)]) + parent_node.add_child(cross_b) diff --git a/tools/boss_preview_capture.gd.uid b/tools/boss_preview_capture.gd.uid new file mode 100644 index 0000000..9c347fe --- /dev/null +++ b/tools/boss_preview_capture.gd.uid @@ -0,0 +1 @@ +uid://c07l7c8184qp8 diff --git a/tools/boss_preview_capture.tscn b/tools/boss_preview_capture.tscn new file mode 100644 index 0000000..cbd6e6b --- /dev/null +++ b/tools/boss_preview_capture.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tools/boss_preview_capture.gd" id="1"] + +[node name="BossPreviewCapture" type="Node2D"] +script = ExtResource("1") diff --git a/tools/character_debug_world.gd b/tools/character_debug_world.gd index 30cd935..8cab0e7 100644 --- a/tools/character_debug_world.gd +++ b/tools/character_debug_world.gd @@ -12,6 +12,9 @@ const APPRENTICE_SCENE := preload("res://actors/enemy/apprentice_mage_enemy.tscn const ARCANIST_SCENE := preload("res://actors/enemy/arcanist_enemy.tscn") const JUDICATOR_BOSS_SCENE := preload("res://actors/bosses/town/judicator_boss.tscn") const TWIN_PRINCES_BOSS_SCENE := preload("res://actors/bosses/town/twin_princes_boss.tscn") +const RANGER_BOSS_SCENE := preload("res://actors/bosses/town/ranger_boss.tscn") +const MAGE_BOSS_SCENE := preload("res://actors/bosses/town/mage_boss.tscn") +const EMPEROR_BOSS_SCENE := preload("res://actors/bosses/town/emperor_boss.tscn") const PLAYER_SCENES := { &"knight": KNIGHT_SCENE, @@ -27,7 +30,10 @@ const ENEMY_SCENES := { &"apprentice": APPRENTICE_SCENE, &"arcanist": ARCANIST_SCENE, &"judicator_boss": JUDICATOR_BOSS_SCENE, - &"twin_princes_boss": TWIN_PRINCES_BOSS_SCENE + &"twin_princes_boss": TWIN_PRINCES_BOSS_SCENE, + &"ranger_boss": RANGER_BOSS_SCENE, + &"mage_boss": MAGE_BOSS_SCENE, + &"emperor_boss": EMPEROR_BOSS_SCENE } @onready var player_spawn: Marker2D = $PlayerSpawn diff --git a/tools/generated_previews/emperor.png b/tools/generated_previews/emperor.png new file mode 100644 index 0000000..adcc681 Binary files /dev/null and b/tools/generated_previews/emperor.png differ diff --git a/tools/generated_previews/emperor.png.import b/tools/generated_previews/emperor.png.import new file mode 100644 index 0000000..2732c14 --- /dev/null +++ b/tools/generated_previews/emperor.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4o0apr3rrs8d" +path="res://.godot/imported/emperor.png-4736952c9d5997f8a2e41b2c7b64999c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/emperor.png" +dest_files=["res://.godot/imported/emperor.png-4736952c9d5997f8a2e41b2c7b64999c.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 diff --git a/tools/generated_previews/emperor_weapon_debug.png b/tools/generated_previews/emperor_weapon_debug.png new file mode 100644 index 0000000..11113a2 Binary files /dev/null and b/tools/generated_previews/emperor_weapon_debug.png differ diff --git a/tools/generated_previews/emperor_weapon_debug.png.import b/tools/generated_previews/emperor_weapon_debug.png.import new file mode 100644 index 0000000..2d8d899 --- /dev/null +++ b/tools/generated_previews/emperor_weapon_debug.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0jpas8mlosl0" +path="res://.godot/imported/emperor_weapon_debug.png-6d34d4637bb300415b1c757c4786b323.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/emperor_weapon_debug.png" +dest_files=["res://.godot/imported/emperor_weapon_debug.png-6d34d4637bb300415b1c757c4786b323.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 diff --git a/tools/generated_previews/judicator.png b/tools/generated_previews/judicator.png new file mode 100644 index 0000000..29243d8 Binary files /dev/null and b/tools/generated_previews/judicator.png differ diff --git a/tools/generated_previews/judicator.png.import b/tools/generated_previews/judicator.png.import new file mode 100644 index 0000000..c2795df --- /dev/null +++ b/tools/generated_previews/judicator.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cl8ybkbawa2ti" +path="res://.godot/imported/judicator.png-6f8bc24c56ede1be0b49fe07eb019990.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/judicator.png" +dest_files=["res://.godot/imported/judicator.png-6f8bc24c56ede1be0b49fe07eb019990.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 diff --git a/tools/generated_previews/judicator_weapon_debug.png b/tools/generated_previews/judicator_weapon_debug.png new file mode 100644 index 0000000..76a97b8 Binary files /dev/null and b/tools/generated_previews/judicator_weapon_debug.png differ diff --git a/tools/generated_previews/judicator_weapon_debug.png.import b/tools/generated_previews/judicator_weapon_debug.png.import new file mode 100644 index 0000000..bb0b04f --- /dev/null +++ b/tools/generated_previews/judicator_weapon_debug.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bd35salmi4fbb" +path="res://.godot/imported/judicator_weapon_debug.png-75332592b0d0142f295ddad0f1c4b400.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/judicator_weapon_debug.png" +dest_files=["res://.godot/imported/judicator_weapon_debug.png-75332592b0d0142f295ddad0f1c4b400.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 diff --git a/tools/generated_previews/mage.png b/tools/generated_previews/mage.png new file mode 100644 index 0000000..0748f71 Binary files /dev/null and b/tools/generated_previews/mage.png differ diff --git a/tools/generated_previews/mage.png.import b/tools/generated_previews/mage.png.import new file mode 100644 index 0000000..1dc8a3e --- /dev/null +++ b/tools/generated_previews/mage.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cx8vdv41vll62" +path="res://.godot/imported/mage.png-c500e331161db9c47ab4da3ba3f58bc7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/mage.png" +dest_files=["res://.godot/imported/mage.png-c500e331161db9c47ab4da3ba3f58bc7.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 diff --git a/tools/generated_previews/mage_weapon_debug.png b/tools/generated_previews/mage_weapon_debug.png new file mode 100644 index 0000000..d69728b Binary files /dev/null and b/tools/generated_previews/mage_weapon_debug.png differ diff --git a/tools/generated_previews/mage_weapon_debug.png.import b/tools/generated_previews/mage_weapon_debug.png.import new file mode 100644 index 0000000..c9ec528 --- /dev/null +++ b/tools/generated_previews/mage_weapon_debug.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cssosukpqfdj5" +path="res://.godot/imported/mage_weapon_debug.png-624ec3546fa1ae5d57602be296abf20a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/mage_weapon_debug.png" +dest_files=["res://.godot/imported/mage_weapon_debug.png-624ec3546fa1ae5d57602be296abf20a.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 diff --git a/tools/generated_previews/ranger.png b/tools/generated_previews/ranger.png new file mode 100644 index 0000000..b98ccb9 Binary files /dev/null and b/tools/generated_previews/ranger.png differ diff --git a/tools/generated_previews/ranger.png.import b/tools/generated_previews/ranger.png.import new file mode 100644 index 0000000..53f1228 --- /dev/null +++ b/tools/generated_previews/ranger.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dtmi6kri7t4an" +path="res://.godot/imported/ranger.png-6d0091ce7519342efb8e731ec1875838.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/ranger.png" +dest_files=["res://.godot/imported/ranger.png-6d0091ce7519342efb8e731ec1875838.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 diff --git a/tools/generated_previews/ranger_weapon_debug.png b/tools/generated_previews/ranger_weapon_debug.png new file mode 100644 index 0000000..9a727b0 Binary files /dev/null and b/tools/generated_previews/ranger_weapon_debug.png differ diff --git a/tools/generated_previews/ranger_weapon_debug.png.import b/tools/generated_previews/ranger_weapon_debug.png.import new file mode 100644 index 0000000..9106592 --- /dev/null +++ b/tools/generated_previews/ranger_weapon_debug.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbj15qck2lfr6" +path="res://.godot/imported/ranger_weapon_debug.png-627471f45866a714ed5a49c2c8fbe73c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/ranger_weapon_debug.png" +dest_files=["res://.godot/imported/ranger_weapon_debug.png-627471f45866a714ed5a49c2c8fbe73c.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 diff --git a/tools/generated_previews/twin_princes.png b/tools/generated_previews/twin_princes.png new file mode 100644 index 0000000..2efe473 Binary files /dev/null and b/tools/generated_previews/twin_princes.png differ diff --git a/tools/generated_previews/twin_princes.png.import b/tools/generated_previews/twin_princes.png.import new file mode 100644 index 0000000..ca32510 --- /dev/null +++ b/tools/generated_previews/twin_princes.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dhub7qonwjro6" +path="res://.godot/imported/twin_princes.png-7d106840e9aed329f07c1cba457ff43c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/twin_princes.png" +dest_files=["res://.godot/imported/twin_princes.png-7d106840e9aed329f07c1cba457ff43c.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 diff --git a/tools/generated_previews/twin_princes_weapon_debug.png b/tools/generated_previews/twin_princes_weapon_debug.png new file mode 100644 index 0000000..6337539 Binary files /dev/null and b/tools/generated_previews/twin_princes_weapon_debug.png differ diff --git a/tools/generated_previews/twin_princes_weapon_debug.png.import b/tools/generated_previews/twin_princes_weapon_debug.png.import new file mode 100644 index 0000000..231a4c4 --- /dev/null +++ b/tools/generated_previews/twin_princes_weapon_debug.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8lgtoo7gdi34" +path="res://.godot/imported/twin_princes_weapon_debug.png-607986cc446a3d661cf535f3bbbff28f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/twin_princes_weapon_debug.png" +dest_files=["res://.godot/imported/twin_princes_weapon_debug.png-607986cc446a3d661cf535f3bbbff28f.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 diff --git a/tools/generated_previews/weapon_debug_zoom/emperor_weapon_debug_zoom.png b/tools/generated_previews/weapon_debug_zoom/emperor_weapon_debug_zoom.png new file mode 100644 index 0000000..ff2dbb8 Binary files /dev/null and b/tools/generated_previews/weapon_debug_zoom/emperor_weapon_debug_zoom.png differ diff --git a/tools/generated_previews/weapon_debug_zoom/emperor_weapon_debug_zoom.png.import b/tools/generated_previews/weapon_debug_zoom/emperor_weapon_debug_zoom.png.import new file mode 100644 index 0000000..4385616 --- /dev/null +++ b/tools/generated_previews/weapon_debug_zoom/emperor_weapon_debug_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://beompct2gm2y0" +path="res://.godot/imported/emperor_weapon_debug_zoom.png-f7b489b30a02423c53454393132e7cd8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/weapon_debug_zoom/emperor_weapon_debug_zoom.png" +dest_files=["res://.godot/imported/emperor_weapon_debug_zoom.png-f7b489b30a02423c53454393132e7cd8.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 diff --git a/tools/generated_previews/weapon_debug_zoom/judicator_weapon_debug_zoom.png b/tools/generated_previews/weapon_debug_zoom/judicator_weapon_debug_zoom.png new file mode 100644 index 0000000..5749b90 Binary files /dev/null and b/tools/generated_previews/weapon_debug_zoom/judicator_weapon_debug_zoom.png differ diff --git a/tools/generated_previews/weapon_debug_zoom/judicator_weapon_debug_zoom.png.import b/tools/generated_previews/weapon_debug_zoom/judicator_weapon_debug_zoom.png.import new file mode 100644 index 0000000..cd5631c --- /dev/null +++ b/tools/generated_previews/weapon_debug_zoom/judicator_weapon_debug_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qkbdiyeonq02" +path="res://.godot/imported/judicator_weapon_debug_zoom.png-15c6f2099855ff821dc5c542ac7fbadd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/weapon_debug_zoom/judicator_weapon_debug_zoom.png" +dest_files=["res://.godot/imported/judicator_weapon_debug_zoom.png-15c6f2099855ff821dc5c542ac7fbadd.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 diff --git a/tools/generated_previews/weapon_debug_zoom/mage_weapon_debug_zoom.png b/tools/generated_previews/weapon_debug_zoom/mage_weapon_debug_zoom.png new file mode 100644 index 0000000..27ce40f Binary files /dev/null and b/tools/generated_previews/weapon_debug_zoom/mage_weapon_debug_zoom.png differ diff --git a/tools/generated_previews/weapon_debug_zoom/mage_weapon_debug_zoom.png.import b/tools/generated_previews/weapon_debug_zoom/mage_weapon_debug_zoom.png.import new file mode 100644 index 0000000..c5c152f --- /dev/null +++ b/tools/generated_previews/weapon_debug_zoom/mage_weapon_debug_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0bgci6hn8uvj" +path="res://.godot/imported/mage_weapon_debug_zoom.png-bfb64a9e781de52db23cd9af19cf053f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/weapon_debug_zoom/mage_weapon_debug_zoom.png" +dest_files=["res://.godot/imported/mage_weapon_debug_zoom.png-bfb64a9e781de52db23cd9af19cf053f.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 diff --git a/tools/generated_previews/weapon_debug_zoom/ranger_weapon_debug_zoom.png b/tools/generated_previews/weapon_debug_zoom/ranger_weapon_debug_zoom.png new file mode 100644 index 0000000..1d7af5f Binary files /dev/null and b/tools/generated_previews/weapon_debug_zoom/ranger_weapon_debug_zoom.png differ diff --git a/tools/generated_previews/weapon_debug_zoom/ranger_weapon_debug_zoom.png.import b/tools/generated_previews/weapon_debug_zoom/ranger_weapon_debug_zoom.png.import new file mode 100644 index 0000000..6cfd677 --- /dev/null +++ b/tools/generated_previews/weapon_debug_zoom/ranger_weapon_debug_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b84hopd1yerum" +path="res://.godot/imported/ranger_weapon_debug_zoom.png-d401c7f3bdcd8411a9aa4a89b1154f85.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/weapon_debug_zoom/ranger_weapon_debug_zoom.png" +dest_files=["res://.godot/imported/ranger_weapon_debug_zoom.png-d401c7f3bdcd8411a9aa4a89b1154f85.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 diff --git a/tools/generated_previews/weapon_debug_zoom/twin_princes_weapon_debug_zoom.png b/tools/generated_previews/weapon_debug_zoom/twin_princes_weapon_debug_zoom.png new file mode 100644 index 0000000..2f06639 Binary files /dev/null and b/tools/generated_previews/weapon_debug_zoom/twin_princes_weapon_debug_zoom.png differ diff --git a/tools/generated_previews/weapon_debug_zoom/twin_princes_weapon_debug_zoom.png.import b/tools/generated_previews/weapon_debug_zoom/twin_princes_weapon_debug_zoom.png.import new file mode 100644 index 0000000..6f7ed0a --- /dev/null +++ b/tools/generated_previews/weapon_debug_zoom/twin_princes_weapon_debug_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://huvagtx2e4co" +path="res://.godot/imported/twin_princes_weapon_debug_zoom.png-a446524c6904cd16275d816725bcacf3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/weapon_debug_zoom/twin_princes_weapon_debug_zoom.png" +dest_files=["res://.godot/imported/twin_princes_weapon_debug_zoom.png-a446524c6904cd16275d816725bcacf3.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 diff --git a/tools/generated_previews/zoomed/emperor_zoom.png b/tools/generated_previews/zoomed/emperor_zoom.png new file mode 100644 index 0000000..e87c7b1 Binary files /dev/null and b/tools/generated_previews/zoomed/emperor_zoom.png differ diff --git a/tools/generated_previews/zoomed/emperor_zoom.png.import b/tools/generated_previews/zoomed/emperor_zoom.png.import new file mode 100644 index 0000000..98efe6f --- /dev/null +++ b/tools/generated_previews/zoomed/emperor_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7a4hate02ddy" +path="res://.godot/imported/emperor_zoom.png-42e2ae10bb86a9991b2e74d380e0dd27.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed/emperor_zoom.png" +dest_files=["res://.godot/imported/emperor_zoom.png-42e2ae10bb86a9991b2e74d380e0dd27.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 diff --git a/tools/generated_previews/zoomed/judicator_zoom.png b/tools/generated_previews/zoomed/judicator_zoom.png new file mode 100644 index 0000000..0183ddf Binary files /dev/null and b/tools/generated_previews/zoomed/judicator_zoom.png differ diff --git a/tools/generated_previews/zoomed/judicator_zoom.png.import b/tools/generated_previews/zoomed/judicator_zoom.png.import new file mode 100644 index 0000000..4a93e1a --- /dev/null +++ b/tools/generated_previews/zoomed/judicator_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bawnqpymb0dd" +path="res://.godot/imported/judicator_zoom.png-e1eb481fdb47adb6c6b3a3c76079d543.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed/judicator_zoom.png" +dest_files=["res://.godot/imported/judicator_zoom.png-e1eb481fdb47adb6c6b3a3c76079d543.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 diff --git a/tools/generated_previews/zoomed/mage_zoom.png b/tools/generated_previews/zoomed/mage_zoom.png new file mode 100644 index 0000000..5dbb89a Binary files /dev/null and b/tools/generated_previews/zoomed/mage_zoom.png differ diff --git a/tools/generated_previews/zoomed/mage_zoom.png.import b/tools/generated_previews/zoomed/mage_zoom.png.import new file mode 100644 index 0000000..33cc9ff --- /dev/null +++ b/tools/generated_previews/zoomed/mage_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxvk0mqrl2a1h" +path="res://.godot/imported/mage_zoom.png-034a57da9f26c31209a4918e615aa5f0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed/mage_zoom.png" +dest_files=["res://.godot/imported/mage_zoom.png-034a57da9f26c31209a4918e615aa5f0.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 diff --git a/tools/generated_previews/zoomed/ranger_zoom.png b/tools/generated_previews/zoomed/ranger_zoom.png new file mode 100644 index 0000000..e1c6b09 Binary files /dev/null and b/tools/generated_previews/zoomed/ranger_zoom.png differ diff --git a/tools/generated_previews/zoomed/ranger_zoom.png.import b/tools/generated_previews/zoomed/ranger_zoom.png.import new file mode 100644 index 0000000..65f3be2 --- /dev/null +++ b/tools/generated_previews/zoomed/ranger_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvsxngcqgs2pd" +path="res://.godot/imported/ranger_zoom.png-9d7893fd1b4c8479f4b4fb9188e1b432.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed/ranger_zoom.png" +dest_files=["res://.godot/imported/ranger_zoom.png-9d7893fd1b4c8479f4b4fb9188e1b432.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 diff --git a/tools/generated_previews/zoomed/twin_princes_zoom.png b/tools/generated_previews/zoomed/twin_princes_zoom.png new file mode 100644 index 0000000..2f3eb6f Binary files /dev/null and b/tools/generated_previews/zoomed/twin_princes_zoom.png differ diff --git a/tools/generated_previews/zoomed/twin_princes_zoom.png.import b/tools/generated_previews/zoomed/twin_princes_zoom.png.import new file mode 100644 index 0000000..3a253fc --- /dev/null +++ b/tools/generated_previews/zoomed/twin_princes_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dwxnlmvg5tvxu" +path="res://.godot/imported/twin_princes_zoom.png-2cc8f469506ea9a65521eb135a9730f4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed/twin_princes_zoom.png" +dest_files=["res://.godot/imported/twin_princes_zoom.png-2cc8f469506ea9a65521eb135a9730f4.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 diff --git a/tools/generated_previews/zoomed2/emperor_zoom.png b/tools/generated_previews/zoomed2/emperor_zoom.png new file mode 100644 index 0000000..e3d0b96 Binary files /dev/null and b/tools/generated_previews/zoomed2/emperor_zoom.png differ diff --git a/tools/generated_previews/zoomed2/emperor_zoom.png.import b/tools/generated_previews/zoomed2/emperor_zoom.png.import new file mode 100644 index 0000000..30010cd --- /dev/null +++ b/tools/generated_previews/zoomed2/emperor_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dj5s3okh1vc8s" +path="res://.godot/imported/emperor_zoom.png-3efca100f9891f70bc80b5750168ea90.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed2/emperor_zoom.png" +dest_files=["res://.godot/imported/emperor_zoom.png-3efca100f9891f70bc80b5750168ea90.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 diff --git a/tools/generated_previews/zoomed2/judicator_zoom.png b/tools/generated_previews/zoomed2/judicator_zoom.png new file mode 100644 index 0000000..fed97cd Binary files /dev/null and b/tools/generated_previews/zoomed2/judicator_zoom.png differ diff --git a/tools/generated_previews/zoomed2/judicator_zoom.png.import b/tools/generated_previews/zoomed2/judicator_zoom.png.import new file mode 100644 index 0000000..b927ff6 --- /dev/null +++ b/tools/generated_previews/zoomed2/judicator_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://baya6njt5mben" +path="res://.godot/imported/judicator_zoom.png-3aac68e4cddc6c42d811b3001289fbb6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed2/judicator_zoom.png" +dest_files=["res://.godot/imported/judicator_zoom.png-3aac68e4cddc6c42d811b3001289fbb6.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 diff --git a/tools/generated_previews/zoomed2/mage_zoom.png b/tools/generated_previews/zoomed2/mage_zoom.png new file mode 100644 index 0000000..b5af4f8 Binary files /dev/null and b/tools/generated_previews/zoomed2/mage_zoom.png differ diff --git a/tools/generated_previews/zoomed2/mage_zoom.png.import b/tools/generated_previews/zoomed2/mage_zoom.png.import new file mode 100644 index 0000000..f71f196 --- /dev/null +++ b/tools/generated_previews/zoomed2/mage_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ctme365qrh25h" +path="res://.godot/imported/mage_zoom.png-effab6344e92fa746e0071984ede8eec.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed2/mage_zoom.png" +dest_files=["res://.godot/imported/mage_zoom.png-effab6344e92fa746e0071984ede8eec.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 diff --git a/tools/generated_previews/zoomed2/ranger_zoom.png b/tools/generated_previews/zoomed2/ranger_zoom.png new file mode 100644 index 0000000..d225912 Binary files /dev/null and b/tools/generated_previews/zoomed2/ranger_zoom.png differ diff --git a/tools/generated_previews/zoomed2/ranger_zoom.png.import b/tools/generated_previews/zoomed2/ranger_zoom.png.import new file mode 100644 index 0000000..e83b9f8 --- /dev/null +++ b/tools/generated_previews/zoomed2/ranger_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d4cudukrvdn38" +path="res://.godot/imported/ranger_zoom.png-28a18353ac52f288fe214b7afe90d544.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed2/ranger_zoom.png" +dest_files=["res://.godot/imported/ranger_zoom.png-28a18353ac52f288fe214b7afe90d544.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 diff --git a/tools/generated_previews/zoomed2/twin_princes_zoom.png b/tools/generated_previews/zoomed2/twin_princes_zoom.png new file mode 100644 index 0000000..15b75c8 Binary files /dev/null and b/tools/generated_previews/zoomed2/twin_princes_zoom.png differ diff --git a/tools/generated_previews/zoomed2/twin_princes_zoom.png.import b/tools/generated_previews/zoomed2/twin_princes_zoom.png.import new file mode 100644 index 0000000..9845961 --- /dev/null +++ b/tools/generated_previews/zoomed2/twin_princes_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dlwaojjyr0qao" +path="res://.godot/imported/twin_princes_zoom.png-77a26d7867feec366e28758f4dd87a49.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed2/twin_princes_zoom.png" +dest_files=["res://.godot/imported/twin_princes_zoom.png-77a26d7867feec366e28758f4dd87a49.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 diff --git a/tools/generated_previews/zoomed3/emperor_zoom.png b/tools/generated_previews/zoomed3/emperor_zoom.png new file mode 100644 index 0000000..0a7f3b0 Binary files /dev/null and b/tools/generated_previews/zoomed3/emperor_zoom.png differ diff --git a/tools/generated_previews/zoomed3/emperor_zoom.png.import b/tools/generated_previews/zoomed3/emperor_zoom.png.import new file mode 100644 index 0000000..0cbc424 --- /dev/null +++ b/tools/generated_previews/zoomed3/emperor_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddl40tty18ed4" +path="res://.godot/imported/emperor_zoom.png-6c41f4af3dfe04ec8db382a7c6695061.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed3/emperor_zoom.png" +dest_files=["res://.godot/imported/emperor_zoom.png-6c41f4af3dfe04ec8db382a7c6695061.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 diff --git a/tools/generated_previews/zoomed3/judicator_zoom.png b/tools/generated_previews/zoomed3/judicator_zoom.png new file mode 100644 index 0000000..a867a6c Binary files /dev/null and b/tools/generated_previews/zoomed3/judicator_zoom.png differ diff --git a/tools/generated_previews/zoomed3/judicator_zoom.png.import b/tools/generated_previews/zoomed3/judicator_zoom.png.import new file mode 100644 index 0000000..ae30532 --- /dev/null +++ b/tools/generated_previews/zoomed3/judicator_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://idijeix647vm" +path="res://.godot/imported/judicator_zoom.png-ea5dd1a944f10165c644d9f732d658eb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed3/judicator_zoom.png" +dest_files=["res://.godot/imported/judicator_zoom.png-ea5dd1a944f10165c644d9f732d658eb.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 diff --git a/tools/generated_previews/zoomed3/mage_zoom.png b/tools/generated_previews/zoomed3/mage_zoom.png new file mode 100644 index 0000000..608e773 Binary files /dev/null and b/tools/generated_previews/zoomed3/mage_zoom.png differ diff --git a/tools/generated_previews/zoomed3/mage_zoom.png.import b/tools/generated_previews/zoomed3/mage_zoom.png.import new file mode 100644 index 0000000..eb8f2cf --- /dev/null +++ b/tools/generated_previews/zoomed3/mage_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0mnslrornkte" +path="res://.godot/imported/mage_zoom.png-17615584e4ff9dbcbc597459d291db74.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed3/mage_zoom.png" +dest_files=["res://.godot/imported/mage_zoom.png-17615584e4ff9dbcbc597459d291db74.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 diff --git a/tools/generated_previews/zoomed3/ranger_zoom.png b/tools/generated_previews/zoomed3/ranger_zoom.png new file mode 100644 index 0000000..123242a Binary files /dev/null and b/tools/generated_previews/zoomed3/ranger_zoom.png differ diff --git a/tools/generated_previews/zoomed3/ranger_zoom.png.import b/tools/generated_previews/zoomed3/ranger_zoom.png.import new file mode 100644 index 0000000..b5de0bc --- /dev/null +++ b/tools/generated_previews/zoomed3/ranger_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2nk4d0ptreor" +path="res://.godot/imported/ranger_zoom.png-edb6e0b6f760dbe60a0bddcecd60190a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed3/ranger_zoom.png" +dest_files=["res://.godot/imported/ranger_zoom.png-edb6e0b6f760dbe60a0bddcecd60190a.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 diff --git a/tools/generated_previews/zoomed3/twin_princes_zoom.png b/tools/generated_previews/zoomed3/twin_princes_zoom.png new file mode 100644 index 0000000..fb78a52 Binary files /dev/null and b/tools/generated_previews/zoomed3/twin_princes_zoom.png differ diff --git a/tools/generated_previews/zoomed3/twin_princes_zoom.png.import b/tools/generated_previews/zoomed3/twin_princes_zoom.png.import new file mode 100644 index 0000000..194bee7 --- /dev/null +++ b/tools/generated_previews/zoomed3/twin_princes_zoom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djq4irlxwqvaw" +path="res://.godot/imported/twin_princes_zoom.png-11e28a39077198703b1171d86546aaef.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/generated_previews/zoomed3/twin_princes_zoom.png" +dest_files=["res://.godot/imported/twin_princes_zoom.png-11e28a39077198703b1171d86546aaef.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 diff --git a/tools/texture_debug/emperor_body.png b/tools/texture_debug/emperor_body.png new file mode 100644 index 0000000..786d31d Binary files /dev/null and b/tools/texture_debug/emperor_body.png differ diff --git a/tools/texture_debug/emperor_body.png.import b/tools/texture_debug/emperor_body.png.import new file mode 100644 index 0000000..b346c8b --- /dev/null +++ b/tools/texture_debug/emperor_body.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://j5cu3qih61bu" +path="res://.godot/imported/emperor_body.png-67ea249c983c6f56348cd2f2ad04f9a8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/emperor_body.png" +dest_files=["res://.godot/imported/emperor_body.png-67ea249c983c6f56348cd2f2ad04f9a8.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 diff --git a/tools/texture_debug/emperor_weapon.png b/tools/texture_debug/emperor_weapon.png new file mode 100644 index 0000000..e3587bf Binary files /dev/null and b/tools/texture_debug/emperor_weapon.png differ diff --git a/tools/texture_debug/emperor_weapon.png.import b/tools/texture_debug/emperor_weapon.png.import new file mode 100644 index 0000000..ca663c1 --- /dev/null +++ b/tools/texture_debug/emperor_weapon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bfcxquqc8h82x" +path="res://.godot/imported/emperor_weapon.png-c56b6aaf5a958700ec01396b95345671.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/emperor_weapon.png" +dest_files=["res://.godot/imported/emperor_weapon.png-c56b6aaf5a958700ec01396b95345671.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 diff --git a/tools/texture_debug/judicator_body.png b/tools/texture_debug/judicator_body.png new file mode 100644 index 0000000..c286444 Binary files /dev/null and b/tools/texture_debug/judicator_body.png differ diff --git a/tools/texture_debug/judicator_body.png.import b/tools/texture_debug/judicator_body.png.import new file mode 100644 index 0000000..83c3967 --- /dev/null +++ b/tools/texture_debug/judicator_body.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpdcqjaupuke0" +path="res://.godot/imported/judicator_body.png-b78c1fde6b4b17ffb8629a81c72e3bfe.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/judicator_body.png" +dest_files=["res://.godot/imported/judicator_body.png-b78c1fde6b4b17ffb8629a81c72e3bfe.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 diff --git a/tools/texture_debug/judicator_weapon.png b/tools/texture_debug/judicator_weapon.png new file mode 100644 index 0000000..43893da Binary files /dev/null and b/tools/texture_debug/judicator_weapon.png differ diff --git a/tools/texture_debug/judicator_weapon.png.import b/tools/texture_debug/judicator_weapon.png.import new file mode 100644 index 0000000..3b7f80a --- /dev/null +++ b/tools/texture_debug/judicator_weapon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xbrj80p0ssbu" +path="res://.godot/imported/judicator_weapon.png-58c0dd4fcb3862f66eee32ad91f90dde.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/judicator_weapon.png" +dest_files=["res://.godot/imported/judicator_weapon.png-58c0dd4fcb3862f66eee32ad91f90dde.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 diff --git a/tools/texture_debug/mage_body.png b/tools/texture_debug/mage_body.png new file mode 100644 index 0000000..3ba3b3d Binary files /dev/null and b/tools/texture_debug/mage_body.png differ diff --git a/tools/texture_debug/mage_body.png.import b/tools/texture_debug/mage_body.png.import new file mode 100644 index 0000000..6756b61 --- /dev/null +++ b/tools/texture_debug/mage_body.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgjkpj85hdtii" +path="res://.godot/imported/mage_body.png-dd20eced81a805eb4699b5995a45d90c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/mage_body.png" +dest_files=["res://.godot/imported/mage_body.png-dd20eced81a805eb4699b5995a45d90c.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 diff --git a/tools/texture_debug/mage_weapon.png b/tools/texture_debug/mage_weapon.png new file mode 100644 index 0000000..3f7e5af Binary files /dev/null and b/tools/texture_debug/mage_weapon.png differ diff --git a/tools/texture_debug/mage_weapon.png.import b/tools/texture_debug/mage_weapon.png.import new file mode 100644 index 0000000..86f43fc --- /dev/null +++ b/tools/texture_debug/mage_weapon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbpxagpoticpa" +path="res://.godot/imported/mage_weapon.png-99871c223ac6ae64416ac017dfca8486.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/mage_weapon.png" +dest_files=["res://.godot/imported/mage_weapon.png-99871c223ac6ae64416ac017dfca8486.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 diff --git a/tools/texture_debug/ranger_body.png b/tools/texture_debug/ranger_body.png new file mode 100644 index 0000000..5ebdf3d Binary files /dev/null and b/tools/texture_debug/ranger_body.png differ diff --git a/tools/texture_debug/ranger_body.png.import b/tools/texture_debug/ranger_body.png.import new file mode 100644 index 0000000..d92115f --- /dev/null +++ b/tools/texture_debug/ranger_body.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cjubh7mb3u8hy" +path="res://.godot/imported/ranger_body.png-83d3923be1f928257711e45e471500e3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/ranger_body.png" +dest_files=["res://.godot/imported/ranger_body.png-83d3923be1f928257711e45e471500e3.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 diff --git a/tools/texture_debug/ranger_weapon.png b/tools/texture_debug/ranger_weapon.png new file mode 100644 index 0000000..5150bf9 Binary files /dev/null and b/tools/texture_debug/ranger_weapon.png differ diff --git a/tools/texture_debug/ranger_weapon.png.import b/tools/texture_debug/ranger_weapon.png.import new file mode 100644 index 0000000..6559637 --- /dev/null +++ b/tools/texture_debug/ranger_weapon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6p4xjvtwc146" +path="res://.godot/imported/ranger_weapon.png-2a8d59f106426a240911ab09724d7563.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/ranger_weapon.png" +dest_files=["res://.godot/imported/ranger_weapon.png-2a8d59f106426a240911ab09724d7563.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 diff --git a/tools/texture_debug/twin_phase1_body.png b/tools/texture_debug/twin_phase1_body.png new file mode 100644 index 0000000..b97bd24 Binary files /dev/null and b/tools/texture_debug/twin_phase1_body.png differ diff --git a/tools/texture_debug/twin_phase1_body.png.import b/tools/texture_debug/twin_phase1_body.png.import new file mode 100644 index 0000000..c83b549 --- /dev/null +++ b/tools/texture_debug/twin_phase1_body.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmawovv18qpe8" +path="res://.godot/imported/twin_phase1_body.png-0ceb890c25e5de89a1ab695194194d73.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/twin_phase1_body.png" +dest_files=["res://.godot/imported/twin_phase1_body.png-0ceb890c25e5de89a1ab695194194d73.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 diff --git a/tools/texture_debug/twin_phase1_weapon.png b/tools/texture_debug/twin_phase1_weapon.png new file mode 100644 index 0000000..eb4ad7b Binary files /dev/null and b/tools/texture_debug/twin_phase1_weapon.png differ diff --git a/tools/texture_debug/twin_phase1_weapon.png.import b/tools/texture_debug/twin_phase1_weapon.png.import new file mode 100644 index 0000000..b711b09 --- /dev/null +++ b/tools/texture_debug/twin_phase1_weapon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cigocti7y7e4k" +path="res://.godot/imported/twin_phase1_weapon.png-25b8f952c97da48b35dbbedeb1b11603.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/twin_phase1_weapon.png" +dest_files=["res://.godot/imported/twin_phase1_weapon.png-25b8f952c97da48b35dbbedeb1b11603.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 diff --git a/tools/texture_debug/twin_phase2_body.png b/tools/texture_debug/twin_phase2_body.png new file mode 100644 index 0000000..6054c4f Binary files /dev/null and b/tools/texture_debug/twin_phase2_body.png differ diff --git a/tools/texture_debug/twin_phase2_body.png.import b/tools/texture_debug/twin_phase2_body.png.import new file mode 100644 index 0000000..0d2111b --- /dev/null +++ b/tools/texture_debug/twin_phase2_body.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1us481s024mo" +path="res://.godot/imported/twin_phase2_body.png-766412a2c1123dc99f190fe452852f43.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/twin_phase2_body.png" +dest_files=["res://.godot/imported/twin_phase2_body.png-766412a2c1123dc99f190fe452852f43.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 diff --git a/tools/texture_debug/twin_phase2_weapon.png b/tools/texture_debug/twin_phase2_weapon.png new file mode 100644 index 0000000..454e1f9 Binary files /dev/null and b/tools/texture_debug/twin_phase2_weapon.png differ diff --git a/tools/texture_debug/twin_phase2_weapon.png.import b/tools/texture_debug/twin_phase2_weapon.png.import new file mode 100644 index 0000000..1d50e43 --- /dev/null +++ b/tools/texture_debug/twin_phase2_weapon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7vi7cidhuxny" +path="res://.godot/imported/twin_phase2_weapon.png-e9bd295dedd4be659853ea13f499daf3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tools/texture_debug/twin_phase2_weapon.png" +dest_files=["res://.godot/imported/twin_phase2_weapon.png-e9bd295dedd4be659853ea13f499daf3.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 diff --git a/ui/debug_enemy_select.gd b/ui/debug_enemy_select.gd index e3d2585..d1050af 100644 --- a/ui/debug_enemy_select.gd +++ b/ui/debug_enemy_select.gd @@ -13,7 +13,10 @@ const ENEMY_OPTIONS := [ {"id": &"apprentice", "label": "学徒法师"}, {"id": &"arcanist", "label": "秘术师"}, {"id": &"judicator_boss", "label": "Boss:审判官"}, - {"id": &"twin_princes_boss", "label": "Boss:双子王子"} + {"id": &"twin_princes_boss", "label": "Boss:双子王子"}, + {"id": &"ranger_boss", "label": "Boss:影猎者"}, + {"id": &"mage_boss", "label": "Boss:大奥术师"}, + {"id": &"emperor_boss", "label": "Boss:皇帝"} ] @@ -66,7 +69,7 @@ func _build_ui() -> void: content.add_child(hint) elite_check = CheckBox.new() - elite_check.text = "小怪使用精英版本" + elite_check.text = "小怪使用精英版" content.add_child(elite_check) for option in ENEMY_OPTIONS: diff --git a/world.gd b/world.gd index 9ce1920..ef6cb29 100644 --- a/world.gd +++ b/world.gd @@ -10,6 +10,9 @@ const DAMAGE_NUMBER_SCENE := preload("res://effects/damage_number.tscn") const RUN_PICKUP_SCRIPT := preload("res://systems/pickups/run_pickup.gd") const WORLD_HEALTH_BAR_SCRIPT := preload("res://ui/world_health_bar.gd") const INVENTORY_PANEL_SCRIPT := preload("res://ui/inventory_panel.gd") +const RANGER_BOSS_SCENE := preload("res://actors/bosses/town/ranger_boss.tscn") +const MAGE_BOSS_SCENE := preload("res://actors/bosses/town/mage_boss.tscn") +const EMPEROR_BOSS_SCENE := preload("res://actors/bosses/town/emperor_boss.tscn") const ENCOUNTER_SCENES := [ preload("res://actors/encounters/town_mob_encounter.tscn"), preload("res://actors/encounters/town_mob_encounter.tscn"), @@ -20,6 +23,11 @@ const ENCOUNTER_SCENES := [ preload("res://actors/bosses/town/royal_guard_formation.tscn"), preload("res://actors/bosses/town/twin_princes_boss.tscn") ] +const FINAL_BOSS_SCENES := [ + EMPEROR_BOSS_SCENE, + RANGER_BOSS_SCENE, + MAGE_BOSS_SCENE +] const RELIC_REROLL_COST := 12 const HIT_FEEDBACK_COOLDOWN_MSEC := 55 const GATE_WALK_FINISH_DISTANCE := 10.0 @@ -73,7 +81,7 @@ func _ready() -> void: _build_inventory_panel() if get_tree() != null and not get_tree().node_added.is_connected(_on_tree_node_added): get_tree().node_added.connect(_on_tree_node_added) - RunDirector.configure_event_count(maxi(ENCOUNTER_SCENES.size() - 1, 1)) + RunDirector.configure_event_count(maxi(_encounter_count() - 1, 1)) if character_select != null: character_select.character_selected.connect(_on_character_selected) if character_select.has_signal("audio_requested"): @@ -301,7 +309,7 @@ func _unhandled_input(event: InputEvent) -> void: func _on_character_selected(character_id: StringName) -> void: _cancel_scheduled_title_music() _clear_player_auto_walk() - RunDirector.configure_event_count(maxi(ENCOUNTER_SCENES.size() - 1, 1)) + RunDirector.configure_event_count(maxi(_encounter_count() - 1, 1)) if result_screen != null: result_screen.visible = false if audio_settings_panel != null and audio_settings_panel.has_method("hide_panel"): @@ -368,7 +376,7 @@ func _start_next_encounter() -> void: return_pause_after_settings_panel = false active_encounter_prep = RunDirector.consume_pending_encounter_prep() encounter_index += 1 - if encounter_index >= ENCOUNTER_SCENES.size(): + if encounter_index >= _encounter_count(): active_encounter_prep.clear() _complete_run_victory() return @@ -380,7 +388,7 @@ func _start_next_encounter() -> void: func _begin_current_encounter() -> void: _activate_map_room(encounter_index) _play_audio_profile_for_encounter(encounter_index) - current_encounter = ENCOUNTER_SCENES[encounter_index].instantiate() + current_encounter = _encounter_scene_for_index(encounter_index).instantiate() current_encounter.position = encounter_marker.position encounter_root.add_child(current_encounter) if not active_encounter_prep.is_empty(): @@ -452,7 +460,7 @@ func _on_encounter_defeated() -> void: if bool(active_encounter_prep.get("clear_shield_on_end", false)): RunEffects.clear_shield(player_character) current_encounter = null - var defeated_final_encounter := encounter_index >= ENCOUNTER_SCENES.size() - 1 + var defeated_final_encounter := encounter_index >= _encounter_count() - 1 active_encounter_prep.clear() _refresh_battle_status( _ui_text("Trial Complete", "试炼阶段完成", "試煉階段完成") if defeated_final_encounter else _ui_text("Encounter Cleared", "遭遇完成", "遭遇完成"), @@ -1156,6 +1164,14 @@ func _localized_detail_text(extra: String) -> String: return control_hint return "%s\n%s" % [extra, control_hint] +func _encounter_count() -> int: + return ENCOUNTER_SCENES.size() + (1 if not FINAL_BOSS_SCENES.is_empty() else 0) + +func _encounter_scene_for_index(index: int) -> PackedScene: + if index < ENCOUNTER_SCENES.size(): + return ENCOUNTER_SCENES[index] + return FINAL_BOSS_SCENES[randi() % FINAL_BOSS_SCENES.size()] + func _refresh_battle_status(override_title: String = "", override_subtitle: String = "", override_detail: String = "") -> void: if battle_status == null or not battle_status.has_method("set_message"): return @@ -1166,7 +1182,7 @@ func _refresh_battle_status(override_title: String = "", override_subtitle: Stri current_encounter.get_status_title(), current_encounter.get_status_text(), _localized_detail_text(_ui_text( - "Route: outer rooms use soldiers, Palace Hall uses the first boss, King Gate uses Twin Princes.", + "Route: outer rooms use soldiers, Palace Hall uses early bosses, King Gate uses Twin Princes, and the final chamber pulls one of three final bosses.", "路线:宫外地图为小兵战,皇宫前厅放第一个 Boss,王门前放双子 Boss。", "路線:宮外地圖為小兵戰,皇宮前廳放第一個 Boss,王門前放雙子 Boss。" )) @@ -1200,7 +1216,7 @@ func _objective_status_text() -> String: if character_select != null and character_select.visible and player_character == null: return _ui_text("Select a champion to begin the town trial.", "选择角色,开始城镇试炼。", "選擇角色,開始城鎮試煉。") if current_encounter != null and is_instance_valid(current_encounter): - var objective_text := _ui_text("Encounter %d / %d: clear the arena.", "第 %d / %d 场:清空战场。", "第 %d / %d 場:清空戰場。") % [encounter_index + 1, ENCOUNTER_SCENES.size()] + var objective_text := _ui_text("Encounter %d / %d: clear the arena.", "第 %d / %d 场:清空战场。", "第 %d / %d 場:清空戰場。") % [encounter_index + 1, _encounter_count()] if not active_encounter_prep.is_empty(): objective_text += _ui_text(" Prep %s is active.", " 已生效准备:%s。", " 已生效準備:%s。") % _prep_title(active_encounter_prep) return objective_text @@ -1252,7 +1268,7 @@ func _threat_status_text() -> String: return low_defense_text if not pending_prep.is_empty(): return _ui_text("Queued prep: %s.", "已排队准备:%s。", "已排隊準備:%s。") % _prep_summary(pending_prep) - return _ui_text("The route ramps from mixed enemy waves into three boss checks.", "路线会从普通敌群逐步推进到三场 Boss 检定。", "路線會從普通敵群逐步推進到三場 Boss 檢定。") + return _ui_text("The route ramps from mixed enemy waves into four boss checks, ending with a random final boss.", "路线会从普通敌群逐步推进到四场 Boss 检定,并以随机终局 Boss 收尾。", "路線會從普通敵群逐步推進到四場 Boss 檢定,並以隨機終局 Boss 收尾。") func _hero_status_text() -> String: if player_character == null or not is_instance_valid(player_character):