@tool extends Node3D class_name CheeseSeller ## Places a cheese table and periodically launches moldy cheese at the player. ## DESIGN: stab the cheese (projectile), not the seller — seller dies of grief when cheese destroyed. ## @tool: billboard look only in editor (no cheese firing). @export var texture_path: String = "res://assets/sprites/placeholders/cheese_seller.png": set(v): texture_path = v if Engine.is_editor_hint() and is_node_ready(): _apply_billboard_look() @export var fire_interval: float = 2.8 @export var cheese_speed: float = 7.0 var _alive: bool = true var _timer: float = 0.0 var _grief: int = 0 @onready var _billboard: BillboardSprite = $BillboardSprite func _ready() -> void: _apply_billboard_look() if Engine.is_editor_hint(): set_process(false) return add_to_group("interactable") add_to_group("cheese_seller") _timer = randf() * fire_interval func _apply_billboard_look() -> void: if _billboard == null: _billboard = get_node_or_null("BillboardSprite") as BillboardSprite if _billboard == null: return _billboard.set_target_height(SpriteFit.WORLD_PROP_HEIGHT) if texture_path != "" and ResourceLoader.exists(texture_path): var tex: Texture2D = load(texture_path) if tex: _billboard.set_texture(tex) func _process(delta: float) -> void: if Engine.is_editor_hint() or not _alive: return _timer -= delta if _timer <= 0.0: _timer = fire_interval _fire() func _fire() -> void: var player := get_tree().get_first_node_in_group("player") if player == null: return var proj_scene: PackedScene = load("res://scenes/prefabs/cheese_projectile.tscn") if proj_scene == null: return var proj: Node3D = proj_scene.instantiate() get_tree().current_scene.add_child(proj) proj.global_position = global_position + Vector3(0, 0.8, 0) var dir: Vector3 = (player.global_position + Vector3(0, 0.8, 0) - proj.global_position).normalized() proj.set("velocity", dir * cheese_speed) proj.add_to_group("cheese_projectile") proj.add_to_group("enemy") proj.set_meta("seller", self) func notify_cheese_destroyed() -> void: _grief += 1 if _grief >= 2: _alive = false var ui := get_node_or_null("/root/UIRoot") if ui and ui.has_method("show_dialog"): ui.show_dialog_key("dialog_cheese") queue_free() func on_interact(_player: Node) -> void: var ui := get_node_or_null("/root/UIRoot") if ui and ui.has_method("show_dialog"): ui.show_dialog_key("dialog_cheese") func take_hit(_amount: int = 1) -> void: ## Hitting the seller does nothing useful (DESIGN). var ui := get_node_or_null("/root/UIRoot") if ui and ui.has_method("show_dialog"): ui.show_dialog_key("dialog_cheese") func get_interact_prompt() -> String: return "%s — %s" % [Locale.tr_key("prompt_talk"), Locale.tr_key("dialog_cheese")] func set_interaction_highlight(enabled: bool, color: Color = Color(1.4, 1.35, 0.4)) -> void: if _billboard and _billboard.has_method("set_highlight"): _billboard.set_highlight(enabled, color)