Add village 3D wiring, voice lines, and 1.74 m world scale.

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>
This commit is contained in:
copilot 2026-09-15 12:02:57 +02:00
parent 1d9e0a6532
commit 1cc8aa9b9d
507 changed files with 12991 additions and 579 deletions

159
scripts/vfx/melee_vfx.gd Normal file
View file

@ -0,0 +1,159 @@
extends Node3D
class_name MeleeVfx
## Visible melee swing (fist/tooth/sword) + range rings + hit particles.
@export var attack_range: float = 2.75
@export var interact_range: float = 2.8
var _swinging: bool = false
var _swing_t: float = 0.0
var _swing_dur: float = 0.22
var _weapon: Node3D
var _weapon_bill: BillboardSprite
var _atk_ring: MeshInstance3D
var _use_ring: MeshInstance3D
var _particles: CPUParticles3D
func _ready() -> void:
_build_weapon()
_build_rings()
_build_particles()
visible = true
func _build_weapon() -> void:
_weapon = Node3D.new()
_weapon.name = "WeaponSwing"
add_child(_weapon)
var packed: PackedScene = load("res://scenes/prefabs/billboard_sprite.tscn")
if packed:
_weapon_bill = packed.instantiate()
_weapon.add_child(_weapon_bill)
_weapon_bill.position = Vector3(0.85, 0.9, 0.0)
_weapon_bill.set_target_height(0.55)
_weapon_bill.max_width = 0.7
_weapon.visible = false
func _build_rings() -> void:
_atk_ring = _make_ring(Color(1.0, 0.35, 0.25, 0.35), attack_range)
_atk_ring.name = "AttackRangeRing"
add_child(_atk_ring)
_use_ring = _make_ring(Color(1.0, 0.9, 0.25, 0.28), interact_range)
_use_ring.name = "InteractRangeRing"
add_child(_use_ring)
_atk_ring.visible = false
_use_ring.visible = false
func _make_ring(color: Color, radius: float) -> MeshInstance3D:
var mi := MeshInstance3D.new()
var torus := TorusMesh.new()
torus.inner_radius = maxf(0.05, radius - 0.06)
torus.outer_radius = radius
torus.rings = 12
torus.ring_segments = 24
mi.mesh = torus
var mat := StandardMaterial3D.new()
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.albedo_color = color
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
mi.material_override = mat
mi.position = Vector3(0, 0.05, 0)
mi.rotation_degrees = Vector3(0, 0, 0)
return mi
func _build_particles() -> void:
_particles = CPUParticles3D.new()
_particles.name = "HitParticles"
_particles.emitting = false
_particles.one_shot = true
_particles.explosiveness = 0.95
_particles.amount = 18
_particles.lifetime = 0.35
_particles.direction = Vector3(0, 1, 0)
_particles.spread = 180.0
_particles.initial_velocity_min = 2.5
_particles.initial_velocity_max = 6.0
_particles.gravity = Vector3(0, -8, 0)
_particles.scale_amount_min = 0.08
_particles.scale_amount_max = 0.18
var mat := StandardMaterial3D.new()
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.albedo_color = Color(1.0, 0.85, 0.3)
mat.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
_particles.mesh = SphereMesh.new()
(_particles.mesh as SphereMesh).radius = 0.08
(_particles.mesh as SphereMesh).height = 0.16
_particles.material_override = mat
add_child(_particles)
func _process(delta: float) -> void:
if _swinging:
_swing_t += delta
var u := clampf(_swing_t / _swing_dur, 0.0, 1.0)
# Arc from -70° to +70° around player up-axis
var ang := deg_to_rad(lerp(-70.0, 70.0, u))
_weapon.rotation = Vector3(0, ang, 0)
_weapon.position = Vector3(0, 0.15 + 0.1 * sin(u * PI), 0)
if u >= 1.0:
_swinging = false
_weapon.visible = false
func set_ranges(attack_r: float, interact_r: float) -> void:
attack_range = attack_r
interact_range = interact_r
_resize_ring(_atk_ring, attack_r)
_resize_ring(_use_ring, interact_r)
func _resize_ring(mi: MeshInstance3D, radius: float) -> void:
if mi == null:
return
var torus := TorusMesh.new()
torus.inner_radius = maxf(0.05, radius - 0.07)
torus.outer_radius = radius
torus.rings = 12
torus.ring_segments = 24
mi.mesh = torus
func show_attack_range(on: bool) -> void:
if _atk_ring:
_atk_ring.visible = on
func show_interact_range(on: bool) -> void:
if _use_ring:
_use_ring.visible = on
func play_swing() -> void:
_refresh_weapon_texture()
_swinging = true
_swing_t = 0.0
_weapon.visible = true
_weapon.rotation = Vector3(0, deg_to_rad(-70), 0)
func burst_at(world_pos: Vector3, hit: bool) -> void:
if _particles == null:
return
_particles.global_position = world_pos + Vector3(0, 0.9, 0)
var mat := _particles.material_override as StandardMaterial3D
if mat:
mat.albedo_color = Color(1.0, 0.35, 0.25) if hit else Color(0.7, 0.7, 0.75)
_particles.restart()
_particles.emitting = true
func _refresh_weapon_texture() -> void:
if _weapon_bill == null:
return
var path := "res://assets/sprites/placeholders/fist.png"
var h := 0.55
if Inventory.has_item("moon_sword"):
path = "res://assets/sprites/placeholders/moon_sword.png"
h = 0.9
elif Inventory.has_item("wartchen"):
path = "res://assets/sprites/placeholders/wartchen.png"
h = 0.65
elif Inventory.has_item("tooth"):
path = "res://assets/sprites/placeholders/tooth.png"
h = 0.6
_weapon_bill.set_target_height(h)
var tex: Texture2D = load(path)
if tex:
_weapon_bill.set_texture(tex)