extends RefCounted ## NPC theater: seated guests, camera raises, and performers playing to the ## sidewalk once their intent's waypoint is reached (PLAN.md: NPC Behavior Types). const NpcAvatar := preload("res://src/game/npc_avatar.gd") const NpcProfile := preload("res://src/core/npc_profile.gd") const Rules := preload("res://src/core/rules.gd") func _avatar(intent: String, npc_type: int = Rules.NpcType.TOURIST) -> Node: var p := NpcProfile.new() p.id = "theater" p.display_name = "Theater" p.npc_type = npc_type var avatar: Node = NpcAvatar.new() avatar.setup(p, intent, 1234, Vector3(2, 0.5, 8), Vector3(0, 0.5, 6)) return avatar func test_pose_per_intent() -> bool: var cases := { "sit": "sit", "photo": "photo", "film": "film", "perform": "perform", "enter": "idle", } var ok := true for intent in cases.keys(): var avatar := _avatar(str(intent)) avatar.arrived = true if avatar.theater_pose() != cases[intent]: ok = false avatar.free() return TestUtils.check(ok, "each intent maps to its theater pose once arrived") func test_walking_npc_has_no_pose() -> bool: var avatar := _avatar("sit") var a := TestUtils.check_eq(avatar.theater_pose(), "idle", "a walking NPC does not sit yet") avatar.arrived = true var b := TestUtils.check_eq(avatar.theater_pose(), "sit", "arriving starts the pose") avatar.free() return a and b func test_only_sit_crouches() -> bool: var a := TestUtils.check(is_equal_approx(NpcAvatar.crouch_for_pose("sit"), -0.34), "sit lowers the rig") var b := TestUtils.check_eq(NpcAvatar.crouch_for_pose("photo"), 0.0, "photo keeps standing") var c := TestUtils.check_eq(NpcAvatar.crouch_for_pose("perform"), 0.0, "perform keeps standing") var d := TestUtils.check_eq(NpcAvatar.crouch_for_pose("idle"), 0.0, "idle keeps standing") return a and b and c and d func test_photo_lift_is_brief() -> bool: var raised := 0.0 var flat := 0.0 for i in 120: var phase := float(i) / 60.0 var lift: float = NpcAvatar.prop_lift_for_pose("photo", phase) raised = maxf(raised, lift) if i == 0: flat = lift var a := TestUtils.check(raised > 0.1, "the camera prop lifts while photographing") var b := TestUtils.check(raised <= 0.16 + 0.001, "the lift stays small") var c := TestUtils.check_eq(flat, 0.0, "the lift starts at rest") var d := TestUtils.check_eq(NpcAvatar.prop_lift_for_pose("sit", 0.5), 0.0, "seated guests do not lift a prop") return a and b and c and d func test_performer_sways_only_when_performing() -> bool: var peak := 0.0 for i in 120: peak = maxf(peak, absf(NpcAvatar.sway_for_pose("perform", float(i) / 60.0))) var a := TestUtils.check(peak > 0.05, "the performer's sign sways") var b := TestUtils.check_eq(NpcAvatar.sway_for_pose("photo", 1.0), 0.0, "photographers do not sway") var c := TestUtils.check_eq(NpcAvatar.sway_for_pose("sit", 1.0), 0.0, "seated guests do not sway") return a and b and c func test_photo_prop_is_wired_for_camera_archetypes() -> bool: var influencer := _avatar("photo", Rules.NpcType.INFLUENCER) var tourist := _avatar("photo", Rules.NpcType.TOURIST) var performer := _avatar("perform", Rules.NpcType.PERFORMER) var a := TestUtils.check(influencer._photo_prop != null, "influencer carries a camera prop") var b := TestUtils.check(tourist._photo_prop != null, "tourist carries a camera prop") var c := TestUtils.check(performer._sign_prop != null, "performer carries a sign prop") influencer.free() tourist.free() performer.free() return a and b and c func test_sitting_rig_actually_lowers() -> bool: var avatar := _avatar("sit") avatar.arrived = true for i in 40: avatar._animate(0.016) var a := TestUtils.check(avatar._rig.position.y < -0.2, "the rig settles into the crouch") var legs_bent := TestUtils.check(absf(avatar._legs[0].rotation.x) > deg_to_rad(30.0), "legs bend for the seat") avatar.arrived = false for i in 60: avatar._animate(0.016) var b := TestUtils.check(avatar._rig.position.y > -0.1, "standing back up lifts the rig") avatar.free() return a and legs_bent and b