Initial scaffold: Godot 4.3 scenes, systems, and placeholders.
Wire all twelve levels with billboard prefabs, autoload systems (inventory, achievements, mock shop, DE/EN/BY), and satire audio locks so the Title→End flow is playable with placeholder assets.
This commit is contained in:
commit
938cc4bc82
114 changed files with 4819 additions and 0 deletions
25
scripts/combat/exit_trigger.gd
Normal file
25
scripts/combat/exit_trigger.gd
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
extends Area3D
|
||||
class_name ExitTrigger
|
||||
## Walk into this volume to advance or jump to a scene.
|
||||
|
||||
@export var next_scene_id: String = ""
|
||||
@export var use_goto_next: bool = true
|
||||
@export var require_flag: String = ""
|
||||
|
||||
func _ready() -> void:
|
||||
body_entered.connect(_on_body_entered)
|
||||
collision_layer = 0
|
||||
collision_mask = 2 # player layer
|
||||
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
if require_flag != "" and not GameState.get_flag(require_flag, false):
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key("exit_hint") + " (?)")
|
||||
return
|
||||
if use_goto_next and next_scene_id == "":
|
||||
GameState.goto_next()
|
||||
elif next_scene_id != "":
|
||||
GameState.goto_scene(next_scene_id)
|
||||
24
scripts/combat/hazard_zone.gd
Normal file
24
scripts/combat/hazard_zone.gd
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
extends Area3D
|
||||
class_name HazardZone
|
||||
## Damages the player on enter.
|
||||
|
||||
@export var damage: int = 1
|
||||
@export var death_cause_key: String = "death_default"
|
||||
@export var cooldown: float = 1.0
|
||||
|
||||
var _ready_to_hurt: bool = true
|
||||
|
||||
func _ready() -> void:
|
||||
body_entered.connect(_on_body_entered)
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
if not _ready_to_hurt:
|
||||
return
|
||||
if not body.is_in_group("player"):
|
||||
return
|
||||
_ready_to_hurt = false
|
||||
GameState.take_damage(damage, death_cause_key)
|
||||
await get_tree().create_timer(cooldown).timeout
|
||||
_ready_to_hurt = true
|
||||
43
scripts/combat/hittable.gd
Normal file
43
scripts/combat/hittable.gd
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
extends Node3D
|
||||
class_name HittableEntity
|
||||
## Generic enemy / boss stub with hit points.
|
||||
|
||||
signal defeated
|
||||
signal hit(remaining_hp: int)
|
||||
|
||||
@export var max_hp: int = 4
|
||||
@export var texture_path: String = "res://assets/sprites/placeholders/enemy.png"
|
||||
@export var defeat_flag: String = ""
|
||||
@export var achievement_on_defeat: String = ""
|
||||
|
||||
var hp: int = 4
|
||||
var _alive: bool = true
|
||||
|
||||
@onready var _billboard: BillboardSprite = $BillboardSprite
|
||||
|
||||
func _ready() -> void:
|
||||
hp = max_hp
|
||||
add_to_group("enemy")
|
||||
add_to_group("interactable")
|
||||
if _billboard:
|
||||
var tex: Texture2D = load(texture_path)
|
||||
if tex:
|
||||
_billboard.set_texture(tex)
|
||||
|
||||
func take_hit(amount: int = 1) -> void:
|
||||
if not _alive:
|
||||
return
|
||||
hp = max(0, hp - amount)
|
||||
hit.emit(hp)
|
||||
AudioDirector.play_hit()
|
||||
if hp <= 0:
|
||||
_alive = false
|
||||
if defeat_flag != "":
|
||||
GameState.set_flag(defeat_flag, true)
|
||||
if achievement_on_defeat != "":
|
||||
Achievements.unlock(achievement_on_defeat)
|
||||
defeated.emit()
|
||||
queue_free()
|
||||
|
||||
func on_interact(_player: Node) -> void:
|
||||
take_hit(1)
|
||||
43
scripts/combat/interactable_npc.gd
Normal file
43
scripts/combat/interactable_npc.gd
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
extends Node3D
|
||||
class_name InteractableNPC
|
||||
## Billboard NPC with dialog key and optional quest hook.
|
||||
|
||||
signal interacted(npc: InteractableNPC)
|
||||
|
||||
@export var dialog_key: String = "dialog_hongkong_npc"
|
||||
@export var texture_path: String = "res://assets/sprites/placeholders/npc.png"
|
||||
@export var give_item_id: String = ""
|
||||
@export var require_item_id: String = ""
|
||||
@export var consume_required: bool = false
|
||||
@export var quest_flag: String = ""
|
||||
@export var achievement_id: String = ""
|
||||
@export var on_success_goto: String = ""
|
||||
|
||||
@onready var _billboard: BillboardSprite = $BillboardSprite
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("interactable")
|
||||
if _billboard:
|
||||
var tex: Texture2D = load(texture_path)
|
||||
if tex:
|
||||
_billboard.set_texture(tex)
|
||||
|
||||
func on_interact(_player: Node) -> void:
|
||||
interacted.emit(self)
|
||||
var ui := get_node_or_null("/root/UIRoot")
|
||||
if ui and ui.has_method("show_dialog"):
|
||||
ui.show_dialog(Locale.tr_key(dialog_key))
|
||||
if require_item_id != "" and not Inventory.has_item(require_item_id):
|
||||
return
|
||||
if require_item_id != "" and consume_required:
|
||||
Inventory.remove_item(require_item_id, 1)
|
||||
if give_item_id != "":
|
||||
Inventory.add_item(give_item_id, 1)
|
||||
if quest_flag != "":
|
||||
GameState.set_flag(quest_flag, true)
|
||||
if quest_flag == "house_built":
|
||||
GameState.house_built = true
|
||||
if achievement_id != "":
|
||||
Achievements.unlock(achievement_id)
|
||||
if on_success_goto != "":
|
||||
GameState.goto_scene(on_success_goto)
|
||||
Loading…
Add table
Add a link
Reference in a new issue