Houses ~8.5 m; chimps capped ~0.85 m with safer axis fitting. Large GLB/PBR packs stay local until Git LFS (Forgejo HTTP 413). Co-authored-by: Cursor <cursoragent@cursor.com>
141 lines
4.6 KiB
GDScript
141 lines
4.6 KiB
GDScript
extends Node3D
|
|
## Wiese-Zone innerhalb der Dorf-Hauptwelt (kein eigener Scene-Teleport).
|
|
## Glitzos leben nur in meadow_aabb; außerhalb → Pixel-Sprühnebel.
|
|
|
|
@export var meadow_aabb: AABB = AABB(Vector3(20.0, -1.0, -18.0), Vector3(36.0, 8.0, 36.0))
|
|
|
|
var _glitzo_timer: float = 0.0
|
|
var _bear_saved: bool = false
|
|
var _first_glitzo_done: bool = false
|
|
|
|
func _ready() -> void:
|
|
_bear_saved = bool(GameState.get_flag("bear_saved", false))
|
|
_first_glitzo_done = Achievements.is_unlocked("first_glitzo")
|
|
var bear := get_node_or_null("Bear")
|
|
if bear and bear.has_signal("interacted"):
|
|
bear.interacted.connect(_on_bear)
|
|
var snake := get_node_or_null("SnakePickup")
|
|
if snake and snake.has_signal("interacted"):
|
|
snake.interacted.connect(_on_snake_pickup)
|
|
if Inventory.has_item("snake") or GameState.get_flag("snake_massaged", false):
|
|
if snake:
|
|
snake.queue_free()
|
|
if GameState.get_flag("snake_massaged", false):
|
|
_disable_mower()
|
|
|
|
func _process(delta: float) -> void:
|
|
## Only tick while player is near the meadow (cheap).
|
|
var player := get_tree().get_first_node_in_group("player")
|
|
if player is Node3D:
|
|
var pp: Vector3 = (player as Node3D).global_position
|
|
if not _near_meadow(pp):
|
|
_cull_glitzos_off_meadow()
|
|
return
|
|
_glitzo_timer += delta
|
|
if _glitzo_timer >= 3.2:
|
|
_glitzo_timer = 0.0
|
|
_spawn_glitzo()
|
|
_cull_glitzos_off_meadow()
|
|
|
|
func _near_meadow(p: Vector3) -> bool:
|
|
var expanded := meadow_aabb.grow(8.0)
|
|
return expanded.has_point(Vector3(p.x, 0.0, p.z))
|
|
|
|
func _cull_glitzos_off_meadow() -> void:
|
|
for child in get_children():
|
|
if child is Node3D and (child.name.begins_with("Glitzo") or str(child.get("texture_path")).ends_with("glitzo.png")):
|
|
var p: Vector3 = child.global_position
|
|
if not meadow_aabb.has_point(Vector3(p.x, 0.0, p.z)):
|
|
_pixel_spray(p)
|
|
child.queue_free()
|
|
|
|
func _pixel_spray(at: Vector3) -> void:
|
|
var scene: PackedScene = load("res://scenes/prefabs/quirxel_projectile.tscn")
|
|
if scene == null:
|
|
return
|
|
for i in range(5):
|
|
var q: Node3D = scene.instantiate()
|
|
get_parent().add_child(q)
|
|
q.global_position = at + Vector3(0, 0.4, 0)
|
|
var ang := randf() * TAU
|
|
q.set("velocity", Vector3(cos(ang), 0.5, sin(ang)) * randf_range(3.0, 6.0))
|
|
q.set("lifetime", 0.7)
|
|
q.set("hp", 99)
|
|
|
|
func _spawn_glitzo() -> void:
|
|
var scene: PackedScene = load("res://scenes/prefabs/enemy.tscn")
|
|
if scene == null:
|
|
return
|
|
var g: Node3D = scene.instantiate()
|
|
g.name = "GlitzoSpawn_%d" % Time.get_ticks_msec()
|
|
g.set("max_hp", 4)
|
|
g.set("texture_path", "res://assets/sprites/placeholders/glitzo.png")
|
|
g.set("ai_enabled", true)
|
|
g.set("glitzo_mode", true)
|
|
g.set("move_speed", 2.6)
|
|
g.set("attack_cooldown", 1.15)
|
|
g.set("attack_range", 1.1)
|
|
g.set("death_cause_key", "death_glitzo")
|
|
g.set("billboard_height", 1.0)
|
|
g.set("aggro_range", 22.0)
|
|
if not _first_glitzo_done:
|
|
g.set("achievement_on_defeat", "first_glitzo")
|
|
add_child(g)
|
|
## Spawn inside meadow AABB (local to world coords used by export).
|
|
var min_p := meadow_aabb.position
|
|
var max_p := meadow_aabb.position + meadow_aabb.size
|
|
g.global_position = Vector3(
|
|
randf_range(min_p.x + 4.0, max_p.x - 4.0),
|
|
0.1,
|
|
randf_range(min_p.z + 4.0, max_p.z - 4.0)
|
|
)
|
|
var bill: Node = g.get_node_or_null("BillboardSprite")
|
|
if bill and bill.has_method("set_texture"):
|
|
var tex: Texture2D = load("res://assets/sprites/placeholders/glitzo.png")
|
|
if tex:
|
|
bill.set_texture(tex)
|
|
if g.has_signal("defeated"):
|
|
g.defeated.connect(_on_glitzo_killed)
|
|
|
|
func _on_glitzo_killed() -> void:
|
|
_first_glitzo_done = true
|
|
|
|
func _on_bear(_npc: Node = null) -> void:
|
|
var ui := get_node_or_null("/root/UIRoot")
|
|
if _bear_saved:
|
|
if ui and ui.has_method("show_dialog"):
|
|
ui.show_dialog_key("dialog_bear_saved")
|
|
return
|
|
if Inventory.has_item("snake") and GameState.get_flag("snake_massaged", false):
|
|
_save_bear()
|
|
return
|
|
if Inventory.has_item("snake"):
|
|
if ui and ui.has_method("show_dialog"):
|
|
ui.show_dialog_key("dialog_villager_snake")
|
|
return
|
|
if ui and ui.has_method("show_dialog"):
|
|
ui.show_dialog_key("dialog_bear_help")
|
|
|
|
func _on_snake_pickup(_npc: Node = null) -> void:
|
|
var ui := get_node_or_null("/root/UIRoot")
|
|
if ui and ui.has_method("show_dialog"):
|
|
ui.show_dialog_key("dialog_snake_picked")
|
|
|
|
func _save_bear() -> void:
|
|
_bear_saved = true
|
|
GameState.set_flag("bear_saved", true)
|
|
if not Inventory.has_item("tooth"):
|
|
Inventory.add_item("tooth", 1)
|
|
_disable_mower()
|
|
var ui := get_node_or_null("/root/UIRoot")
|
|
if ui and ui.has_method("show_dialog"):
|
|
ui.show_dialog_key("dialog_bear_saved", 4.5)
|
|
|
|
func _disable_mower() -> void:
|
|
var mower := get_node_or_null("MowerHazard")
|
|
if mower:
|
|
mower.queue_free()
|
|
|
|
func notify_snake_massaged() -> void:
|
|
if not _bear_saved:
|
|
_save_bear()
|