Implement TODO backlog: LATE phase fix, protester, CALL_SOMEONE, door lock, flashlight, persistence, navmesh, JSON localization, and release tooling
CI / headless-tests (push) Successful in 55s
CI / headless-tests (push) Successful in 55s
- Fix LATE shift phase (start 700s) so the violent-threat event is reachable - Add PROTESTER archetype (rules, profile, colors, events in public venues) - Add CALL_SOMEONE reaction (FSM -> ARGUING, influencer/trespasser/protester weights) - Door lock: DoorState core class, L binding, red/green visual indicator, locked doors thwart FORCE_ENTRY - Flashlight: G binding, SpotLight3D, perception radius bonus at evening/late - Persistence: save shift results via Save autoload, history + best score on review screen, round-trip test - Navigation: flat NavigationRegion3D navmesh, NavigationAgent3D path following with direct-movement fallback; steps made visual-only so NPCs reach entrance - Localization: strings moved to content/strings.en.json (loader keeps lookup/fmt signatures; coverage test over all keys) - Distribution: export_presets.cfg (Linux/Windows/macOS), tools/build_releases.sh, docs/RELEASING.md, prominent README Downloads section - Remove stale signal_bus.gd (invalid static-signal experiment) - Tests: 53 passing across 9 suites
This commit is contained in:
1 parent
c808165302
commit
1e972a7490
27 files changed
+1044
-340
No files matched your search
@@ -4,6 +4,9 @@ class_name WorldBuilder
|
||||
## No art assets required; all geometry is BoxMesh with flat materials.
|
||||
|
||||
var root: Node3D = null
|
||||
## Door lock indicator mesh near the handle; toggled by GameManager.
|
||||
var door_indicator: MeshInstance3D = null
|
||||
var _door_indicator_mat: StandardMaterial3D = null
|
||||
|
||||
func _init(root_node: Node3D) -> void:
|
||||
root = root_node
|
||||
@@ -15,6 +18,7 @@ func build() -> void:
|
||||
_build_sidewalk()
|
||||
_build_street()
|
||||
_build_guard_spawn()
|
||||
_build_navmesh()
|
||||
|
||||
func _add_box(parent: Node3D, size: Vector3, pos: Vector3, color: Color, cast_shadow: bool = false) -> MeshInstance3D:
|
||||
var mesh := BoxMesh.new()
|
||||
@@ -50,6 +54,9 @@ func _build_building() -> void:
|
||||
_add_box(building, Vector3(3.0, 5.2, 0.3), Vector3(0, 2.9, -4.15), Color(0.16, 0.15, 0.13))
|
||||
# Door window
|
||||
_add_box(building, Vector3(1.8, 1.4, 0.34), Vector3(0, 3.8, -4.0), Color(0.5, 0.7, 0.9, 1.0))
|
||||
# Door lock indicator (green = open, red = locked), placed beside the handle
|
||||
door_indicator = _add_box(building, Vector3(0.18, 0.18, 0.24), Vector3(-1.8, 1.7, -4.35), Color(0.1, 0.9, 0.3))
|
||||
door_indicator.material_override = _lock_indicator_material(Color(0.1, 0.9, 0.3))
|
||||
# Awning sign bar
|
||||
_add_box(building, Vector3(8, 0.5, 0.6), Vector3(0, 7.0, -3.6), Color(0.45, 0.44, 0.44))
|
||||
var col := BoxShape3D.new()
|
||||
@@ -60,7 +67,10 @@ func _build_building() -> void:
|
||||
root.add_child(building)
|
||||
|
||||
func _build_steps() -> void:
|
||||
var steps := StaticBody3D.new()
|
||||
# Steps are visual only (no collider) so NPCs and the guard can walk up the
|
||||
# entrance on the flat navigation mesh; the ground and building provide the
|
||||
# actual collision boundaries.
|
||||
var steps := Node3D.new()
|
||||
steps.name = "Steps"
|
||||
var step_w := 8.0
|
||||
var tread := 0.6
|
||||
@@ -71,11 +81,6 @@ func _build_steps() -> void:
|
||||
_add_box(steps, Vector3(step_w - i * 0.35, height, tread), Vector3(0, y + height / 2.0, z), Color(0.55, 0.52, 0.5))
|
||||
# Landing at the top
|
||||
_add_box(steps, Vector3(9, 0.2, 3), Vector3(0, 4 * height + 0.1, -1.5), Color(0.5, 0.47, 0.45))
|
||||
var col := BoxShape3D.new()
|
||||
col.size = Vector3(step_w, 0.65, tread * 4 + 3)
|
||||
var so := steps.create_shape_owner(steps)
|
||||
steps.shape_owner_add_shape(so, col)
|
||||
steps.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, 0.32, 0.8)))
|
||||
root.add_child(steps)
|
||||
|
||||
func _build_sidewalk() -> void:
|
||||
@@ -103,8 +108,44 @@ func _build_street() -> void:
|
||||
street.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, 0, 22)))
|
||||
root.add_child(street)
|
||||
|
||||
func _build_navmesh() -> void:
|
||||
## Flat navigation mesh over the walkable floor: landing strip, sidewalk,
|
||||
## and street front. Hand-authored polygons; no runtime bake needed.
|
||||
var region := NavigationRegion3D.new()
|
||||
region.name = "NavRegion"
|
||||
var mesh := NavigationMesh.new()
|
||||
mesh.agent_radius = 0.4
|
||||
mesh.agent_max_climb = 0.8
|
||||
mesh.agent_max_slope = 45.0
|
||||
mesh.cell_size = 0.25
|
||||
var verts := PackedVector3Array([
|
||||
Vector3(-18, 0.0, -0.6), Vector3(18, 0.0, -0.6),
|
||||
Vector3(18, 0.0, 20.0), Vector3(-18, 0.0, 20.0),
|
||||
])
|
||||
mesh.set_vertices(verts)
|
||||
mesh.add_polygon(PackedInt32Array([0, 1, 2, 3]))
|
||||
region.navigation_mesh = mesh
|
||||
root.add_child(region)
|
||||
|
||||
func _build_guard_spawn() -> void:
|
||||
var marker := Marker3D.new()
|
||||
marker.name = "GuardSpawn"
|
||||
marker.position = Vector3(0, 1.6, -2.5)
|
||||
root.add_child(marker)
|
||||
root.add_child(marker)
|
||||
|
||||
func _lock_indicator_material(color: Color) -> StandardMaterial3D:
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = color
|
||||
mat.emission_enabled = true
|
||||
mat.emission = color
|
||||
mat.emission_energy_multiplier = 1.5
|
||||
return mat
|
||||
|
||||
## Mirror the door lock state visually (green open, red locked).
|
||||
func set_door_locked(locked: bool) -> void:
|
||||
if door_indicator == null:
|
||||
return
|
||||
var color := Color(0.9, 0.15, 0.15) if locked else Color(0.1, 0.9, 0.3)
|
||||
if door_indicator.material_override is StandardMaterial3D:
|
||||
(door_indicator.material_override as StandardMaterial3D).albedo_color = color
|
||||
(door_indicator.material_override as StandardMaterial3D).emission = color
|
||||
Reference in new issue
Block a user