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
@@ -84,10 +84,18 @@ static func shared_profiles() -> Array[NpcProfile]:
|
||||
Rules.Reaction.ARGUE: 10,
|
||||
},
|
||||
Rules.Level.FORMAL_WARNING: {
|
||||
Rules.Reaction.RECORD: 45,
|
||||
Rules.Reaction.ARGUE: 25,
|
||||
Rules.Reaction.RECORD: 35,
|
||||
Rules.Reaction.ARGUE: 20,
|
||||
Rules.Reaction.CALL_SOMEONE: 15,
|
||||
Rules.Reaction.COMPLY: 30,
|
||||
},
|
||||
Rules.Level.BLOCKING: {
|
||||
Rules.Reaction.RECORD: 30,
|
||||
Rules.Reaction.ARGUE: 20,
|
||||
Rules.Reaction.CALL_SOMEONE: 25,
|
||||
Rules.Reaction.LEAVE: 15,
|
||||
Rules.Reaction.COMPLY: 10,
|
||||
},
|
||||
}
|
||||
influencer.dialogue_lines = {
|
||||
Rules.Tone.SUSPICIOUS: [
|
||||
@@ -138,9 +146,10 @@ static func shared_profiles() -> Array[NpcProfile]:
|
||||
Rules.Reaction.COMPLY: 40,
|
||||
},
|
||||
Rules.Level.BLOCKING: {
|
||||
Rules.Reaction.LEAVE: 40,
|
||||
Rules.Reaction.ARGUE: 30,
|
||||
Rules.Reaction.RETURN_LATER: 20,
|
||||
Rules.Reaction.LEAVE: 35,
|
||||
Rules.Reaction.ARGUE: 25,
|
||||
Rules.Reaction.RETURN_LATER: 15,
|
||||
Rules.Reaction.CALL_SOMEONE: 15,
|
||||
Rules.Reaction.FORCE_ENTRY: 10,
|
||||
},
|
||||
Rules.Level.RADIO_ASSIST: {
|
||||
@@ -235,6 +244,48 @@ static func shared_profiles() -> Array[NpcProfile]:
|
||||
}
|
||||
profiles.append(violent_threat)
|
||||
|
||||
var protester := NpcProfile.new()
|
||||
protester.id = "protester"
|
||||
protester.display_name = "Protester"
|
||||
protester.npc_type = Rules.NpcType.PROTESTER
|
||||
protester.temperament = 0.5
|
||||
protester.base_compliance = 0.4
|
||||
protester.return_probability = 0.25
|
||||
protester.reactions_by_level = {
|
||||
Rules.Level.SIMPLE_DETERRENCE: {
|
||||
Rules.Reaction.ARGUE: 35,
|
||||
Rules.Reaction.IGNORE: 25,
|
||||
Rules.Reaction.RECORD: 15,
|
||||
Rules.Reaction.COMPLY: 25,
|
||||
},
|
||||
Rules.Level.FORMAL_WARNING: {
|
||||
Rules.Reaction.ARGUE: 40,
|
||||
Rules.Reaction.RECORD: 25,
|
||||
Rules.Reaction.IGNORE: 15,
|
||||
Rules.Reaction.CALL_SOMEONE: 5,
|
||||
Rules.Reaction.COMPLY: 15,
|
||||
},
|
||||
Rules.Level.BLOCKING: {
|
||||
Rules.Reaction.ARGUE: 25,
|
||||
Rules.Reaction.RECORD: 25,
|
||||
Rules.Reaction.CALL_SOMEONE: 15,
|
||||
Rules.Reaction.LEAVE: 25,
|
||||
Rules.Reaction.COMPLY: 10,
|
||||
},
|
||||
Rules.Level.RADIO_ASSIST: {
|
||||
Rules.Reaction.LEAVE: 60,
|
||||
Rules.Reaction.ARGUE: 20,
|
||||
Rules.Reaction.FORCE_ENTRY: 20,
|
||||
},
|
||||
}
|
||||
protester.dialogue_lines = {
|
||||
Rules.Tone.SUSPICIOUS: [
|
||||
"I have every right to be on public sidewalk.",
|
||||
"You can't touch me, this is a lawful protest.",
|
||||
],
|
||||
}
|
||||
profiles.append(protester)
|
||||
|
||||
# --- Authorized crowd ---------------------------------------------------------
|
||||
var staff := NpcProfile.new()
|
||||
staff.id = "authorized_staff"
|
||||
@@ -276,7 +327,7 @@ static func hollywood_building() -> VenueScenario:
|
||||
scenario.equipment = ["wristband_policy", "flashlight", "radio"]
|
||||
scenario.locations = {
|
||||
"guard_post": Vector3(0, 0, -3),
|
||||
"entrance": Vector3(0, 0, 0),
|
||||
"entrance": Vector3(0, 0, 1.0),
|
||||
"steps_end": Vector3(0, 0, 3),
|
||||
"sidewalk": Vector3(0, 0, 9),
|
||||
"queue_area": Vector3(0, 0, 6),
|
||||
@@ -286,15 +337,18 @@ static func hollywood_building() -> VenueScenario:
|
||||
Rules.Phase.EARLY: {"start_s": 0.0},
|
||||
Rules.Phase.MIDDAY: {"start_s": 300.0},
|
||||
Rules.Phase.EVENING: {"start_s": 600.0},
|
||||
Rules.Phase.LATE: {"start_s": 700.0},
|
||||
}
|
||||
|
||||
scenario.npc_profiles = _filter_profiles([
|
||||
"tourist", "confused_visitor", "delivery", "authorized_staff", "authorized_guest",
|
||||
"street_performer", "influencer", "intoxicated", "trespasser", "mental_crisis", "violent_threat",
|
||||
"protester",
|
||||
])
|
||||
|
||||
scenario.event_pool = [
|
||||
_building_routine("tourist_photo", Rules.NpcType.TOURIST, "photo", Vector2(0, 900), 0.35),
|
||||
_building_routine("protest_gather", Rules.NpcType.PROTESTER, "protest", Vector2(300, 900), 0.18),
|
||||
_building_routine("tourist_sit", Rules.NpcType.TOURIST, "sit", Vector2(0, 900), 0.20),
|
||||
_building_routine("confused_enter", Rules.NpcType.CONFUSED_VISITOR, "enter", Vector2(0, 900), 0.25),
|
||||
_building_routine("delivery_drop", Rules.NpcType.DELIVERY, "deliver", Vector2(0, 600), 0.30),
|
||||
@@ -349,7 +403,7 @@ static func joke_shop() -> VenueScenario:
|
||||
scenario.equipment = ["id_check", "guest_list", "wand", "radio"]
|
||||
scenario.locations = {
|
||||
"guard_post": Vector3(0, 0, -3),
|
||||
"entrance": Vector3(0, 0, 0),
|
||||
"entrance": Vector3(0, 0, 1.0),
|
||||
"door_area": Vector3(0, 0, 3),
|
||||
"queue_area": Vector3(0, 0, 9),
|
||||
}
|
||||
@@ -380,6 +434,7 @@ static func joke_shop() -> VenueScenario:
|
||||
Rules.Phase.EARLY: {"start_s": 0.0},
|
||||
Rules.Phase.MIDDAY: {"start_s": 300.0},
|
||||
Rules.Phase.EVENING: {"start_s": 600.0},
|
||||
Rules.Phase.LATE: {"start_s": 700.0},
|
||||
}
|
||||
|
||||
scenario.npc_profiles = _filter_profiles([
|
||||
@@ -430,7 +485,7 @@ static func tv_studio() -> VenueScenario:
|
||||
scenario.equipment = ["wristband_policy", "guest_list", "radio", "flashlight"]
|
||||
scenario.locations = {
|
||||
"guard_post": Vector3(0, 0, -3),
|
||||
"entrance": Vector3(0, 0, 0),
|
||||
"entrance": Vector3(0, 0, 1.0),
|
||||
"stage_door": Vector3(6, 0, -2),
|
||||
"queue_area": Vector3(0, 0, 9),
|
||||
}
|
||||
@@ -439,15 +494,18 @@ static func tv_studio() -> VenueScenario:
|
||||
Rules.Phase.EARLY: {"start_s": 0.0},
|
||||
Rules.Phase.MIDDAY: {"start_s": 300.0},
|
||||
Rules.Phase.EVENING: {"start_s": 600.0},
|
||||
Rules.Phase.LATE: {"start_s": 700.0},
|
||||
}
|
||||
|
||||
scenario.npc_profiles = _filter_profiles([
|
||||
"tourist", "confused_visitor", "delivery", "authorized_staff", "authorized_guest",
|
||||
"street_performer", "influencer", "intoxicated", "trespasser", "mental_crisis", "violent_threat",
|
||||
"protester",
|
||||
])
|
||||
|
||||
scenario.event_pool = [
|
||||
_studio_routine("audience_ticket", Rules.NpcType.AUTHORIZED_GUEST, "enter", Vector2(0, 900), 0.35),
|
||||
_studio_routine("protest_outside", Rules.NpcType.PROTESTER, "protest", Vector2(300, 900), 0.18),
|
||||
_studio_routine("celebrity_arrival", Rules.NpcType.AUTHORIZED_GUEST, "arrive", Vector2(300, 700), 0.20),
|
||||
_studio_routine("crew_shift", Rules.NpcType.AUTHORIZED_STAFF, "enter", Vector2(0, 700), 0.30),
|
||||
_studio_routine("tourist_photo", Rules.NpcType.TOURIST, "photo", Vector2(0, 900), 0.35),
|
||||
|
||||
Reference in new issue
Block a user