diff --git a/Docs/PS_AI_Behavior_Documentation.pdf b/Docs/PS_AI_Behavior_Documentation.pdf new file mode 100644 index 0000000..5c47011 Binary files /dev/null and b/Docs/PS_AI_Behavior_Documentation.pdf differ diff --git a/Docs/PS_AI_Behavior_Documentation.pptx b/Docs/PS_AI_Behavior_Documentation.pptx new file mode 100644 index 0000000..114aee8 Binary files /dev/null and b/Docs/PS_AI_Behavior_Documentation.pptx differ diff --git a/Docs/PS_AI_ConvAgent_Documentation.pdf b/Docs/PS_AI_ConvAgent_Documentation.pdf new file mode 100644 index 0000000..b7a2a17 Binary files /dev/null and b/Docs/PS_AI_ConvAgent_Documentation.pdf differ diff --git a/Docs/PS_AI_ConvAgent_Documentation.pptx b/Docs/PS_AI_ConvAgent_Documentation.pptx new file mode 100644 index 0000000..65435a0 Binary files /dev/null and b/Docs/PS_AI_ConvAgent_Documentation.pptx differ diff --git a/Docs/gen_behavior_doc.js b/Docs/gen_behavior_doc.js new file mode 100644 index 0000000..d9c9ac4 --- /dev/null +++ b/Docs/gen_behavior_doc.js @@ -0,0 +1,691 @@ +// PS_AI_Behavior User Documentation Generator +// Palette: Warm Terracotta (terracotta #B85042, sand #E7E8D1, sage #A7BEAE) + +const pptxgen = require("pptxgenjs"); +const pres = new pptxgen(); + +pres.layout = "LAYOUT_WIDE"; // 13.333 x 7.5 inch + +// ─── Color Palette ───────────────────────────────── +const TERRA = "B85042"; +const TERRA_D = "7A3229"; +const SAND = "E7E8D1"; +const SAND_LT = "F5F5EA"; +const SAGE = "A7BEAE"; +const SAGE_D = "5F7566"; +const WHITE = "FFFFFF"; +const CHARCOAL = "2E2A26"; +const GOLD = "D4A84B"; + +// ─── Font ────────────────────────────────────────── +const FONT_H = "Calibri"; +const FONT_B = "Calibri"; + +// ─── Master slide backgrounds ────────────────────── +pres.defineSlideMaster({ + title: "CONTENT", + background: { color: SAND_LT }, + objects: [ + { rect: { x: 0, y: 0, w: 13.333, h: 0.35, fill: { color: TERRA } } }, + { rect: { x: 0, y: 7.15, w: 13.333, h: 0.35, fill: { color: TERRA } } }, + { text: { + text: "PS_AI_Behavior — User Documentation", + options: { x: 0.3, y: 7.18, w: 8, h: 0.3, fontSize: 10, color: SAND, fontFace: FONT_B } + } }, + { text: { + text: "ASTERION", + options: { x: 11.5, y: 7.18, w: 1.5, h: 0.3, fontSize: 10, color: SAND, fontFace: FONT_B, align: "right", bold: true } + } }, + ], +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 1 — TITLE +// ═══════════════════════════════════════════════════════════════════ +let s = pres.addSlide(); +s.background = { color: TERRA }; +s.addShape(pres.ShapeType.rect, { x: 0, y: 3.3, w: 13.333, h: 0.05, fill: { color: GOLD } }); +s.addText("PS_AI_Behavior", { x: 0.6, y: 2.2, w: 12, h: 1.2, fontSize: 60, bold: true, color: WHITE, fontFace: FONT_H }); +s.addText("Personality-Driven NPC AI for Unreal Engine", { x: 0.6, y: 3.5, w: 12, h: 0.6, fontSize: 22, color: SAND, fontFace: FONT_B, italic: true }); +s.addText("User Documentation", { x: 0.6, y: 4.4, w: 12, h: 0.5, fontSize: 18, color: SAND, fontFace: FONT_B }); +s.addText("Civilians • Enemies • Protectors • Cover System • Splines", { x: 0.6, y: 5.2, w: 12, h: 0.4, fontSize: 14, color: SAND, fontFace: FONT_B }); +s.addText("ASTERION • 2026", { x: 0.6, y: 6.6, w: 12, h: 0.4, fontSize: 12, color: SAND, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 2 — OVERVIEW +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Plugin Overview", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); + +s.addText("What it does", { x: 0.5, y: 1.7, w: 6, h: 0.4, fontSize: 18, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "Personality-driven AI for NPCs", options: { bullet: true, fontSize: 14 } }, + { text: "Three archetypes: Civilian, Enemy, Protector", options: { bullet: true, fontSize: 14 } }, + { text: "Trait-based reactions: Courage, Aggressivity, Caution", options: { bullet: true, fontSize: 14 } }, + { text: "Tactical combat + cover system + EQS", options: { bullet: true, fontSize: 14 } }, + { text: "Spline-based patrol networks", options: { bullet: true, fontSize: 14 } }, + { text: "Interface-driven — no Pawn class dependency", options: { bullet: true, fontSize: 14 } }, +], { x: 0.5, y: 2.1, w: 6, h: 3, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 8 }); + +s.addShape(pres.ShapeType.roundRect, { x: 7, y: 1.7, w: 5.8, h: 4.8, fill: { color: TERRA }, rectRadius: 0.1 }); +s.addText("Primary Use Case", { x: 7.2, y: 1.9, w: 5.4, h: 0.4, fontSize: 16, bold: true, color: GOLD, fontFace: FONT_H }); +s.addText("Populate your world with intelligent NPCs that perceive threats, evaluate risk based on personality, and respond appropriately — civilians flee from gunfire, enemies engage in tactical combat, protectors defend civilians.", { + x: 7.2, y: 2.4, w: 5.4, h: 1.7, fontSize: 13, color: SAND, fontFace: FONT_B, italic: true +}); +s.addText("Key Differentiators", { x: 7.2, y: 4.2, w: 5.4, h: 0.4, fontSize: 16, bold: true, color: GOLD, fontFace: FONT_H }); +s.addText([ + { text: "Data-driven personality profiles", options: { bullet: true, fontSize: 12 } }, + { text: "Built-in cover cycle state machine", options: { bullet: true, fontSize: 12 } }, + { text: "Spline network with junction detection", options: { bullet: true, fontSize: 12 } }, + { text: "Gaze bridge to ConvAgent (reflection)", options: { bullet: true, fontSize: 12 } }, +], { x: 7.2, y: 4.7, w: 5.4, h: 1.7, color: SAND, fontFace: FONT_B, paraSpaceAfter: 4 }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 3 — CORE ARCHITECTURE +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Core Architecture", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); + +s.addText("The plugin is built around four concepts:", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const pillars = [ + { title: "Interface", subtitle: "IPS_AI_Behavior_Interface", desc: "Implemented on host project Pawns. Zero compile dependency — identity, team, combat, movement." }, + { title: "NPC Type", subtitle: "Civilian / Enemy / Protector", desc: "Determines team, spline access, default tree. Enemies can be disguised (hostile=false)." }, + { title: "State", subtitle: "Behavior State enum", desc: "Idle, Patrol, Alerted, Combat, Fleeing, TakingCover, Dead, Scripted. Written to Blackboard." }, + { title: "Profile", subtitle: "PersonalityProfile data asset", desc: "Traits, thresholds, target priority, speed per state, default BT." }, +]; +pillars.forEach((p, i) => { + const col = i % 2; + const row = Math.floor(i / 2); + const x = 0.5 + col * 6.3; + const y = 2.2 + row * 2.3; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 6.0, h: 2.1, fill: { color: TERRA }, rectRadius: 0.1 }); + s.addText(p.title, { x: x + 0.2, y: y + 0.15, w: 5.6, h: 0.45, fontSize: 20, bold: true, color: GOLD, fontFace: FONT_H }); + s.addText(p.subtitle, { x: x + 0.2, y: y + 0.6, w: 5.6, h: 0.35, fontSize: 12, color: SAND, italic: true, fontFace: "Consolas" }); + s.addText(p.desc, { x: x + 0.2, y: y + 1.0, w: 5.6, h: 1.0, fontSize: 12, color: SAND, fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 4 — NPC TYPES & TEAM ID +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("NPC Types & TeamId", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Identity drives perception, targeting, and access control.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +// Three NPC cards +const npcs = [ + { name: "CIVILIAN", color: SAGE, id: "0x00", desc: "Non-hostile. Flees. Allied with Protector." }, + { name: "ENEMY", color: TERRA, id: "0x10", desc: "Hostile. Tactical combat. Can be disguised." }, + { name: "PROTECTOR", color: SAGE_D, id: "0x20", desc: "Police / guard. Defends Civilians." }, +]; +npcs.forEach((n, i) => { + const x = 0.5 + i * 4.3; + s.addShape(pres.ShapeType.roundRect, { x: x, y: 2.15, w: 4.0, h: 2.2, fill: { color: n.color }, rectRadius: 0.1 }); + s.addText(n.name, { x: x + 0.2, y: 2.3, w: 3.6, h: 0.5, fontSize: 22, bold: true, color: WHITE, align: "center", fontFace: FONT_H }); + s.addText(`TeamId = ${n.id}`, { x: x + 0.2, y: 2.85, w: 3.6, h: 0.35, fontSize: 14, color: WHITE, align: "center", italic: true, fontFace: "Consolas" }); + s.addText(n.desc, { x: x + 0.2, y: 3.3, w: 3.6, h: 0.95, fontSize: 12, color: WHITE, align: "center", fontFace: FONT_B }); +}); + +// TeamId encoding +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 4.6, w: 12.3, h: 2.3, fill: { color: TERRA }, rectRadius: 0.1 }); +s.addText("TeamId Encoding", { x: 0.7, y: 4.7, w: 12, h: 0.4, fontSize: 16, bold: true, color: GOLD, fontFace: FONT_H }); +s.addText("uint8 = (high nibble: NPCType) | (low nibble: Faction 0-15)", { x: 0.7, y: 5.1, w: 12, h: 0.3, fontSize: 13, color: SAND, italic: true, fontFace: "Consolas" }); + +s.addText([ + { text: "Same type + same faction → Friendly (allies)", options: { bullet: true, fontSize: 12 } }, + { text: "Same type + different faction → Hostile (rival gangs)", options: { bullet: true, fontSize: 12 } }, + { text: "Civilian ↔ Protector → always Friendly", options: { bullet: true, fontSize: 12 } }, + { text: "Everything else → Hostile", options: { bullet: true, fontSize: 12 } }, + { text: "Disguised Enemy (hostile=false) → TeamId 0x01 (civilian disguise)", options: { bullet: true, fontSize: 12 } }, +], { x: 0.7, y: 5.45, w: 12, h: 1.4, color: SAND, fontFace: FONT_B, paraSpaceAfter: 3 }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 5 — BEHAVIOR STATES +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Behavior States", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Enum written to Blackboard, drives BT branching.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const states = [ + { name: "Idle", desc: "Resting / waiting. Proximity gaze active." }, + { name: "Patrol", desc: "Wandering on waypoints or splines." }, + { name: "Alerted", desc: "Suspicious, heightened awareness." }, + { name: "Combat", desc: "Engaged with a threat, attacking." }, + { name: "Fleeing", desc: "Running away from danger." }, + { name: "TakingCover", desc: "Behind tactical cover during combat." }, + { name: "Dead", desc: "NPC deceased, all systems shut down." }, + { name: "Scripted", desc: "Manual override (conversations, cinematics)." }, +]; +states.forEach((state, i) => { + const col = i % 4; + const row = Math.floor(i / 4); + const x = 0.5 + col * 3.15; + const y = 2.2 + row * 2.3; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 2.95, h: 2.1, fill: { color: SAND }, line: { color: TERRA, width: 1 }, rectRadius: 0.08 }); + s.addShape(pres.ShapeType.rect, { x: x, y: y, w: 2.95, h: 0.55, fill: { color: TERRA } }); + s.addText(state.name, { x: x, y: y + 0.05, w: 2.95, h: 0.5, fontSize: 16, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); + s.addText(state.desc, { x: x + 0.15, y: y + 0.7, w: 2.65, h: 1.3, fontSize: 11, color: CHARCOAL, valign: "top", fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 6 — AI CONTROLLER +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("AI Controller", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("APS_AI_Behavior_AIController — the central orchestrator.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +s.addText("Auto-setup on Possession", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "Discovers PersonalityComponent on Pawn", options: { bullet: true, fontSize: 12 } }, + { text: "Computes TeamId from NPCType + Faction", options: { bullet: true, fontSize: 12 } }, + { text: "Initializes Blackboard with required keys", options: { bullet: true, fontSize: 12 } }, + { text: "Binds gaze bridge to ConvAgent (if present)", options: { bullet: true, fontSize: 12 } }, + { text: "Starts BT (unless bAutoStartBehavior = false)", options: { bullet: true, fontSize: 12 } }, +], { x: 0.5, y: 2.55, w: 6, h: 2.5, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 4 }); + +s.addText("Key Methods", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "StartBehavior() / StopBehavior()", options: { bullet: true, fontSize: 12 } }, + { text: "SetBehaviorState() / GetBehaviorState()", options: { bullet: true, fontSize: 12 } }, + { text: "SetTeamId() — runtime team change", options: { bullet: true, fontSize: 12 } }, + { text: "GetBehaviorPerception()", options: { bullet: true, fontSize: 12 } }, + { text: "GetPersonalityComponent()", options: { bullet: true, fontSize: 12 } }, +], { x: 7, y: 2.55, w: 6, h: 2.5, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 4 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.2, w: 12.3, h: 1.8, fill: { color: TERRA }, rectRadius: 0.1 }); +s.addText("Gaze Bridge (Proximity)", { x: 0.7, y: 5.3, w: 12, h: 0.35, fontSize: 14, bold: true, color: GOLD, fontFace: FONT_H }); +s.addText("Reflection-based integration with PS_AI_ConvAgent. During Idle/Patrol, NPC glances at nearby actors within GazeProximityRadius. Configurable duration and cooldown. Auto-pauses during active conversations.", { + x: 0.7, y: 5.7, w: 12, h: 1.25, fontSize: 12, color: SAND, italic: true, fontFace: FONT_B +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 7 — PERSONALITY COMPONENT +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Personality Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Runtime traits & state reactions for the NPC.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +// Traits triangle +s.addText("The Three Traits", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); + +const traits = [ + { name: "Courage", desc: "0 = coward, 1 = fearless", effect: "Gates flee threshold & cover advancement", color: "C9463A" }, + { name: "Aggressivity", desc: "0 = peaceful, 1 = violent", effect: "Drives attack threshold, combat/cover cycle", color: "D4803A" }, + { name: "Caution", desc: "0 = reckless, 1 = prudent", effect: "Flee threshold, cover duration, spline threat", color: "7C9D7B" }, +]; +traits.forEach((t, i) => { + const y = 2.6 + i * 0.95; + s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 1.8, h: 0.8, fill: { color: t.color }, rectRadius: 0.08 }); + s.addText(t.name, { x: 0.5, y: y + 0.1, w: 1.8, h: 0.3, fontSize: 14, bold: true, color: WHITE, align: "center", fontFace: FONT_H }); + s.addText(t.desc, { x: 0.5, y: y + 0.45, w: 1.8, h: 0.3, fontSize: 10, color: WHITE, align: "center", italic: true, fontFace: FONT_B }); + s.addText(t.effect, { x: 2.4, y: y + 0.15, w: 4.2, h: 0.6, fontSize: 12, color: CHARCOAL, valign: "middle", fontFace: FONT_B }); +}); + +s.addText("Properties & Events", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "Profile — PersonalityProfile DataAsset", options: { bullet: true, fontSize: 12 } }, + { text: "bAutoStartBehavior — per-NPC override", options: { bullet: true, fontSize: 12 } }, + { text: "RuntimeTraits — editable at runtime", options: { bullet: true, fontSize: 12 } }, + { text: "PerceivedThreatLevel — replicated", options: { bullet: true, fontSize: 12 } }, + { text: "CurrentState — replicated with OnRep", options: { bullet: true, fontSize: 12 } }, + { text: "EvaluateReaction() / ApplyReaction()", options: { bullet: true, fontSize: 12 } }, + { text: "ModifyTrait() / GetTrait()", options: { bullet: true, fontSize: 12 } }, + { text: "ForceState() — scripting override", options: { bullet: true, fontSize: 12 } }, + { text: "OnBehaviorStateChanged delegate", options: { bullet: true, fontSize: 12 } }, +], { x: 7, y: 2.55, w: 6, h: 4.5, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 3 }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 8 — PERSONALITY PROFILE +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("PersonalityProfile DataAsset", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 32, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("The behavioral recipe — one profile per NPC archetype.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const profileCats = [ + { title: "Identity", items: ["ProfileName", "NPCType", "Faction (0-15)"] }, + { title: "Traits", items: ["Courage (0-1)", "Aggressivity (0-1)", "Caution (0-1)"] }, + { title: "Thresholds", items: ["FleeThreshold", "AttackThreshold", "AlertThreshold", "ThreatDecayRate"] }, + { title: "Combat", items: ["TargetPriority[]", "MinAttackRange / MaxAttackRange", "CombatCoverCycleDuration"] }, + { title: "Movement", items: ["SpeedPerState map", "DefaultWalkSpeed"] }, + { title: "Behavior", items: ["DefaultBehaviorTree (soft ref)"] }, +]; +profileCats.forEach((c, i) => { + const col = i % 3; + const row = Math.floor(i / 3); + const x = 0.5 + col * 4.15; + const y = 2.2 + row * 2.3; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 3.95, h: 2.1, fill: { color: SAND }, line: { color: TERRA, width: 1 }, rectRadius: 0.08 }); + s.addText(c.title, { x: x + 0.15, y: y + 0.1, w: 3.65, h: 0.35, fontSize: 14, bold: true, color: TERRA, fontFace: FONT_H }); + s.addShape(pres.ShapeType.rect, { x: x + 0.15, y: y + 0.45, w: 0.35, h: 0.04, fill: { color: GOLD } }); + s.addText(c.items.map(t => ({ text: t, options: { bullet: true, fontSize: 11 } })), + { x: x + 0.15, y: y + 0.55, w: 3.65, h: 1.4, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 3 }); +}); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 6.85, w: 12.3, h: 0.35, fill: { color: TERRA }, rectRadius: 0.04 }); +s.addText("Example presets: Cowardly Villager • Aggressive Guard • Rival Gang Member", { x: 0.7, y: 6.88, w: 12, h: 0.3, fontSize: 11, color: SAND, italic: true, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 9 — COMPONENTS OVERVIEW +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Components", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Attach to Pawns as needed — most are optional.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const components = [ + { name: "Personality", desc: "Traits, profile, state reactions", required: true }, + { name: "Perception", desc: "AI Perception wrapper, threat computation", required: "auto" }, + { name: "Team", desc: "Identity for non-AI pawns (player)", required: false }, + { name: "Combat", desc: "Attack events, fire/kill delegates", required: false }, + { name: "Spline Follower", desc: "Spline walking, junction detection", required: false }, +]; +components.forEach((c, i) => { + const y = 2.2 + i * 0.9; + s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 3.5, h: 0.8, fill: { color: TERRA }, rectRadius: 0.08 }); + s.addText(c.name, { x: 0.5, y: y, w: 3.5, h: 0.8, fontSize: 16, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); + + s.addShape(pres.ShapeType.roundRect, { x: 4.2, y: y, w: 7.0, h: 0.8, fill: { color: SAND }, line: { color: TERRA, width: 1 }, rectRadius: 0.06 }); + s.addText(c.desc, { x: 4.4, y: y + 0.1, w: 6.8, h: 0.6, fontSize: 13, color: CHARCOAL, valign: "middle", fontFace: FONT_B }); + + let tagText = c.required === true ? "Required" : (c.required === "auto" ? "Auto" : "Optional"); + let tagColor = c.required === true ? TERRA : (c.required === "auto" ? SAGE_D : GOLD); + s.addShape(pres.ShapeType.roundRect, { x: 11.4, y: y + 0.2, w: 1.4, h: 0.4, fill: { color: tagColor }, rectRadius: 0.08 }); + s.addText(tagText, { x: 11.4, y: y + 0.2, w: 1.4, h: 0.4, fontSize: 11, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 10 — PERCEPTION COMPONENT +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Perception Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Sight, hearing, damage senses — auto-configured.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +s.addText("Senses", { x: 0.5, y: 2.15, w: 4, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "Sight — vision cone + line of sight", options: { bullet: true, fontSize: 13 } }, + { text: "Hearing — noise events with tags", options: { bullet: true, fontSize: 13 } }, + { text: "Damage — damage received tracking", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.55, w: 4, h: 2, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Threat Scoring", { x: 4.8, y: 2.15, w: 4, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "Target priority (from Profile)", options: { bullet: true, fontSize: 13 } }, + { text: "Recent damage weight", options: { bullet: true, fontSize: 13 } }, + { text: "Proximity weight", options: { bullet: true, fontSize: 13 } }, + { text: "Target persistence (80% score hold)", options: { bullet: true, fontSize: 13 } }, +], { x: 4.8, y: 2.55, w: 4.2, h: 2.2, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Key Methods", { x: 9.3, y: 2.15, w: 3.5, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "GetHighestThreatActor()", options: { bullet: true, fontSize: 12 } }, + { text: "CalculateThreatLevel()", options: { bullet: true, fontSize: 12 } }, + { text: "GetThreatLocation()", options: { bullet: true, fontSize: 12 } }, + { text: "ClassifyActor()", options: { bullet: true, fontSize: 12 } }, +], { x: 9.3, y: 2.55, w: 3.5, h: 2, color: CHARCOAL, fontFace: "Consolas", paraSpaceAfter: 4 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.15, w: 12.3, h: 1.85, fill: { color: TERRA }, rectRadius: 0.1 }); +s.addText("Perception / Threat Resolution", { x: 0.7, y: 5.25, w: 12, h: 0.35, fontSize: 14, bold: true, color: GOLD, fontFace: FONT_H }); +s.addText("Detects ALL affiliations → filters by Hostile attitude. Supports separate ThreatTarget (e.g. AimTargetActor sphere) vs. owning Pawn. Line-of-sight tracking with last-known-position memory.", { + x: 0.7, y: 5.65, w: 12, h: 1.3, fontSize: 12, color: SAND, italic: true, fontFace: FONT_B +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 11 — COMBAT COMPONENT +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Combat Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Attack execution, cooldown, damage events.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +s.addText("Properties", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "AttackRange — max distance (cm)", options: { bullet: true, fontSize: 13 } }, + { text: "AttackCooldown — minimum between attacks", options: { bullet: true, fontSize: 13 } }, + { text: "AttackDamage — base damage value", options: { bullet: true, fontSize: 13 } }, + { text: "CurrentTarget — replicated", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.55, w: 6, h: 2.3, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Methods & Events", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "CanAttack() — cooldown check", options: { bullet: true, fontSize: 13 } }, + { text: "IsInAttackRange() — distance test", options: { bullet: true, fontSize: 13 } }, + { text: "ExecuteAttack() — apply damage + multicast", options: { bullet: true, fontSize: 13 } }, + { text: "OnAttackExecuted (multicast)", options: { bullet: true, fontSize: 13 } }, + { text: "OnDamageReceived (server-only)", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 2.55, w: 6, h: 2.5, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.3, w: 12.3, h: 1.7, fill: { color: TERRA }, rectRadius: 0.1 }); +s.addText("Combat Type", { x: 0.7, y: 5.4, w: 12, h: 0.35, fontSize: 14, bold: true, color: GOLD, fontFace: FONT_H }); +s.addText("NPC's combat type (Melee / Ranged) comes from IPS_AI_Behavior::GetBehaviorCombatType(). Melee = rush the target (no cooldown between re-positions). Ranged = maintain distance, use cover cycle, peek to fire.", { + x: 0.7, y: 5.75, w: 12, h: 1.2, fontSize: 12, color: SAND, italic: true, fontFace: FONT_B +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 12 — WORLD ACTORS +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("World Actors", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Placed by level designers — drive navigation and tactics.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const actors = [ + { name: "SplinePath", desc: "Placeable spline defining a navigation corridor. Filtered by NPCType. Bidirectional, with priority for junction selection." }, + { name: "SplineNetwork", desc: "WorldSubsystem — auto-detects intersections between SplinePath actors. Provides navigation queries, threat-aware switching." }, + { name: "CoverPoint", desc: "Manually placed tactical position. Cover or HidingSpot type, quality score, crouch flag, max occupants, type filter." }, +]; +actors.forEach((a, i) => { + const y = 2.2 + i * 1.5; + s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 12.3, h: 1.35, fill: { color: SAND }, line: { color: TERRA, width: 1 }, rectRadius: 0.1 }); + s.addShape(pres.ShapeType.rect, { x: 0.5, y: y, w: 3.5, h: 1.35, fill: { color: TERRA } }); + s.addText(a.name, { x: 0.5, y: y, w: 3.5, h: 1.35, fontSize: 20, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); + s.addText(a.desc, { x: 4.2, y: y + 0.15, w: 8.4, h: 1.05, fontSize: 13, color: CHARCOAL, valign: "middle", fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 13 — BEHAVIOR TREE SERVICES & TASKS +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Behavior Tree — Services & Tasks", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 30, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Reusable BT nodes — build custom trees per NPC type.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +s.addText("Services (root-level)", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +const services = [ + { name: "UpdateThreat", desc: "Queries perception, writes threat BB keys" }, + { name: "EvaluateReaction", desc: "Computes state from traits + threat" }, + { name: "UpdateGaze", desc: "Manages eye contact, ConvAgent bridge" }, +]; +services.forEach((svc, i) => { + const y = 2.55 + i * 0.7; + s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 2.3, h: 0.55, fill: { color: SAGE_D }, rectRadius: 0.06 }); + s.addText(svc.name, { x: 0.5, y: y, w: 2.3, h: 0.55, fontSize: 12, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); + s.addText(svc.desc, { x: 2.9, y: y, w: 3.7, h: 0.55, fontSize: 11, color: CHARCOAL, valign: "middle", fontFace: FONT_B }); +}); + +s.addText("Tasks (leaves)", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +const tasks = [ + { name: "FollowSpline", desc: "Drives SplineFollower" }, + { name: "FindAndFollowSpline", desc: "Auto-locate + follow" }, + { name: "Patrol", desc: "Cycle PatrolPoints" }, + { name: "FindCover", desc: "Manual + procedural cover" }, + { name: "CoverShootCycle", desc: "Cover → peek → fire cycle" }, + { name: "Attack", desc: "Chase + attack (Melee/Ranged)" }, + { name: "FleeFrom", desc: "Move away from threat" }, +]; +tasks.forEach((task, i) => { + const y = 2.55 + i * 0.58; + s.addShape(pres.ShapeType.roundRect, { x: 7, y: y, w: 2.3, h: 0.45, fill: { color: TERRA }, rectRadius: 0.06 }); + s.addText(task.name, { x: 7, y: y, w: 2.3, h: 0.45, fontSize: 11, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); + s.addText(task.desc, { x: 9.4, y: y, w: 3.5, h: 0.45, fontSize: 10, color: CHARCOAL, valign: "middle", fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 14 — COVER SYSTEM +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Cover System", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Manual CoverPoints + procedural raycast fallback + EQS refinement.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const coverCols = [ + { + title: "Manual CoverPoints", + items: ["Placed by level designer", "Type: Cover or HidingSpot", "Quality score (0–1)", "MaxOccupants, Crouch flag", "Type filter (Any / Civ / Enemy / Protector)"] + }, + { + title: "Procedural", + items: ["Fallback if no manual points", "Raycast sampling around threat", "LOS blockage test", "Advancement bias toward threat"] + }, + { + title: "EQS Refinement", + items: ["OnCircle generator around cover", "LineOfSight filter", "Distance + quality tests", "Optional firing position refine"] + }, +]; +coverCols.forEach((c, i) => { + const x = 0.5 + i * 4.28; + s.addShape(pres.ShapeType.roundRect, { x: x, y: 2.2, w: 4.08, h: 3.1, fill: { color: SAND }, line: { color: TERRA, width: 1 }, rectRadius: 0.1 }); + s.addText(c.title, { x: x + 0.2, y: 2.3, w: 3.78, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); + s.addShape(pres.ShapeType.rect, { x: x + 0.2, y: 2.72, w: 0.4, h: 0.04, fill: { color: GOLD } }); + s.addText(c.items.map(t => ({ text: t, options: { bullet: true, fontSize: 11 } })), + { x: x + 0.2, y: 2.85, w: 3.78, h: 2.3, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 3 }); +}); + +// Cover cycle state machine +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.55, w: 12.3, h: 1.45, fill: { color: TERRA }, rectRadius: 0.1 }); +s.addText("Cover Cycle State Machine (CombatSubState)", { x: 0.7, y: 5.65, w: 12, h: 0.35, fontSize: 14, bold: true, color: GOLD, fontFace: FONT_H }); + +const subStates = ["Engaging", "AtCover", "MovingToFire", "Peeking", "ReturningToCover", "Advancing"]; +subStates.forEach((sub, i) => { + const x = 0.7 + i * 2.0; + s.addShape(pres.ShapeType.roundRect, { x: x, y: 6.15, w: 1.9, h: 0.7, fill: { color: TERRA_D }, rectRadius: 0.05 }); + s.addText(sub, { x: x, y: 6.15, w: 1.9, h: 0.7, fontSize: 11, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 15 — INTERFACE (IMPLEMENTERS GUIDE) +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("IPS_AI_Behavior_Interface", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("What your Pawn must implement (C++ or Blueprint).", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const ifCats = [ + { + title: "Identity", + items: ["GetBehaviorNPCType()", "SetBehaviorNPCType()", "IsBehaviorHostile()", "SetBehaviorHostile()"] + }, + { + title: "Movement", + items: ["SetBehaviorMovementSpeed()", "GetBehaviorMovementSpeed()", "SetBehaviorCrouch()"] + }, + { + title: "State Feedback", + items: ["OnBehaviorStateChanged()", "IsTargetActorValid()", "GetBehaviorThreatActor()"] + }, + { + title: "Combat", + items: ["BehaviorStartAttack()", "BehaviorStopAttack()", "CanBehaviorAttack()", "GetBehaviorCombatType()"] + }, +]; +ifCats.forEach((c, i) => { + const col = i % 2; + const row = Math.floor(i / 2); + const x = 0.5 + col * 6.3; + const y = 2.2 + row * 2.4; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 6.0, h: 2.2, fill: { color: TERRA }, rectRadius: 0.1 }); + s.addText(c.title, { x: x + 0.2, y: y + 0.15, w: 5.6, h: 0.4, fontSize: 16, bold: true, color: GOLD, fontFace: FONT_H }); + s.addText(c.items.map(t => ({ text: t, options: { bullet: true, fontSize: 12 } })), + { x: x + 0.2, y: y + 0.6, w: 5.6, h: 1.55, color: SAND, fontFace: "Consolas", paraSpaceAfter: 3 }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 16 — SETUP WORKFLOW +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Setup Workflow", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("From zero to a behaving NPC in 6 steps.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const bsteps = [ + { num: "1", title: "Personality Profile", desc: "Create DataAsset — traits, thresholds, DefaultBehaviorTree." }, + { num: "2", title: "Implement Interface", desc: "Pawn: implement IPS_AI_Behavior_Interface (identity, combat, movement)." }, + { num: "3", title: "Add Components", desc: "Pawn: add PersonalityComponent. Optional: Combat, SplineFollower." }, + { num: "4", title: "Behavior Tree", desc: "Branch on BehaviorState. Add services UpdateThreat + EvaluateReaction at root." }, + { num: "5", title: "Set AIController", desc: "Pawn's AIController class = APS_AI_Behavior_AIController." }, + { num: "6", title: "Place World Actors", desc: "Optional: SplinePaths, CoverPoints, SplineNetwork (auto)." }, +]; +bsteps.forEach((step, i) => { + const y = 2.2 + i * 0.78; + s.addShape(pres.ShapeType.ellipse, { x: 0.5, y: y, w: 0.7, h: 0.7, fill: { color: GOLD } }); + s.addText(step.num, { x: 0.5, y: y, w: 0.7, h: 0.7, fontSize: 24, bold: true, color: TERRA, align: "center", valign: "middle", fontFace: FONT_H }); + s.addShape(pres.ShapeType.roundRect, { x: 1.4, y: y, w: 11.4, h: 0.7, fill: { color: SAND }, line: { color: TERRA, width: 1 }, rectRadius: 0.05 }); + s.addText(step.title, { x: 1.6, y: y + 0.05, w: 3.5, h: 0.3, fontSize: 13, bold: true, color: TERRA, fontFace: FONT_H }); + s.addText(step.desc, { x: 1.6, y: y + 0.35, w: 11.2, h: 0.35, fontSize: 11, color: CHARCOAL, italic: true, fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 17 — BLACKBOARD KEYS +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Blackboard Keys", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Auto-created by the AIController — use in your BT decorators and tasks.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const bbKeys = [ + { name: "BehaviorState", type: "Enum", desc: "Current state (Idle, Combat, etc.)" }, + { name: "ThreatActor", type: "Object", desc: "Current highest-scored threat" }, + { name: "ThreatLocation", type: "Vector", desc: "Last known threat position" }, + { name: "ThreatLevel", type: "Float", desc: "Perceived threat (0–1+)" }, + { name: "CoverLocation", type: "Vector", desc: "Target cover position" }, + { name: "CoverPoint", type: "Object", desc: "Current CoverPoint actor" }, + { name: "PatrolIndex", type: "Int", desc: "Current patrol waypoint index" }, + { name: "HomeLocation", type: "Vector", desc: "Spawn / home point" }, + { name: "CurrentSpline", type: "Object", desc: "Active SplinePath" }, + { name: "SplineProgress", type: "Float", desc: "Distance along current spline" }, + { name: "CombatSubState", type: "Enum", desc: "Cover cycle sub-state (Peeking, etc.)" }, + { name: "LastKnownTargetPosition", type: "Vector", desc: "LOS-lost target memory" }, + { name: "PreferCover", type: "Bool", desc: "Combat/cover cycle toggle" }, + { name: "ConversationPaused", type: "Bool", desc: "Active dialogue pause flag" }, +]; +bbKeys.forEach((k, i) => { + const col = i % 2; + const row = Math.floor(i / 2); + const x = 0.5 + col * 6.3; + const y = 2.2 + row * 0.62; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 1.8, h: 0.5, fill: { color: TERRA }, rectRadius: 0.05 }); + s.addText(k.name, { x: x, y: y, w: 1.8, h: 0.5, fontSize: 10, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: "Consolas" }); + s.addShape(pres.ShapeType.roundRect, { x: x + 1.85, y: y + 0.1, w: 0.6, h: 0.3, fill: { color: GOLD }, rectRadius: 0.05 }); + s.addText(k.type, { x: x + 1.85, y: y + 0.1, w: 0.6, h: 0.3, fontSize: 9, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); + s.addText(k.desc, { x: x + 2.55, y: y, w: 3.7, h: 0.5, fontSize: 10, color: CHARCOAL, valign: "middle", italic: true, fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 18 — NETWORKING +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Networking", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Server-authoritative AI, minimal replication for visuals.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +s.addText("Server-only", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "All AI planning & decision-making", options: { bullet: true, fontSize: 13 } }, + { text: "Threat evaluation, state transitions", options: { bullet: true, fontSize: 13 } }, + { text: "Perception stimuli processing", options: { bullet: true, fontSize: 13 } }, + { text: "Path selection, cover choice", options: { bullet: true, fontSize: 13 } }, + { text: "Combat targeting", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.55, w: 6, h: 2.7, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Replicated to clients", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "CurrentState (OnRep fires delegate)", options: { bullet: true, fontSize: 13 } }, + { text: "PerceivedThreatLevel (HUD/debug)", options: { bullet: true, fontSize: 13 } }, + { text: "CurrentTarget (anims/visuals)", options: { bullet: true, fontSize: 13 } }, + { text: "CurrentSpline, bIsFollowing", options: { bullet: true, fontSize: 13 } }, + { text: "OnAttackExecuted (multicast)", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 2.55, w: 6, h: 2.7, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.4, w: 12.3, h: 1.6, fill: { color: TERRA }, rectRadius: 0.1 }); +s.addText("Character movement syncs via CharacterMovementComponent. Spline follower updates server-side, position replicates naturally. Clients receive enough state for animation and HUD without any AI logic running locally.", { + x: 0.7, y: 5.6, w: 12, h: 1.2, fontSize: 13, color: SAND, italic: true, fontFace: FONT_B, valign: "middle" +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 19 — INTEGRATION WITH CONVAGENT +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Integration with PS_AI_ConvAgent", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 28, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Optional — behavior and conversation plugins talk via reflection.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +const integrations = [ + { title: "Gaze Bridge", desc: "AIController discovers GazeComponent on the Pawn, sets target during Idle/Patrol (proximity gaze) and forwards current threat during Combat." }, + { title: "Conversation Pause", desc: "When ConvAgent is conversing, AI planning pauses — NPC stops moving, waits. Local VAD detects user speech." }, + { title: "Disguise Reveal", desc: "Enemy NPC with IsBehaviorHostile=false starts as Civilian TeamId. Flips to hostile via interface when triggered by dialogue." }, + { title: "Forced State", desc: "ConvAgent LLM can invoke tools that call ForceState(Scripted) or ForceState(Fleeing) for narrative control." }, +]; +integrations.forEach((item, i) => { + const col = i % 2; + const row = Math.floor(i / 2); + const x = 0.5 + col * 6.3; + const y = 2.2 + row * 2.3; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 6.0, h: 2.1, fill: { color: SAND }, line: { color: TERRA, width: 1 }, rectRadius: 0.1 }); + s.addText(item.title, { x: x + 0.2, y: y + 0.15, w: 5.6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); + s.addShape(pres.ShapeType.rect, { x: x + 0.2, y: y + 0.58, w: 0.4, h: 0.04, fill: { color: GOLD } }); + s.addText(item.desc, { x: x + 0.2, y: y + 0.7, w: 5.6, h: 1.3, fontSize: 12, color: CHARCOAL, fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 20 — DEBUG TOOLS +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Debug Tools", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: TERRA, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: GOLD } }); +s.addText("Visualize state, perception, and targeting at runtime.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: CHARCOAL, italic: true, fontFace: FONT_B }); + +s.addText("Per-NPC Debug", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "PersonalityComponent.bDebug — floating text", options: { bullet: true, fontSize: 13 } }, + { text: "Name, NPCType, TeamId, State", options: { bullet: true, fontSize: 13 } }, + { text: "ThreatLevel, ThreatActor", options: { bullet: true, fontSize: 13 } }, + { text: "Hostile, active spline", options: { bullet: true, fontSize: 13 } }, + { text: "SplineFollower debug sphere (green)", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.55, w: 6, h: 2.8, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Global Logging", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: TERRA, fontFace: FONT_H }); +s.addText([ + { text: "CVar: ps.ai.Behavior.Debug 1", options: { bullet: true, fontSize: 13 } }, + { text: "Toggles verbose logs globally", options: { bullet: true, fontSize: 13 } }, + { text: "LogPS_AI_Behavior category", options: { bullet: true, fontSize: 13 } }, + { text: "BTView / AIDebug (Unreal built-in)", options: { bullet: true, fontSize: 13 } }, + { text: "EQS Visualizer for cover queries", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 2.55, w: 6, h: 2.8, color: CHARCOAL, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.5, w: 12.3, h: 1.5, fill: { color: TERRA }, rectRadius: 0.1 }); +s.addText("Pro tip", { x: 0.7, y: 5.6, w: 12, h: 0.35, fontSize: 14, bold: true, color: GOLD, fontFace: FONT_H }); +s.addText("Enable bDebug only on specific NPCs (or via a debug HUD key). Global debug logs can be overwhelming in crowded scenes. The floating text is cheap but accumulates.", + { x: 0.7, y: 5.95, w: 12, h: 1.0, fontSize: 12, color: SAND, italic: true, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 21 — SUMMARY +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide(); +s.background = { color: TERRA }; +s.addShape(pres.ShapeType.rect, { x: 0, y: 2.5, w: 13.333, h: 0.05, fill: { color: GOLD } }); +s.addText("Summary", { x: 0.6, y: 0.8, w: 12, h: 1.2, fontSize: 54, bold: true, color: WHITE, fontFace: FONT_H }); +s.addText("Complete NPC behavior stack for tactical game AI.", { x: 0.6, y: 2.0, w: 12, h: 0.5, fontSize: 18, color: SAND, italic: true, fontFace: FONT_B }); + +const bsummary = [ + { h: "Personality-driven", t: "Three traits modulate every decision" }, + { h: "Cover-aware", t: "Manual CoverPoints + procedural fallback + EQS" }, + { h: "Spline navigation", t: "Network with junction detection, type filtering" }, + { h: "Perception built-in", t: "Sight + hearing + damage, priority-based threat" }, + { h: "Server-authoritative", t: "Minimal replication, client just animates" }, + { h: "Plugin-friendly", t: "Gaze bridge to ConvAgent, interface-only dependency" }, +]; +bsummary.forEach((item, i) => { + const col = i % 2; + const row = Math.floor(i / 2); + const x = 0.6 + col * 6.2; + const y = 3.0 + row * 1.4; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 5.9, h: 1.2, fill: { color: TERRA_D }, line: { color: GOLD, width: 1 }, rectRadius: 0.1 }); + s.addText(item.h, { x: x + 0.2, y: y + 0.1, w: 5.5, h: 0.4, fontSize: 18, bold: true, color: GOLD, fontFace: FONT_H }); + s.addText(item.t, { x: x + 0.2, y: y + 0.55, w: 5.5, h: 0.6, fontSize: 12, color: SAND, italic: true, fontFace: FONT_B }); +}); + +s.addText("ASTERION • PS_AI_Behavior • 2026", { x: 0.6, y: 6.9, w: 12, h: 0.4, fontSize: 12, color: SAND, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SAVE +// ═══════════════════════════════════════════════════════════════════ +pres.writeFile({ fileName: "PS_AI_Behavior_Documentation.pptx" }) + .then(name => console.log(`Generated: ${name}`)) + .catch(err => { console.error(err); process.exit(1); }); diff --git a/Docs/gen_convagent_doc.js b/Docs/gen_convagent_doc.js new file mode 100644 index 0000000..4d829c8 --- /dev/null +++ b/Docs/gen_convagent_doc.js @@ -0,0 +1,711 @@ +// PS_AI_ConvAgent User Documentation Generator +// Palette: Midnight Executive (navy #1E2761, ice blue #CADCFC, white) + +const pptxgen = require("pptxgenjs"); +const pres = new pptxgen(); + +pres.layout = "LAYOUT_WIDE"; // 13.333 x 7.5 inch + +// ─── Color Palette ───────────────────────────────── +const NAVY = "1E2761"; +const NAVY_DARK = "141B45"; +const ICE = "CADCFC"; +const ICE_DEEP = "7892D6"; +const WHITE = "FFFFFF"; +const GREY = "3A3F5C"; +const LIGHT_GREY= "F5F7FC"; +const ACCENT = "F9A825"; // warm gold accent + +// ─── Font ────────────────────────────────────────── +const FONT_H = "Calibri"; +const FONT_B = "Calibri"; + +// ─── Master slide backgrounds ────────────────────── +pres.defineSlideMaster({ + title: "CONTENT", + background: { color: LIGHT_GREY }, + objects: [ + { rect: { x: 0, y: 0, w: 13.333, h: 0.35, fill: { color: NAVY } } }, + { rect: { x: 0, y: 7.15, w: 13.333, h: 0.35, fill: { color: NAVY } } }, + { text: { + text: "PS_AI_ConvAgent — User Documentation", + options: { x: 0.3, y: 7.18, w: 8, h: 0.3, fontSize: 10, color: ICE, fontFace: FONT_B } + } }, + { text: { + text: "ASTERION", + options: { x: 11.5, y: 7.18, w: 1.5, h: 0.3, fontSize: 10, color: ICE, fontFace: FONT_B, align: "right", bold: true } + } }, + ], +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 1 — TITLE +// ═══════════════════════════════════════════════════════════════════ +let s = pres.addSlide(); +s.background = { color: NAVY }; +s.addShape(pres.ShapeType.rect, { x: 0, y: 3.3, w: 13.333, h: 0.05, fill: { color: ACCENT } }); +s.addText("PS_AI_ConvAgent", { x: 0.6, y: 2.2, w: 12, h: 1.2, fontSize: 60, bold: true, color: WHITE, fontFace: FONT_H }); +s.addText("Conversational AI for MetaHumans in Unreal Engine", { x: 0.6, y: 3.5, w: 12, h: 0.6, fontSize: 22, color: ICE, fontFace: FONT_B, italic: true }); +s.addText("User Documentation", { x: 0.6, y: 4.4, w: 12, h: 0.5, fontSize: 18, color: ICE_DEEP, fontFace: FONT_B }); +s.addText("ElevenLabs • MetaHuman • Listen Server", { x: 0.6, y: 5.2, w: 12, h: 0.4, fontSize: 14, color: ICE_DEEP, fontFace: FONT_B }); +s.addText("ASTERION • 2026", { x: 0.6, y: 6.6, w: 12, h: 0.4, fontSize: 12, color: ICE_DEEP, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 2 — OVERVIEW +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Plugin Overview", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); + +s.addText("What it does", { x: 0.5, y: 1.7, w: 6, h: 0.4, fontSize: 18, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "Full conversational AI NPC integration for Unreal Engine", options: { bullet: true, fontSize: 14 } }, + { text: "Real-time voice capture, turn-taking, TTS playback", options: { bullet: true, fontSize: 14 } }, + { text: "Emotion-driven MetaHuman animations (face, body, gaze)", options: { bullet: true, fontSize: 14 } }, + { text: "Multiplayer support with speaker arbitration", options: { bullet: true, fontSize: 14 } }, + { text: "Tool system for LLM-triggered game actions", options: { bullet: true, fontSize: 14 } }, +], { x: 0.5, y: 2.1, w: 6, h: 2.6, color: GREY, fontFace: FONT_B, paraSpaceAfter: 8 }); + +s.addShape(pres.ShapeType.roundRect, { x: 7, y: 1.7, w: 5.8, h: 4.8, fill: { color: NAVY }, rectRadius: 0.1 }); +s.addText("Primary Use Case", { x: 7.2, y: 1.9, w: 5.4, h: 0.4, fontSize: 16, bold: true, color: ACCENT, fontFace: FONT_H }); +s.addText("Interactive, emotionally expressive voice NPCs that listen to players, respond with contextual answers, express emotion through facial animation and body language, and trigger physical actions based on conversation flow.", { + x: 7.2, y: 2.4, w: 5.4, h: 1.7, fontSize: 13, color: ICE, fontFace: FONT_B, italic: true +}); +s.addText("Key Differentiators", { x: 7.2, y: 4.2, w: 5.4, h: 0.4, fontSize: 16, bold: true, color: ACCENT, fontFace: FONT_H }); +s.addText([ + { text: "Full-duplex Server VAD or push-to-talk", options: { bullet: true, fontSize: 12 } }, + { text: "Adaptive pre-buffering & speculative TTS", options: { bullet: true, fontSize: 12 } }, + { text: "Spectral lip sync + artist-driven poses", options: { bullet: true, fontSize: 12 } }, + { text: "Opus codec + distance LOD for multiplayer", options: { bullet: true, fontSize: 12 } }, +], { x: 7.2, y: 4.7, w: 5.4, h: 1.7, color: ICE, fontFace: FONT_B, paraSpaceAfter: 4 }); + +s.addText("\"Drop components on a MetaHuman, configure a few data assets, and talk.\"", { + x: 0.5, y: 5.3, w: 6, h: 0.6, fontSize: 13, color: NAVY, italic: true, fontFace: FONT_B, bold: true +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 3 — ARCHITECTURE DIAGRAM +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Architecture", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); + +s.addText("Component chain on a MetaHuman NPC", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +// Input row +const boxY = 2.4; +const boxH = 1.0; +const boxes = [ + { x: 0.5, label: "Microphone\nCapture", sub: "VAD • 16kHz" }, + { x: 2.6, label: "Interaction\nComponent", sub: "Agent select\n(player side)" }, + { x: 4.7, label: "ElevenLabs\nComponent", sub: "WebSocket\nTurn-taking" }, + { x: 6.8, label: "Lip Sync\nComponent", sub: "Spectral\nFFT → visemes" }, + { x: 8.9, label: "Facial Expr.\nComponent", sub: "Emotion\ncurves" }, + { x: 11.0, label: "Body Expr.\nComponent", sub: "Gesture\nanim blend" }, +]; +boxes.forEach((b, i) => { + s.addShape(pres.ShapeType.roundRect, { x: b.x, y: boxY, w: 2.0, h: boxH, fill: { color: NAVY }, rectRadius: 0.1 }); + s.addText(b.label, { x: b.x, y: boxY + 0.1, w: 2.0, h: 0.5, fontSize: 13, bold: true, color: WHITE, align: "center", fontFace: FONT_H }); + s.addText(b.sub, { x: b.x, y: boxY + 0.58, w: 2.0, h: 0.4, fontSize: 9, color: ICE, align: "center", fontFace: FONT_B }); +}); + +// Arrows between boxes +for (let i = 0; i < boxes.length - 1; i++) { + const ax = boxes[i].x + 2.0; + s.addShape(pres.ShapeType.line, { x: ax, y: boxY + 0.5, w: 0.1, h: 0, line: { color: ACCENT, width: 2 } }); +} + +// Gaze row +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 4.0, w: 2.0, h: boxH, fill: { color: ICE_DEEP }, rectRadius: 0.1 }); +s.addText("Gaze\nComponent", { x: 0.5, y: 4.1, w: 2.0, h: 0.5, fontSize: 13, bold: true, color: WHITE, align: "center", fontFace: FONT_H }); +s.addText("Eyes • Head\n• Body", { x: 0.5, y: 4.58, w: 2.0, h: 0.4, fontSize: 9, color: WHITE, align: "center", fontFace: FONT_B }); + +// AnimGraph pipeline +s.addText("→ AnimGraph (Face + Body)", { x: 2.8, y: 4.2, w: 4.5, h: 0.5, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText("FacialExpression → LipSync | Gaze → BodyExpression", { x: 2.8, y: 4.65, w: 8, h: 0.4, fontSize: 12, color: GREY, italic: true, fontFace: FONT_B }); + +// Project settings layer +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.6, w: 12.3, h: 0.9, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 }); +s.addText("PROJECT SETTINGS — API Key • Server Region • Global Context Prompt", { x: 0.7, y: 5.75, w: 12, h: 0.3, fontSize: 13, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText("Data Assets: AgentConfig • EmotionPoseMap • LipSyncPoseMap • BodyPoseMap • ActionSet • Tool", { x: 0.7, y: 6.1, w: 12, h: 0.3, fontSize: 11, color: GREY, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 4 — ELEVENLABS COMPONENT +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("ElevenLabs Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("The core conversation engine — WebSocket, turn-taking, audio pipeline.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +s.addText("Key Properties", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "AgentConfig — identity, voice, tools", options: { bullet: true, fontSize: 12 } }, + { text: "TurnMode — ServerVAD / Client (push-to-talk)", options: { bullet: true, fontSize: 12 } }, + { text: "MicChunkDurationMs (50–250 ms)", options: { bullet: true, fontSize: 12 } }, + { text: "AudioPreBufferMs / bAdaptivePreBuffer", options: { bullet: true, fontSize: 12 } }, + { text: "bAllowInterruption, bPersistentSession", options: { bullet: true, fontSize: 12 } }, + { text: "SoundAttenuation (optional 3D audio)", options: { bullet: true, fontSize: 12 } }, + { text: "AudioLODCullDistance / LipSyncLODDistance", options: { bullet: true, fontSize: 12 } }, +], { x: 0.5, y: 2.55, w: 6, h: 3.6, color: GREY, fontFace: FONT_B, paraSpaceAfter: 4 }); + +s.addText("Key Methods", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "StartConversation() / EndConversation()", options: { bullet: true, fontSize: 12 } }, + { text: "StartListening() / StopListening()", options: { bullet: true, fontSize: 12 } }, + { text: "SendTextMessage() — text without voice", options: { bullet: true, fontSize: 12 } }, + { text: "InterruptAgent() — force stop mid-speech", options: { bullet: true, fontSize: 12 } }, + { text: "ForceDisableConversation(Action) — blend to neutral", options: { bullet: true, fontSize: 12 } }, + { text: "ForceEnableConversation() — re-enable", options: { bullet: true, fontSize: 12 } }, + { text: "FeedExternalAudio() — external mic source", options: { bullet: true, fontSize: 12 } }, +], { x: 7, y: 2.55, w: 6, h: 3.6, color: GREY, fontFace: FONT_B, paraSpaceAfter: 4 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 6.2, w: 12.3, h: 0.75, fill: { color: NAVY }, rectRadius: 0.08 }); +s.addText("Major events: OnAgentConnected • OnAgentTranscript • OnAgentTextResponse • OnAgentStartedSpeaking • OnAgentEmotionChanged • OnAgentActionRequested • OnActiveSpeakerChanged • OnReadyForAction", { x: 0.7, y: 6.3, w: 12, h: 0.55, fontSize: 11, color: ICE, fontFace: FONT_B, italic: true }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 5 — MICROPHONE CAPTURE +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Microphone Capture Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Captures, resamples, and performs local voice activity detection.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +s.addText("Audio Pipeline", { x: 0.5, y: 2.2, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "WASAPI 48kHz stereo float32 input", options: { bullet: true, fontSize: 13 } }, + { text: "Resample to 16kHz mono float", options: { bullet: true, fontSize: 13 } }, + { text: "Convert to int16 LE byte stream", options: { bullet: true, fontSize: 13 } }, + { text: "Delivered via multicast delegate", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.6, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 6 }); + +s.addText("Voice Activity Detection", { x: 7, y: 2.2, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "VolumeMultiplier — mic gain", options: { bullet: true, fontSize: 13 } }, + { text: "VoiceActivityThreshold — RMS threshold", options: { bullet: true, fontSize: 13 } }, + { text: "VoiceOnsetTime — debounce on speech start", options: { bullet: true, fontSize: 13 } }, + { text: "VoiceSilenceTime — debounce on end", options: { bullet: true, fontSize: 13 } }, + { text: "OnUserVoiceActivityChanged event", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 2.6, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 6 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.4, w: 12.3, h: 1.4, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 }); +s.addText("Public API", { x: 0.7, y: 5.5, w: 12, h: 0.3, fontSize: 13, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText("StartCapture() • StopCapture() • IsCapturing()", { x: 0.7, y: 5.85, w: 12, h: 0.3, fontSize: 13, color: GREY, fontFace: "Consolas" }); +s.addText("The component auto-opens the default input device. Output is consumed by ElevenLabsComponent (or InteractionComponent, which relays via Server RPC in networked games).", { x: 0.7, y: 6.2, w: 12, h: 0.55, fontSize: 11, color: GREY, italic: true, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 6 — LIP SYNC +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Lip Sync Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Spectral analysis of agent audio → 15 OVR visemes → ARKit blendshapes.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +s.addText("How it works", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "FFT on agent audio stream", options: { bullet: true, fontSize: 13 } }, + { text: "Estimates 15 OVR viseme weights", options: { bullet: true, fontSize: 13 } }, + { text: "Maps to ARKit blendshapes (jawOpen, mouthFunnel…)", options: { bullet: true, fontSize: 13 } }, + { text: "Optional: custom PoseMap with artist-crafted poses", options: { bullet: true, fontSize: 13 } }, + { text: "Thread-safe curve output for AnimGraph", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.55, w: 6, h: 3.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Key Properties", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "LipSyncStrength — overall intensity", options: { bullet: true, fontSize: 13 } }, + { text: "SmoothingSpeed (35–65) — viseme interp", options: { bullet: true, fontSize: 13 } }, + { text: "SpeechBlendDuration — fade in/out", options: { bullet: true, fontSize: 13 } }, + { text: "EmotionExpressionBlend (0–1) — emotion bleed through mouth", options: { bullet: true, fontSize: 13 } }, + { text: "EnvelopeAttackMs / EnvelopeReleaseMs", options: { bullet: true, fontSize: 13 } }, + { text: "PoseMap — optional artist override", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 2.55, w: 6, h: 3.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 6.2, w: 12.3, h: 0.75, fill: { color: NAVY }, rectRadius: 0.08 }); +s.addText("AnimGraph placement (Face AnimBP): Live Link → FacialExpression → LipSync → mh_arkit_mapping_pose", { x: 0.7, y: 6.3, w: 12, h: 0.55, fontSize: 12, color: ICE, fontFace: FONT_B, italic: true, bold: true }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 7 — FACIAL EXPRESSION +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Facial Expression Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Emotion-driven face animation via real-time AnimSequence sampling.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +s.addText("Design", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "Plays AnimSequences in real-time (looping)", options: { bullet: true, fontSize: 13 } }, + { text: "Extracts emotion curves (eyes, brows, cheeks, mouth mood)", options: { bullet: true, fontSize: 13 } }, + { text: "Injects into AnimGraph via FacialExpression AnimNode", options: { bullet: true, fontSize: 13 } }, + { text: "Lip sync overrides mouth area during speech", options: { bullet: true, fontSize: 13 } }, + { text: "3 intensity levels: Normal / Medium / Extreme", options: { bullet: true, fontSize: 13 } }, + { text: "Crossfades between emotions", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.55, w: 6, h: 3.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Properties", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "EmotionPoseMap — data asset of AnimSequences", options: { bullet: true, fontSize: 13 } }, + { text: "ActivationBlendDuration (default 0.5s)", options: { bullet: true, fontSize: 13 } }, + { text: "EmotionBlendDuration — crossfade time", options: { bullet: true, fontSize: 13 } }, + { text: "bActive — auto-managed by conversation", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 2.55, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addShape(pres.ShapeType.roundRect, { x: 7, y: 5.0, w: 5.8, h: 1.5, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 }); +s.addText("Emotions covered", { x: 7.15, y: 5.1, w: 5.6, h: 0.3, fontSize: 13, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText("Joy • Sadness • Anger • Fear • Surprise • Disgust • Neutral", { x: 7.15, y: 5.45, w: 5.6, h: 0.9, fontSize: 12, color: GREY, italic: true, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 8 — BODY EXPRESSION +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Body Expression Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Upper-body gestures and posture driven by emotion + speaking state.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +s.addText("Animation lists per emotion", { x: 0.5, y: 2.15, w: 6.3, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); + +const animLists = [ + { name: "Idle", desc: "Played while listening (not speaking)" }, + { name: "Normal", desc: "Low-intensity speaking anims" }, + { name: "Medium", desc: "Medium-intensity speaking" }, + { name: "Extreme", desc: "High-intensity speaking" }, +]; +animLists.forEach((a, i) => { + const y = 2.7 + i * 0.75; + s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 1.5, h: 0.55, fill: { color: NAVY }, rectRadius: 0.05 }); + s.addText(a.name, { x: 0.5, y: y, w: 1.5, h: 0.55, fontSize: 13, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); + s.addText(a.desc, { x: 2.15, y: y + 0.05, w: 4.6, h: 0.45, fontSize: 12, color: GREY, valign: "middle", fontFace: FONT_B }); +}); + +s.addText("Key Properties", { x: 7.2, y: 2.15, w: 5.8, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "BodyPoseMap — emotion → anim lists", options: { bullet: true, fontSize: 13 } }, + { text: "ActivationBlendDuration (0.5s)", options: { bullet: true, fontSize: 13 } }, + { text: "EmotionBlendDuration (1.0s)", options: { bullet: true, fontSize: 13 } }, + { text: "BlendWeight (0–1)", options: { bullet: true, fontSize: 13 } }, +], { x: 7.2, y: 2.55, w: 5.8, h: 2, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("AnimNode behaviour", { x: 7.2, y: 4.7, w: 5.8, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "Override mode, per-bone mask", options: { bullet: true, fontSize: 12 } }, + { text: "Default BlendRootBone = spine_02", options: { bullet: true, fontSize: 12 } }, + { text: "Excludes neck_01 (Gaze handles it)", options: { bullet: true, fontSize: 12 } }, + { text: "Lower body passes through upstream", options: { bullet: true, fontSize: 12 } }, +], { x: 7.2, y: 5.1, w: 5.8, h: 2, color: GREY, fontFace: FONT_B, paraSpaceAfter: 3 }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 9 — GAZE +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Gaze Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Multi-layer look-at: body • head • eyes.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +const layers = [ + { name: "BODY", ratio: "full 360°", desc: "Actor yaw — follows target around the character" }, + { name: "HEAD", ratio: "limited", desc: "Head bone yaw + pitch, clamped" }, + { name: "EYES", ratio: "limited", desc: "ARKit eye curves, fine targeting" }, +]; +layers.forEach((l, i) => { + const y = 2.3 + i * 1.1; + s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 2.2, h: 0.9, fill: { color: NAVY }, rectRadius: 0.08 }); + s.addText(l.name, { x: 0.5, y: y + 0.05, w: 2.2, h: 0.4, fontSize: 18, bold: true, color: WHITE, align: "center", fontFace: FONT_H }); + s.addText(l.ratio, { x: 0.5, y: y + 0.48, w: 2.2, h: 0.3, fontSize: 11, color: ACCENT, align: "center", italic: true, fontFace: FONT_B }); + s.addText(l.desc, { x: 3.0, y: y + 0.15, w: 5.0, h: 0.7, fontSize: 13, color: GREY, valign: "middle", fontFace: FONT_B }); +}); + +s.addText("Key Properties", { x: 8.5, y: 2.15, w: 4.5, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "TargetActor — who to look at", options: { bullet: true, fontSize: 12 } }, + { text: "MaxHeadYaw / MaxHeadPitch", options: { bullet: true, fontSize: 12 } }, + { text: "MaxEyeHorizontal / MaxEyeVertical", options: { bullet: true, fontSize: 12 } }, + { text: "BodyInterpSpeed / HeadInterpSpeed / EyeInterpSpeed", options: { bullet: true, fontSize: 12 } }, + { text: "HeadAnimationCompensation (0.9)", options: { bullet: true, fontSize: 12 } }, + { text: "EyeAnimationCompensation (0.6)", options: { bullet: true, fontSize: 12 } }, + { text: "BodyDriftCompensation (0.8)", options: { bullet: true, fontSize: 12 } }, + { text: "NeckBoneChain — multi-bone distribution", options: { bullet: true, fontSize: 12 } }, +], { x: 8.5, y: 2.55, w: 4.5, h: 4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 3 }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 10 — INTERACTION COMPONENT +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Interaction Component (Player)", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 32, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Placed on the player pawn — selects NPCs, routes mic, manages gaze.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +s.addText("Selection Logic", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "Proximity + view cone detection", options: { bullet: true, fontSize: 13 } }, + { text: "MaxInteractionDistance (300 cm default)", options: { bullet: true, fontSize: 13 } }, + { text: "ViewConeHalfAngle (45° default)", options: { bullet: true, fontSize: 13 } }, + { text: "SelectionStickyAngle — hysteresis", options: { bullet: true, fontSize: 13 } }, + { text: "ConversationSwitchDelay — anti-flicker", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.55, w: 6, h: 3, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Auto-management", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "bAutoStartConversation — open WS on select", options: { bullet: true, fontSize: 13 } }, + { text: "bAutoManageListening — mic open/close", options: { bullet: true, fontSize: 13 } }, + { text: "bAutoManageGaze — set agent gaze to player", options: { bullet: true, fontSize: 13 } }, + { text: "bAutoPassiveGaze — out-of-range passive tracking", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 2.55, w: 6, h: 2.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.8, w: 12.3, h: 1.2, fill: { color: NAVY }, rectRadius: 0.08 }); +s.addText("Events: OnAgentSelected • OnAgentDeselected • OnNoAgentInRange", { x: 0.7, y: 5.95, w: 12, h: 0.3, fontSize: 13, bold: true, color: ACCENT, fontFace: FONT_H }); +s.addText("Methods: GetSelectedAgent() • ForceSelectAgent() • StartConversationWithSelectedAgent() • SendTextToSelectedAgent() • ClearSelection()", { x: 0.7, y: 6.3, w: 12, h: 0.55, fontSize: 11, color: ICE, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 11 — DATA ASSETS OVERVIEW +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Data Assets", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Reusable configuration assets for agent identity, animations, and tools.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +const dataAssets = [ + { name: "AgentConfig", desc: "Agent identity, voice, LLM model, tools", color: NAVY }, + { name: "EmotionPoseMap", desc: "Emotion → facial AnimSequences (3 levels)", color: NAVY }, + { name: "LipSyncPoseMap", desc: "15 OVR visemes → mouth poses", color: NAVY }, + { name: "BodyPoseMap", desc: "Emotion → body gesture anim lists", color: NAVY }, + { name: "ActionSet", desc: "Actions the agent can trigger", color: ICE_DEEP }, + { name: "Tool", desc: "Custom LLM tools with parameters", color: ICE_DEEP }, +]; +dataAssets.forEach((d, i) => { + const col = i % 2; + const row = Math.floor(i / 2); + const x = 0.5 + col * 6.3; + const y = 2.2 + row * 1.5; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 6.0, h: 1.3, fill: { color: d.color }, rectRadius: 0.1 }); + s.addText(d.name, { x: x + 0.2, y: y + 0.2, w: 5.6, h: 0.4, fontSize: 18, bold: true, color: ACCENT, fontFace: FONT_H }); + s.addText(d.desc, { x: x + 0.2, y: y + 0.65, w: 5.6, h: 0.6, fontSize: 12, color: ICE, italic: true, fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 12 — AGENT CONFIG DETAILS +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("AgentConfig (ElevenLabs)", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Complete agent definition — editable in Content Browser with API buttons.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +const configCats = [ + { title: "Identity", items: ["AgentID", "AgentName"] }, + { title: "Voice", items: ["VoiceID / VoiceName", "TTSModelID", "Stability • Similarity • Speed"] }, + { title: "LLM", items: ["LLMModel (gemini-2.5-flash default)", "Language (en default)"] }, + { title: "Behavior", items: ["CharacterPrompt (personality)", "FirstMessage (greeting)", "TurnTimeout, MaxTurns"] }, + { title: "Latency", items: ["TurnEagerness (Eager/Normal/Patient)", "bSpeculativeTurn"] }, + { title: "Tools", items: ["Tools[] array of Tool assets", "ActionSet integration"] }, +]; + +configCats.forEach((c, i) => { + const col = i % 3; + const row = Math.floor(i / 3); + const x = 0.5 + col * 4.15; + const y = 2.2 + row * 2.15; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 3.95, h: 1.95, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 }); + s.addText(c.title, { x: x + 0.15, y: y + 0.1, w: 3.65, h: 0.35, fontSize: 14, bold: true, color: NAVY, fontFace: FONT_H }); + s.addShape(pres.ShapeType.rect, { x: x + 0.15, y: y + 0.45, w: 0.35, h: 0.04, fill: { color: ACCENT } }); + s.addText(c.items.map(t => ({ text: t, options: { bullet: true, fontSize: 11 } })), + { x: x + 0.15, y: y + 0.55, w: 3.65, h: 1.3, color: GREY, fontFace: FONT_B, paraSpaceAfter: 2 }); +}); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 6.55, w: 12.3, h: 0.45, fill: { color: NAVY }, rectRadius: 0.05 }); +s.addText("Editor: Voice picker • Model picker • LLM picker • Create / Update / Fetch Agent buttons", { x: 0.7, y: 6.6, w: 12, h: 0.35, fontSize: 11, color: ICE, fontFace: FONT_B, italic: true, bold: true }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 13 — POSE MAPS +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Pose Maps", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Three pose-map data assets map abstract states → AnimSequences.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +// Emotion +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 2.15, w: 4.0, h: 4.5, fill: { color: NAVY }, rectRadius: 0.1 }); +s.addText("EmotionPoseMap", { x: 0.6, y: 2.3, w: 3.8, h: 0.4, fontSize: 17, bold: true, color: ACCENT, fontFace: FONT_H }); +s.addText("Emotion → facial AnimSequences\n(3 intensity levels per emotion)", { x: 0.6, y: 2.7, w: 3.8, h: 0.8, fontSize: 11, color: ICE, italic: true, fontFace: FONT_B }); +s.addText([ + { text: "Joy (N / M / E)", options: { bullet: true, fontSize: 11 } }, + { text: "Sadness (N / M / E)", options: { bullet: true, fontSize: 11 } }, + { text: "Anger (N / M / E)", options: { bullet: true, fontSize: 11 } }, + { text: "Fear (N / M / E)", options: { bullet: true, fontSize: 11 } }, + { text: "Surprise (N / M / E)", options: { bullet: true, fontSize: 11 } }, + { text: "Disgust (N / M / E)", options: { bullet: true, fontSize: 11 } }, + { text: "Neutral (N / M / E)", options: { bullet: true, fontSize: 11 } }, +], { x: 0.7, y: 3.7, w: 3.7, h: 2.8, color: ICE, fontFace: FONT_B, paraSpaceAfter: 3 }); + +// LipSync +s.addShape(pres.ShapeType.roundRect, { x: 4.7, y: 2.15, w: 4.0, h: 4.5, fill: { color: NAVY }, rectRadius: 0.1 }); +s.addText("LipSyncPoseMap", { x: 4.8, y: 2.3, w: 3.8, h: 0.4, fontSize: 17, bold: true, color: ACCENT, fontFace: FONT_H }); +s.addText("15 OVR visemes →\nphoneme AnimSequences", { x: 4.8, y: 2.7, w: 3.8, h: 0.7, fontSize: 11, color: ICE, italic: true, fontFace: FONT_B }); +s.addText([ + { text: "sil • PP • FF • TH • DD", options: { bullet: true, fontSize: 11 } }, + { text: "kk • CH • SS • nn • RR", options: { bullet: true, fontSize: 11 } }, + { text: "aa • E • ih • oh • ou", options: { bullet: true, fontSize: 11 } }, + { text: "MetaHuman MHF_* recommended", options: { bullet: true, fontSize: 11 } }, + { text: "Shareable across characters", options: { bullet: true, fontSize: 11 } }, +], { x: 4.9, y: 3.5, w: 3.7, h: 2.9, color: ICE, fontFace: FONT_B, paraSpaceAfter: 4 }); + +// Body +s.addShape(pres.ShapeType.roundRect, { x: 8.9, y: 2.15, w: 4.0, h: 4.5, fill: { color: NAVY }, rectRadius: 0.1 }); +s.addText("BodyPoseMap", { x: 9.0, y: 2.3, w: 3.8, h: 0.4, fontSize: 17, bold: true, color: ACCENT, fontFace: FONT_H }); +s.addText("Emotion → body anim lists\n(Idle / N / M / E per emotion)", { x: 9.0, y: 2.7, w: 3.8, h: 0.8, fontSize: 11, color: ICE, italic: true, fontFace: FONT_B }); +s.addText([ + { text: "Idle: listening animations", options: { bullet: true, fontSize: 11 } }, + { text: "Normal: light speaking", options: { bullet: true, fontSize: 11 } }, + { text: "Medium: expressive speaking", options: { bullet: true, fontSize: 11 } }, + { text: "Extreme: emphatic speaking", options: { bullet: true, fontSize: 11 } }, + { text: "Random selection per loop", options: { bullet: true, fontSize: 11 } }, + { text: "Continuous variety", options: { bullet: true, fontSize: 11 } }, +], { x: 9.0, y: 3.7, w: 3.8, h: 2.8, color: ICE, fontFace: FONT_B, paraSpaceAfter: 3 }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 14 — TOOLS & ACTIONSETS +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Tools & ActionSets", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("LLM-triggered actions that bridge conversation → gameplay.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +s.addText("Tool asset", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "ToolName — snake_case identifier", options: { bullet: true, fontSize: 13 } }, + { text: "ToolDescription — when LLM should use it", options: { bullet: true, fontSize: 13 } }, + { text: "PromptFragment — appended system prompt", options: { bullet: true, fontSize: 13 } }, + { text: "Parameters[] — typed LLM arguments", options: { bullet: true, fontSize: 13 } }, + { text: "ToolID auto-populated on Create", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.55, w: 6, h: 2.8, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Built-in Tools", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "set_emotion(emotion, intensity)", options: { bullet: true, fontSize: 13 } }, + { text: "perform_action(action) — from ActionSet", options: { bullet: true, fontSize: 13 } }, + { text: "Custom tools — any parameters", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 2.55, w: 6, h: 2, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.4, w: 12.3, h: 1.6, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 }); +s.addText("Action flow", { x: 0.7, y: 5.5, w: 12, h: 0.3, fontSize: 14, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText("1. LLM decides to call a tool (e.g. perform_action(\"flee\"))\n2. Plugin fires OnAgentActionRequested(\"flee\")\n3. Game calls ForceDisableConversation(\"flee\") → animations blend to neutral\n4. OnReadyForAction(\"flee\") fires → play gameplay montage\n5. Game calls ForceEnableConversation() when done", { x: 0.7, y: 5.85, w: 12, h: 1.1, fontSize: 11, color: GREY, fontFace: "Consolas" }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 15 — ANIMGRAPH INTEGRATION +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("AnimGraph Integration", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Four AnimNodes auto-discover components — no manual wiring.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +// Face AnimBP chain +s.addText("Face AnimBP", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +const faceChain = [ + { label: "Live Link", color: ICE_DEEP }, + { label: "Facial\nExpression", color: "E88B3E" }, + { label: "Gaze\n(eyes only)", color: "5E7AC7" }, + { label: "Lip Sync", color: "3EB086" }, + { label: "mh_arkit\n_mapping", color: GREY }, +]; +faceChain.forEach((n, i) => { + const x = 0.5 + i * 2.55; + s.addShape(pres.ShapeType.roundRect, { x: x, y: 2.65, w: 2.3, h: 1.0, fill: { color: n.color }, rectRadius: 0.1 }); + s.addText(n.label, { x: x, y: 2.75, w: 2.3, h: 0.8, fontSize: 12, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); +}); + +// Body AnimBP chain +s.addText("Body AnimBP", { x: 0.5, y: 4.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +const bodyChain = [ + { label: "Locomotion\n/ Idle", color: ICE_DEEP }, + { label: "Gaze\n(head rot)", color: "5E7AC7" }, + { label: "Body\nExpression", color: "B85042" }, + { label: "Output Pose", color: GREY }, +]; +bodyChain.forEach((n, i) => { + const x = 0.5 + i * 3.2; + s.addShape(pres.ShapeType.roundRect, { x: x, y: 4.65, w: 2.9, h: 1.0, fill: { color: n.color }, rectRadius: 0.1 }); + s.addText(n.label, { x: x, y: 4.75, w: 2.9, h: 0.8, fontSize: 12, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H }); +}); + +s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 6.15, w: 12.3, h: 0.85, fill: { color: NAVY }, rectRadius: 0.08 }); +s.addText("Why this order?", { x: 0.7, y: 6.2, w: 12, h: 0.3, fontSize: 13, bold: true, color: ACCENT, fontFace: FONT_H }); +s.addText("Emotion curves set mood (brows, eyes, cheeks). Lip sync overrides mouth during speech. Gaze head rotation runs before body expression so it doesn't fight arms.", { x: 0.7, y: 6.55, w: 12, h: 0.4, fontSize: 11, color: ICE, italic: true, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 16 — PROJECT SETTINGS +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Project Settings", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Project Settings → Plugins → PS AI ConvAgent - ElevenLabs", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +const settings = [ + { name: "API_Key", desc: "ElevenLabs API key — required for authentication" }, + { name: "ServerRegion", desc: "Global / US / EU / India" }, + { name: "CustomWebSocketURL", desc: "Override WebSocket URL (advanced)" }, + { name: "bVerboseLogging", desc: "Verbose WebSocket logging (development)" }, + { name: "GlobalContextPrompt", desc: "Shared context injected as dynamic variables for all agents" }, +]; + +settings.forEach((setting, i) => { + const y = 2.3 + i * 0.85; + s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 3.5, h: 0.65, fill: { color: NAVY }, rectRadius: 0.05 }); + s.addText(setting.name, { x: 0.5, y: y, w: 3.5, h: 0.65, fontSize: 13, bold: true, color: ACCENT, align: "center", valign: "middle", fontFace: "Consolas" }); + s.addText(setting.desc, { x: 4.2, y: y + 0.05, w: 8.6, h: 0.55, fontSize: 12, color: GREY, valign: "middle", fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 17 — SETUP WORKFLOW +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Setup Workflow", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("From zero to a talking MetaHuman in 5 steps.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +const steps = [ + { num: "1", title: "Project Settings", desc: "Enter API_Key, pick ServerRegion." }, + { num: "2", title: "Data Assets", desc: "Create AgentConfig, EmotionPoseMap, LipSyncPoseMap, BodyPoseMap." }, + { num: "3", title: "NPC Components", desc: "Add ElevenLabs + Mic + LipSync + FacialExpr + BodyExpr + Gaze on the MetaHuman." }, + { num: "4", title: "AnimGraphs", desc: "Wire AnimNodes in Face + Body AnimBPs. Components auto-discover." }, + { num: "5", title: "Player Pawn", desc: "Add InteractionComponent. Bind selection events to UI. Play." }, +]; +steps.forEach((step, i) => { + const y = 2.25 + i * 0.9; + s.addShape(pres.ShapeType.ellipse, { x: 0.5, y: y, w: 0.8, h: 0.8, fill: { color: ACCENT } }); + s.addText(step.num, { x: 0.5, y: y, w: 0.8, h: 0.8, fontSize: 28, bold: true, color: NAVY, align: "center", valign: "middle", fontFace: FONT_H }); + s.addShape(pres.ShapeType.roundRect, { x: 1.6, y: y, w: 11.2, h: 0.8, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.05 }); + s.addText(step.title, { x: 1.8, y: y + 0.1, w: 3.5, h: 0.3, fontSize: 14, bold: true, color: NAVY, fontFace: FONT_H }); + s.addText(step.desc, { x: 1.8, y: y + 0.4, w: 11, h: 0.35, fontSize: 11, color: GREY, italic: true, fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 18 — NETWORKING +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Networking & Multiplayer", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Listen Server model — server owns WebSocket, clients relay via RPCs.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +s.addText("Audio Pipeline", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "Client mic → Server RPC (Opus-encoded)", options: { bullet: true, fontSize: 13 } }, + { text: "Server forwards to ElevenLabs WebSocket", options: { bullet: true, fontSize: 13 } }, + { text: "Agent audio → Multicast RPC to all clients", options: { bullet: true, fontSize: 13 } }, + { text: "Clients decode Opus → procedural sound wave", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 2.55, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Speaker Arbitration", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "SpeakerSwitchHysteresis — min silence before switching", options: { bullet: true, fontSize: 13 } }, + { text: "SpeakerIdleTimeout — clear active speaker", options: { bullet: true, fontSize: 13 } }, + { text: "NetActiveSpeakerPawn replicated for gaze", options: { bullet: true, fontSize: 13 } }, + { text: "Only active speaker's audio forwarded to LLM", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 2.55, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Level of Detail (LOD)", { x: 0.5, y: 5.1, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "AudioLODCullDistance — skip audio at distance", options: { bullet: true, fontSize: 13 } }, + { text: "LipSyncLODDistance — skip lip sync at distance", options: { bullet: true, fontSize: 13 } }, + { text: "Active speaker always full quality", options: { bullet: true, fontSize: 13 } }, +], { x: 0.5, y: 5.5, w: 6, h: 1.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +s.addText("Replicated State", { x: 7, y: 5.1, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H }); +s.addText([ + { text: "bNetIsConversing", options: { bullet: true, fontSize: 13 } }, + { text: "NetConnectedPawns[]", options: { bullet: true, fontSize: 13 } }, + { text: "CurrentEmotion + CurrentEmotionIntensity", options: { bullet: true, fontSize: 13 } }, +], { x: 7, y: 5.5, w: 6, h: 1.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 }); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 19 — FORCE DISABLE FLOW +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("ForceDisableConversation Flow", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 30, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Clean handoff between conversation and gameplay actions.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +const flow = [ + { step: "LLM Tool Call", desc: "perform_action(\"flee\")" }, + { step: "OnAgentActionRequested", desc: "Event fires on ElevenLabs component" }, + { step: "ForceDisableConversation", desc: "Blend out face/body/gaze animations" }, + { step: "OnReadyForAction", desc: "All components have reached neutral" }, + { step: "Play Gameplay", desc: "Montage / behavior switch" }, + { step: "ForceEnableConversation", desc: "Resume conversation when done" }, +]; +flow.forEach((f, i) => { + const y = 2.2 + i * 0.75; + s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 4, h: 0.6, fill: { color: NAVY }, rectRadius: 0.08 }); + s.addText(f.step, { x: 0.5, y: y, w: 4, h: 0.6, fontSize: 13, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: "Consolas" }); + s.addText("→", { x: 4.6, y: y + 0.05, w: 0.5, h: 0.5, fontSize: 22, color: ACCENT, align: "center", valign: "middle", bold: true, fontFace: FONT_H }); + s.addText(f.desc, { x: 5.2, y: y + 0.05, w: 7.6, h: 0.5, fontSize: 13, color: GREY, italic: true, valign: "middle", fontFace: FONT_B }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 20 — EVENTS CHEAT SHEET +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide({ masterName: "CONTENT" }); +s.addText("Events Cheat Sheet", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H }); +s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } }); +s.addText("Blueprint-assignable delegates on ElevenLabs component.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B }); + +const eventCols = [ + { + title: "Connection", + items: ["OnAgentConnected", "OnAgentDisconnected", "OnAgentError"] + }, + { + title: "Dialogue", + items: ["OnAgentTranscript", "OnAgentTextResponse", "OnAgentPartialResponse", "OnAgentStartedGenerating"] + }, + { + title: "Audio", + items: ["OnAgentStartedSpeaking", "OnAudioPlaybackStarted", "OnAgentStoppedSpeaking", "OnAgentInterrupted"] + }, + { + title: "State / Actions", + items: ["OnAgentEmotionChanged", "OnAgentActionRequested", "OnAgentClientToolCall", "OnActiveSpeakerChanged", "OnReadyForAction", "OnAgentResponseTimeout"] + }, +]; +eventCols.forEach((c, i) => { + const col = i % 2; + const row = Math.floor(i / 2); + const x = 0.5 + col * 6.3; + const y = 2.15 + row * 2.45; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 6.0, h: 2.25, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 }); + s.addText(c.title, { x: x + 0.2, y: y + 0.1, w: 5.6, h: 0.3, fontSize: 14, bold: true, color: NAVY, fontFace: FONT_H }); + s.addShape(pres.ShapeType.rect, { x: x + 0.2, y: y + 0.42, w: 0.35, h: 0.04, fill: { color: ACCENT } }); + s.addText(c.items.map(t => ({ text: t, options: { bullet: true, fontSize: 11 } })), + { x: x + 0.2, y: y + 0.52, w: 5.6, h: 1.7, color: GREY, fontFace: "Consolas", paraSpaceAfter: 2 }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// SLIDE 21 — CLOSING / SUMMARY +// ═══════════════════════════════════════════════════════════════════ +s = pres.addSlide(); +s.background = { color: NAVY }; +s.addShape(pres.ShapeType.rect, { x: 0, y: 2.5, w: 13.333, h: 0.05, fill: { color: ACCENT } }); +s.addText("Summary", { x: 0.6, y: 0.8, w: 12, h: 1.2, fontSize: 54, bold: true, color: WHITE, fontFace: FONT_H }); +s.addText("A complete conversational AI stack for Unreal Engine.", { x: 0.6, y: 2.0, w: 12, h: 0.5, fontSize: 18, color: ICE, italic: true, fontFace: FONT_B }); + +const summary = [ + { h: "Real-time", t: "Spectral lip sync, speculative TTS, adaptive pre-buffering" }, + { h: "Expressive", t: "Emotion-driven face/body animation via AnimSequences" }, + { h: "Multiplayer", t: "Listen Server + Opus codec + distance LOD + speaker arbitration" }, + { h: "Data-driven", t: "Reusable PoseMaps, AgentConfigs, Tools, ActionSets" }, + { h: "Bridgeable", t: "Behavior plugin gaze bridge, ForceDisable handoff for gameplay" }, + { h: "BP-friendly", t: "All components blueprintable, rich event system" }, +]; +summary.forEach((item, i) => { + const col = i % 2; + const row = Math.floor(i / 2); + const x = 0.6 + col * 6.2; + const y = 3.0 + row * 1.4; + s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 5.9, h: 1.2, fill: { color: NAVY_DARK }, line: { color: ACCENT, width: 1 }, rectRadius: 0.1 }); + s.addText(item.h, { x: x + 0.2, y: y + 0.1, w: 5.5, h: 0.4, fontSize: 18, bold: true, color: ACCENT, fontFace: FONT_H }); + s.addText(item.t, { x: x + 0.2, y: y + 0.55, w: 5.5, h: 0.6, fontSize: 12, color: ICE, italic: true, fontFace: FONT_B }); +}); + +s.addText("ASTERION • PS_AI_ConvAgent • 2026", { x: 0.6, y: 6.9, w: 12, h: 0.4, fontSize: 12, color: ICE_DEEP, fontFace: FONT_B }); + +// ═══════════════════════════════════════════════════════════════════ +// SAVE +// ═══════════════════════════════════════════════════════════════════ +pres.writeFile({ fileName: "PS_AI_ConvAgent_Documentation.pptx" }) + .then(name => console.log(`Generated: ${name}`)) + .catch(err => { console.error(err); process.exit(1); }); diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_AIController.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_AIController.cpp index 8e8699d..aa309c1 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_AIController.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_AIController.cpp @@ -81,7 +81,12 @@ void APS_AI_Behavior_AIController::OnPossess(APawn* InPawn) SetupBlackboard(); - if (bAutoStartBehavior) + // Read auto-start from PersonalityComponent (defaults to true if no component) + const bool bAutoStart = PersonalityComp + ? PersonalityComp->bAutoStartBehavior + : true; + + if (bAutoStart) { StartBehavior(); } @@ -89,7 +94,7 @@ void APS_AI_Behavior_AIController::OnPossess(APawn* InPawn) { // Stay in Scripted state — no BT, no services, controlled externally SetBehaviorState(EPS_AI_Behavior_State::Scripted); - UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] bAutoStartBehavior=false — entering Scripted state."), + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] bAutoStartBehavior=false (from Profile) — entering Scripted state."), *GetName()); } @@ -97,7 +102,7 @@ void APS_AI_Behavior_AIController::OnPossess(APawn* InPawn) TryBindGazeComponent(); UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Possessed Pawn '%s' — TeamId=%d, AutoStart=%d."), - *GetName(), *InPawn->GetName(), TeamId, (int32)bAutoStartBehavior); + *GetName(), *InPawn->GetName(), TeamId, (int32)bAutoStart); } void APS_AI_Behavior_AIController::OnUnPossess() diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_AIController.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_AIController.h index 9081275..450bb80 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_AIController.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_AIController.h @@ -48,7 +48,7 @@ public: uint8 TeamId = FGenericTeamId::NoTeam; /** Set the team ID at runtime. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Team") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Team") void SetTeamId(uint8 NewTeamId); // ─── IGenericTeamAgentInterface (inherited from AAIController) ──── @@ -59,10 +59,6 @@ public: // ─── Configuration ────────────────────────────────────────────────── - /** If false, the Behavior Tree does NOT start on possess. Call StartBehavior() manually. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Behavior") - bool bAutoStartBehavior = true; - /** Behavior Tree to run. If null, uses the Profile's DefaultBehaviorTree. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Behavior") TObjectPtr BehaviorTreeAsset; @@ -78,45 +74,45 @@ public: // ─── Gaze Configuration ──────────────────────────────────────────── /** Detection radius for proximity gaze (cm). NPC glances at nearby actors within this range. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI Behavior|Gaze", meta = (ClampMin = "100.0", ClampMax = "1500.0")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_Behavior|Gaze", meta = (ClampMin = "100.0", ClampMax = "1500.0")) float GazeProximityRadius = 400.0f; /** Maximum duration (seconds) the NPC looks at a proximity target before releasing. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI Behavior|Gaze", meta = (ClampMin = "0.5", ClampMax = "30.0")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_Behavior|Gaze", meta = (ClampMin = "0.5", ClampMax = "30.0")) float GazeMaxDuration = 3.0f; /** Cooldown (seconds) before re-targeting the same actor after releasing. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI Behavior|Gaze", meta = (ClampMin = "0.0", ClampMax = "30.0")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_Behavior|Gaze", meta = (ClampMin = "0.0", ClampMax = "30.0")) float GazeCooldown = 5.0f; /** Enable/disable proximity gaze (NPC glances at nearby actors in Idle/Patrol). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI Behavior|Gaze") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_Behavior|Gaze") bool bEnableProximityGaze = true; // ─── Component Access ─────────────────────────────────────────────── - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") UPS_AI_Behavior_PerceptionComponent* GetBehaviorPerception() const { return BehaviorPerception; } - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") UPS_AI_Behavior_PersonalityComponent* GetPersonalityComponent() const { return PersonalityComp; } // ─── Blackboard Helpers ───────────────────────────────────────────── /** Write the current behavior state to the Blackboard. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Blackboard") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Blackboard") void SetBehaviorState(EPS_AI_Behavior_State NewState); /** Read the current behavior state from the Blackboard. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Blackboard") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Blackboard") EPS_AI_Behavior_State GetBehaviorState() const; /** Start (or restart) the Behavior Tree. If state is Scripted, transitions to Idle. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") void StartBehavior(); /** Stop the Behavior Tree and enter Scripted state. NPC stays alive and perceptible. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") void StopBehavior(); protected: diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_CombatComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_CombatComponent.h index 05bb16a..9b10dbf 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_CombatComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_CombatComponent.h @@ -17,7 +17,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnDamageReceived, float, Damage, A * * Attach to the NPC Pawn alongside PersonalityComponent. */ -UCLASS(ClassGroup = "PS AI Behavior", meta = (BlueprintSpawnableComponent, DisplayName = "PS AI Behavior - Combat")) +UCLASS(ClassGroup = "ASTERION|PS_AI_Behavior", meta = (BlueprintSpawnableComponent, DisplayName = "PS AI Behavior - Combat")) class PS_AI_BEHAVIOR_API UPS_AI_Behavior_CombatComponent : public UActorComponent { GENERATED_BODY() @@ -52,21 +52,21 @@ public: // ─── Delegates ────────────────────────────────────────────────────── /** Fired on ALL machines (server + clients) when an attack is executed. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI Behavior|Combat") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_Behavior|Combat") FOnAttackExecuted OnAttackExecuted; /** Fired on server when damage is received. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI Behavior|Combat") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_Behavior|Combat") FOnDamageReceived OnDamageReceived; // ─── API ──────────────────────────────────────────────────────────── /** Whether the NPC can currently attack (cooldown elapsed). Server-only. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Combat") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Combat") bool CanAttack() const; /** Whether the target is within attack range. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Combat") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Combat") bool IsInAttackRange(AActor* Target) const; /** @@ -74,14 +74,14 @@ public: * cooldown, and multicasts cosmetic event to all clients. * @return True if the attack was executed. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Combat") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Combat") bool ExecuteAttack(AActor* Target); /** * Called when this NPC takes damage. Updates threat perception. * Hook this into the owning Actor's OnTakeAnyDamage. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Combat") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Combat") void NotifyDamageReceived(float Damage, AActor* DamageInstigator); // ─── Replication ──────────────────────────────────────────────────── diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_CoverPoint.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_CoverPoint.h index d7d2056..b297ef3 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_CoverPoint.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_CoverPoint.h @@ -88,23 +88,23 @@ public: // ─── API ──────────────────────────────────────────────────────────── /** Can this point be used by the given NPC type? */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Cover") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Cover") bool IsAccessibleTo(EPS_AI_Behavior_NPCType NPCType) const; /** Is there room for one more occupant? */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Cover") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Cover") bool HasRoom() const; /** Try to claim this point for an NPC. Returns true if successful. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Cover") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Cover") bool Claim(AActor* Occupant); /** Release this point (NPC leaves cover). */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Cover") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Cover") void Release(AActor* Occupant); /** Get the facing direction (forward vector of the actor). */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Cover") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Cover") FVector GetCoverDirection() const; /** @@ -113,11 +113,11 @@ public: * @param ThreatLocation Where the threat is. * @return Combined score 0.0 to 1.0. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Cover") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Cover") float EvaluateAgainstThreat(const FVector& ThreatLocation) const; /** Enable/disable at runtime (e.g. barricade destroyed). */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Cover") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Cover") void SetEnabled(bool bNewEnabled); #if WITH_EDITOR diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Interface.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Interface.h index 6aee683..d3921e9 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Interface.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Interface.h @@ -44,11 +44,11 @@ public: // ─── NPC Type ─────────────────────────────────────────────────────── /** Get this NPC's type (Civilian, Enemy, Protector). */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") EPS_AI_Behavior_NPCType GetBehaviorNPCType() const; /** Set this NPC's type. Called by gameplay logic or plugin actions. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") void SetBehaviorNPCType(EPS_AI_Behavior_NPCType NewType); // ─── Hostility ────────────────────────────────────────────────────── @@ -58,14 +58,14 @@ public: * An infiltrated Enemy with IsHostile=false appears as Civilian to the perception system. * When SetHostile(true) is called, the NPC reveals itself and TeamId changes. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") bool IsBehaviorHostile() const; /** * Set hostility state. Typically called by gameplay scripts or ConvAgent actions. * Implementors should update their TeamId accordingly. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") void SetBehaviorHostile(bool bNewHostile); // ─── Movement ─────────────────────────────────────────────────────── @@ -80,13 +80,13 @@ public: * * @param NewSpeed Desired walk speed in cm/s. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") void SetBehaviorMovementSpeed(float NewSpeed); /** * Get the Pawn's current movement speed (cm/s). */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") float GetBehaviorMovementSpeed() const; /** @@ -98,7 +98,7 @@ public: * @param NewState The new behavioral state. * @param OldState The previous behavioral state. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") void OnBehaviorStateChanged(EPS_AI_Behavior_State NewState, EPS_AI_Behavior_State OldState); // ─── Target Validation ────────────────────────────────────────────── @@ -113,7 +113,7 @@ public: * @param TargetActor The actor to validate. * @return True if the actor can still be targeted. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") bool IsTargetActorValid(AActor* TargetActor) const; // ─── Combat ───────────────────────────────────────────────────────── @@ -125,21 +125,21 @@ public: * * @param Target The actor to attack. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") void BehaviorStartAttack(AActor* Target); /** * Order the Pawn to stop attacking. * Called when the BT exits the Attack task (state changed, target lost, etc.). */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") void BehaviorStopAttack(); /** * Query whether the Pawn can currently attack the target (has ammo, weapon ready, etc.). * If false, the BT will keep the NPC in combat stance but won't call BehaviorStartAttack. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") bool CanBehaviorAttack(AActor* Target) const; // ─── Stance ───────────────────────────────────────────────────────── @@ -151,7 +151,7 @@ public: * * @param bCrouch True = crouch, False = stand up. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") void SetBehaviorCrouch(bool bCrouch); // ─── Combat Style ─────────────────────────────────────────────────── @@ -162,7 +162,7 @@ public: * Melee NPCs rush the target; Ranged NPCs use cover and maintain distance. * Default: Melee. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") EPS_AI_Behavior_CombatType GetBehaviorCombatType() const; // ─── Actor Resolution ────────────────────────────────────────────── @@ -177,6 +177,6 @@ public: * * @return The actor to use as threat source / target. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "ASTERION|PS_AI_Behavior") AActor* GetBehaviorThreatActor() const; }; diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PerceptionComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PerceptionComponent.h index 991b2dc..a37f159 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PerceptionComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PerceptionComponent.h @@ -14,7 +14,7 @@ * * Automatically added by PS_AI_Behavior_AIController — you don't need to add it manually. */ -UCLASS(ClassGroup = "PS AI Behavior", meta = (DisplayName = "PS AI Behavior - Perception")) +UCLASS(ClassGroup = "ASTERION|PS_AI_Behavior", meta = (DisplayName = "PS AI Behavior - Perception")) class PS_AI_BEHAVIOR_API UPS_AI_Behavior_PerceptionComponent : public UAIPerceptionComponent { GENERATED_BODY() @@ -32,7 +32,7 @@ public: * If empty, uses default [Protector, Player, Civilian]. * @return The most threatening actor, or nullptr if none perceived. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Perception") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Perception") AActor* GetHighestThreatActor(const TArray& TargetPriority); /** Convenience overload — reads priority from the Pawn's PersonalityProfile. */ @@ -42,7 +42,7 @@ public: * Compute an aggregate threat level from all currently perceived hostile stimuli. * Returns 0.0 (no threat) to 1.0+ (extreme danger). */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Perception") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Perception") float CalculateThreatLevel(); /** @@ -50,7 +50,7 @@ public: * @param OutLocation Filled with the threat location if any threat exists. * @return True if a threat was found. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Perception") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Perception") bool GetThreatLocation(FVector& OutLocation); /** @@ -82,7 +82,7 @@ protected: * Uses IsPlayerControlled() for Player, IPS_AI_Behavior interface or * PersonalityComponent for NPC type. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Perception") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Perception") static EPS_AI_Behavior_TargetType ClassifyActor(const AActor* Actor); private: diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityComponent.h index eb4fa6f..a8501f4 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityComponent.h @@ -22,7 +22,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnBehaviorStateChanged, EPS_AI_Beh * * Attach to the NPC Pawn/Character. */ -UCLASS(ClassGroup = "PS AI Behavior", meta = (BlueprintSpawnableComponent, DisplayName = "PS AI Behavior - Personality")) +UCLASS(ClassGroup = "ASTERION|PS_AI_Behavior", meta = (BlueprintSpawnableComponent, DisplayName = "PS AI Behavior - Personality")) class PS_AI_BEHAVIOR_API UPS_AI_Behavior_PersonalityComponent : public UActorComponent { GENERATED_BODY() @@ -36,6 +36,14 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Personality") TObjectPtr Profile; + /** + * If false, the Behavior Tree does NOT start on possess. + * Use this for NPCs controlled externally (conversations, cinematics). + * Call StartBehavior() on the AIController to activate manually. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Personality") + bool bAutoStartBehavior = true; + /** * Master debug toggle for this NPC. * When enabled, draws floating text above the NPC's head with: @@ -73,7 +81,7 @@ public: // ─── Delegates ────────────────────────────────────────────────────── /** Fired when the behavioral state changes (on server AND clients via OnRep). */ - UPROPERTY(BlueprintAssignable, Category = "PS AI Behavior|Personality") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_Behavior|Personality") FOnBehaviorStateChanged OnBehaviorStateChanged; // ─── API ──────────────────────────────────────────────────────────── @@ -82,7 +90,7 @@ public: * Evaluate the NPC's reaction based on current traits and perceived threat. * Returns the recommended behavioral state. Server-only. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Personality") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Personality") EPS_AI_Behavior_State EvaluateReaction() const; /** @@ -90,23 +98,23 @@ public: * Server-only: state is replicated to clients via OnRep. * @return The new behavioral state. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Personality") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Personality") EPS_AI_Behavior_State ApplyReaction(); /** Get a runtime trait value (returns 0.5 if undefined). */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Personality") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Personality") float GetTrait(EPS_AI_Behavior_TraitAxis Axis) const; /** Modify a runtime trait by delta, clamped to [0, 1]. Server-only. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Personality") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Personality") void ModifyTrait(EPS_AI_Behavior_TraitAxis Axis, float Delta); /** Force a specific state (e.g. from conversation agent action). Server-only. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Personality") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Personality") void ForceState(EPS_AI_Behavior_State NewState); /** Get the NPC type from the interface or profile. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Personality") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Personality") EPS_AI_Behavior_NPCType GetNPCType() const; /** @@ -114,7 +122,7 @@ public: * Driven by the Combat/Cover cycle timer based on Aggressivity vs Caution. * Used by the IsCoverNeeded BT decorator. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Personality") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Personality") bool ShouldPreferCover() const { return bPreferCover; } // ─── Replication ──────────────────────────────────────────────────── diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityProfile.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityProfile.h index d23a18b..8e23634 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityProfile.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityProfile.h @@ -160,11 +160,11 @@ public: /** * Get the score for a given trait axis. Returns 0.5 if the axis is not defined. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Personality") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Personality") float GetTrait(EPS_AI_Behavior_TraitAxis Axis) const; /** Get the speed for a given state. Returns DefaultWalkSpeed if state not in SpeedPerState. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Personality") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Personality") float GetSpeedForState(EPS_AI_Behavior_State State) const; /** UPrimaryDataAsset interface */ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Settings.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Settings.h index 4d42a7b..918a4ab 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Settings.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Settings.h @@ -10,7 +10,7 @@ * Project-wide settings for the PS AI Behavior plugin. * Accessible via Project Settings -> Plugins -> PS AI Behavior. */ -UCLASS(config = Game, defaultconfig, meta = (DisplayName = "PS AI Behavior")) +UCLASS(config = Game, defaultconfig, meta = (DisplayName = "ASTERION|PS_AI_Behavior")) class PS_AI_BEHAVIOR_API UPS_AI_Behavior_Settings : public UDeveloperSettings { GENERATED_BODY() diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineFollowerComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineFollowerComponent.h index 60754da..4468eb2 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineFollowerComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineFollowerComponent.h @@ -32,7 +32,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSplineEndReached, * * Attach to the NPC Pawn. Works with or without the AI Controller. */ -UCLASS(ClassGroup = "PS AI Behavior", meta = (BlueprintSpawnableComponent, DisplayName = "PS AI Behavior - Spline Follower")) +UCLASS(ClassGroup = "ASTERION|PS_AI_Behavior", meta = (BlueprintSpawnableComponent, DisplayName = "PS AI Behavior - Spline Follower")) class PS_AI_BEHAVIOR_API UPS_AI_Behavior_SplineFollowerComponent : public UActorComponent { GENERATED_BODY() @@ -109,15 +109,15 @@ public: // ─── Delegates ────────────────────────────────────────────────────── /** Fired when approaching a junction. Use to make custom spline selection. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI Behavior|Spline Follower") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") FOnApproachingJunction OnApproachingJunction; /** Fired when the NPC switches to a different spline. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI Behavior|Spline Follower") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") FOnSplineChanged OnSplineChanged; /** Fired when the NPC reaches the end of a spline (if bReverseAtEnd is false). */ - UPROPERTY(BlueprintAssignable, Category = "PS AI Behavior|Spline Follower") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") FOnSplineEndReached OnSplineEndReached; // ─── API ──────────────────────────────────────────────────────────── @@ -128,24 +128,24 @@ public: * @param bForward Direction of travel. * @return True if successfully started. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline Follower") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") bool StartFollowing(APS_AI_Behavior_SplinePath* Spline, bool bForward = true); /** * Start following the given spline from a specific distance. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline Follower") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") bool StartFollowingAtDistance(APS_AI_Behavior_SplinePath* Spline, float StartDistance, bool bForward = true); /** Stop following the current spline. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline Follower") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") void StopFollowing(); /** Pause/resume without losing state. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline Follower") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") void PauseFollowing(); - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline Follower") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") void ResumeFollowing(); /** @@ -154,15 +154,15 @@ public: * @param DistanceOnNew Distance along the new spline to start from. * @param bNewForward Direction on the new spline. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline Follower") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") void SwitchToSpline(APS_AI_Behavior_SplinePath* NewSpline, float DistanceOnNew, bool bNewForward = true); /** Get the effective walk speed (considering spline override). */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline Follower") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") float GetEffectiveSpeed() const; /** Get progress as a 0-1 ratio. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline Follower") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline Follower") float GetProgress() const; protected: diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineNetwork.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineNetwork.h index 9930383..6f42a11 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineNetwork.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineNetwork.h @@ -35,7 +35,7 @@ public: * Called automatically after world initialization. Can be called again * if splines are added/removed at runtime. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|SplineNetwork") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|SplineNetwork") void RebuildNetwork(); // ─── Queries ──────────────────────────────────────────────────────── @@ -49,7 +49,7 @@ public: * @param OutDistance Distance along the spline to the closest point. * @return True if a spline was found within MaxDistance. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|SplineNetwork") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|SplineNetwork") bool FindClosestSpline(const FVector& WorldLocation, EPS_AI_Behavior_NPCType NPCType, float MaxDistance, APS_AI_Behavior_SplinePath*& OutSpline, float& OutDistance) const; @@ -57,7 +57,7 @@ public: /** * Get all splines of a given category. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|SplineNetwork") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|SplineNetwork") TArray GetSplinesForCategory( EPS_AI_Behavior_NPCType Category) const; @@ -73,7 +73,7 @@ public: * @param CautionScore Optional: NPC's caution trait (0-1). Higher = prefer quieter paths. * @return The chosen spline (could be the same if staying is best). */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|SplineNetwork") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|SplineNetwork") APS_AI_Behavior_SplinePath* ChooseSplineAtJunction( APS_AI_Behavior_SplinePath* CurrentSpline, int32 JunctionIndex, EPS_AI_Behavior_NPCType NPCType, @@ -81,11 +81,11 @@ public: float CautionScore = 0.5f) const; /** Total number of splines in the network. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|SplineNetwork") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|SplineNetwork") int32 GetSplineCount() const { return AllSplines.Num(); } /** Total number of junctions detected. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|SplineNetwork") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|SplineNetwork") int32 GetJunctionCount() const { return TotalJunctions; } private: diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplinePath.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplinePath.h index 23f55b4..ddba1df 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplinePath.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplinePath.h @@ -94,7 +94,7 @@ public: // ─── API ──────────────────────────────────────────────────────────── /** Can the given NPC type use this spline? */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline") bool IsAccessibleTo(EPS_AI_Behavior_NPCType NPCType) const; /** @@ -104,7 +104,7 @@ public: * @param OutWorldPoint World location of the closest point on the spline. * @return Distance from WorldLocation to the closest spline point. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline") float GetClosestPointOnSpline(const FVector& WorldLocation, float& OutDistance, FVector& OutWorldPoint) const; @@ -115,24 +115,24 @@ public: * @param bForward Travel direction (true = increasing distance). * @return Array of upcoming junctions. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline") TArray GetUpcomingJunctions( float CurrentDistance, float LookAheadDist, bool bForward) const; /** Total spline length. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline") float GetSplineLength() const; /** Get world location at a distance along the spline. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline") FVector GetWorldLocationAtDistance(float Distance) const; /** Get world rotation at a distance along the spline. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline") FRotator GetWorldRotationAtDistance(float Distance) const; /** Get world-space tangent direction at a distance along the spline. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Spline") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Spline") FVector GetWorldDirectionAtDistance(float Distance) const; #if WITH_EDITOR diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Statics.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Statics.h index f65686b..db6007a 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Statics.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Statics.h @@ -35,7 +35,7 @@ public: * @param Loudness Noise loudness (default 1.0). Scales with HearingRange. * @param MaxRange Override max hearing range (0 = use sense default). */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Perception", + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Perception", meta = (WorldContext = "WorldContext", DefaultToSelf = "Shooter")) static void ReportGunfire(UObject* WorldContext, FVector Location, AActor* Shooter, bool bIsEnemyFire = false, diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_TeamComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_TeamComponent.h index 253e3d0..8848ace 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_TeamComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_TeamComponent.h @@ -21,7 +21,7 @@ * Enemy (Team 2) — Protectors and Civilians react * Protector (Team 3) — Allied with Civilians, hostile to Enemies */ -UCLASS(ClassGroup = "PS AI Behavior", meta = (BlueprintSpawnableComponent, +UCLASS(ClassGroup = "ASTERION|PS_AI_Behavior", meta = (BlueprintSpawnableComponent, DisplayName = "PS AI Behavior Pawn Identity")) class PS_AI_BEHAVIOR_API UPS_AI_Behavior_TeamComponent : public UActorComponent { @@ -31,16 +31,16 @@ public: UPS_AI_Behavior_TeamComponent(); /** Role of this actor in the behavior system. Determines team affiliation. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI Behavior|Team") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_Behavior|Team") EPS_AI_Behavior_NPCType Role = EPS_AI_Behavior_NPCType::Civilian; /** Faction within the same role. Same role + different faction = rivals. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI Behavior|Team", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_Behavior|Team", meta = (ClampMin = "0", ClampMax = "15")) uint8 Faction = 0; /** Change role at runtime. */ - UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Team") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_Behavior|Team") void SetRole(EPS_AI_Behavior_NPCType NewRole) { Role = NewRole; } /** Returns the TeamId derived from the current Role. */ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent.h index 9b4b75d..877e6c1 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent.h @@ -36,7 +36,7 @@ public: * Obtain from https://elevenlabs.io – used to authenticate WebSocket connections. * Keep this secret; do not ship with the key hard-coded in a shipping build. */ - UPROPERTY(Config, EditAnywhere, Category = "PS AI ConvAgent|ElevenLabs API") + UPROPERTY(Config, EditAnywhere, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API") FString API_Key; /** @@ -45,18 +45,18 @@ public: * - US: force US servers (api.us.elevenlabs.io). * - EU / India: Enterprise-only data residency endpoints. */ - UPROPERTY(Config, EditAnywhere, Category = "PS AI ConvAgent|ElevenLabs API") + UPROPERTY(Config, EditAnywhere, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API") EPS_AI_ConvAgent_ElevenLabsRegion ServerRegion = EPS_AI_ConvAgent_ElevenLabsRegion::Global; /** * Override the ElevenLabs WebSocket URL entirely. Leave empty to use ServerRegion setting. * Example: wss://custom-proxy.example.com/v1/convai/conversation?agent_id=YOUR_ID */ - UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "PS AI ConvAgent|ElevenLabs API") + UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API") FString CustomWebSocketURL; /** Log verbose WebSocket messages to the Output Log (useful during development). */ - UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "PS AI ConvAgent|ElevenLabs API") + UPROPERTY(Config, EditAnywhere, AdvancedDisplay, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API") bool bVerboseLogging = false; /** @@ -65,7 +65,7 @@ public: * Injected at conversation start via dynamic variables — changes take effect * on the next conversation without re-syncing agents. */ - UPROPERTY(Config, EditAnywhere, Category = "PS AI ConvAgent|Global Prompt", + UPROPERTY(Config, EditAnywhere, Category = "ASTERION|PS_AI_ConvAgent|Global Prompt", meta = (MultiLine = "true", ToolTip = "Context prompt shared by all agents.\nDescribe the world, era, common rules.\nChanges take effect on next conversation start (no re-sync needed).")) FString GlobalContextPrompt; diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BlueprintLibrary.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BlueprintLibrary.h index fbe67e7..0982128 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BlueprintLibrary.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BlueprintLibrary.h @@ -24,7 +24,7 @@ public: * This allows per-instance override without modifying the Skeletal Mesh asset. * @param SkelMeshComp The target Skeletal Mesh Component. * @param AnimBPClass The AnimBlueprint class to use as post-process (nullptr to clear). */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|Utilities", + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|Utilities", meta = (DisplayName = "Set Post Process Anim Blueprint")) static void SetPostProcessAnimBlueprint(USkeletalMeshComponent* SkelMeshComp, TSubclassOf AnimBPClass); diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BodyExpressionComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BodyExpressionComponent.h index e4b9312..91bb22b 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BodyExpressionComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BodyExpressionComponent.h @@ -55,7 +55,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnBodyExpressionChanged, // 4. The component listens to conversation lifecycle + emotion events. // 5. The AnimNode reads GetSnapshot() and blends the upper body pose. // ───────────────────────────────────────────────────────────────────────────── -UCLASS(ClassGroup = "PS AI ConvAgent", meta = (BlueprintSpawnableComponent), +UCLASS(ClassGroup = "ASTERION|PS_AI_ConvAgent", meta = (BlueprintSpawnableComponent), DisplayName = "PS AI ConvAgent Body Expression") class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_BodyExpressionComponent : public UActorComponent { @@ -68,28 +68,28 @@ public: /** Body pose map asset containing body animation lists per emotion. * Create a dedicated PS_AI_ConvAgent_BodyPoseMap asset in the Content Browser. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|BodyExpression", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|BodyExpression", meta = (ToolTip = "Body Pose Map asset with animation lists per emotion.\nRight-click Content Browser > Miscellaneous > PS AI ConvAgent Body Pose Map.")) TObjectPtr BodyPoseMap; /** Whether body expressions are currently active (read-only). * Managed automatically: activates on conversation connect, deactivates on disconnect. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|BodyExpression") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|BodyExpression") bool bActive = false; /** How long (seconds) to blend in/out when conversation starts/ends. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|BodyExpression", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|BodyExpression", meta = (ClampMin = "0.05", ClampMax = "3.0")) float ActivationBlendDuration = 0.5f; /** Crossfade duration (seconds) when switching between animations. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|BodyExpression", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|BodyExpression", meta = (ClampMin = "0.1", ClampMax = "3.0", ToolTip = "How long (seconds) to crossfade between animations.\n0.5 = snappy, 1.5 = smooth.")) float EmotionBlendDuration = 1.0f; /** Overall blend weight for body expressions. 1.0 = full, 0.5 = subtle. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|BodyExpression", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|BodyExpression", meta = (ClampMin = "0.0", ClampMax = "1.0", ToolTip = "Overall blend weight for body expressions.\n1.0 = full, 0.5 = subtle.")) float BlendWeight = 1.0f; @@ -97,12 +97,12 @@ public: // ── Debug ──────────────────────────────────────────────────────────────── /** Enable debug logging for this component. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug") bool bDebug = false; /** Verbosity level when bDebug is true. * 0 = minimal extras, 1 = normal debug, 2 = detailed, 3 = per-frame data. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug", meta = (ClampMin = "0", ClampMax = "3", EditCondition = "bDebug")) int32 DebugVerbosity = 1; @@ -117,22 +117,22 @@ public: } /** Get the active emotion. */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|BodyExpression") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|BodyExpression") EPS_AI_ConvAgent_Emotion GetActiveEmotion() const { return ActiveEmotion; } /** Get the active emotion intensity. */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|BodyExpression") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|BodyExpression") EPS_AI_ConvAgent_EmotionIntensity GetActiveIntensity() const { return ActiveEmotionIntensity; } /** True when the agent is currently speaking (body uses N/M/E lists). */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|BodyExpression") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|BodyExpression") bool IsSpeaking() const { return bIsSpeaking; } // ── Events ─────────────────────────────────────────────────────────────── /** Fired when the body expression changes (emotion + intensity). * Only fires when the emotion actually differs from the previous one. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|BodyExpression|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|BodyExpression|Events") FOnBodyExpressionChanged OnBodyExpressionChanged; /** Current activation blend alpha (0 = fully inactive, 1 = fully active). diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_Definitions.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_Definitions.h index 2271f49..d69ebe8 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_Definitions.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_Definitions.h @@ -91,11 +91,11 @@ struct PS_AI_CONVAGENT_API FPS_AI_ConvAgent_ConversationInfo_ElevenLabs GENERATED_BODY() /** Unique ID of this conversation session assigned by ElevenLabs. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") FString ConversationID; /** Agent ID that is responding. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") FString AgentID; }; @@ -108,15 +108,15 @@ struct PS_AI_CONVAGENT_API FPS_AI_ConvAgent_TranscriptSegment_ElevenLabs GENERATED_BODY() /** Transcribed text. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") FString Text; /** "user" or "agent". */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") FString Speaker; /** Whether this is a final transcript or a tentative (in-progress) one. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") bool bIsFinal = false; }; @@ -155,15 +155,15 @@ struct PS_AI_CONVAGENT_API FPS_AI_ConvAgent_ClientToolCall_ElevenLabs GENERATED_BODY() /** Name of the tool the agent wants to invoke (e.g. "set_emotion"). */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") FString ToolName; /** Unique ID for this tool invocation — must be echoed back in client_tool_result. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") FString ToolCallId; /** Raw JSON parameters as key-value string pairs. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") TMap Parameters; }; @@ -177,11 +177,11 @@ struct PS_AI_CONVAGENT_API FPS_AI_ConvAgent_AgentAction_ElevenLabs GENERATED_BODY() /** Action identifier sent by the LLM (e.g. "flee", "draw_weapon"). Keep short, snake_case. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") FString Name; /** Human-readable description — helps the LLM understand when to use this action. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") FString Description; }; @@ -210,23 +210,23 @@ struct PS_AI_CONVAGENT_API FPS_AI_ConvAgent_ToolParameter_ElevenLabs /** Parameter identifier (snake_case). Must match the name expected by the client code. * Examples: "emotion", "intensity", "action", "target_name" */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "Parameter name (snake_case).\nThe key sent in the tool call JSON.")) FString Name; /** Data type for this parameter. Maps to JSON Schema "type". */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "Data type: String, Integer, Number, or Boolean.")) EPS_AI_ConvAgent_ToolParamType Type = EPS_AI_ConvAgent_ToolParamType::String; /** Whether this parameter is required in the tool call. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "If true, the LLM must provide this parameter.")) bool bRequired = true; /** Description passed to the LLM explaining what this parameter is * and how to extract or determine its value from the conversation. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (MultiLine = "true", ToolTip = "LLM prompt describing this parameter.\nGuides the LLM on what value to provide.")) FString Description; @@ -234,7 +234,7 @@ struct PS_AI_CONVAGENT_API FPS_AI_ConvAgent_ToolParameter_ElevenLabs /** Optional predefined values the LLM can choose from. * If empty, the LLM can use any value of the specified type. * Examples: ["joy","sadness","anger"] for emotion, ["low","medium","high"] for intensity. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "Predefined enum values (optional).\nIf set, the LLM must choose from these.")) TArray EnumValues; }; diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h index 6651248..66ba3ac 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h @@ -139,7 +139,7 @@ DECLARE_MULTICAST_DELEGATE_OneParam(FOnAgentAudioData, const TArray& /*PC // 4. React to events (OnAgentTranscript, OnAgentTextResponse, etc.) in Blueprint. // 5. Call EndConversation() when done. // ───────────────────────────────────────────────────────────────────────────── -UCLASS(ClassGroup = "PS AI ConvAgent", meta = (BlueprintSpawnableComponent), +UCLASS(ClassGroup = "ASTERION|PS_AI_ConvAgent", meta = (BlueprintSpawnableComponent), DisplayName = "PS AI ConvAgent - ElevenLabs Conv") class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_ElevenLabsComponent : public UActorComponent { @@ -153,18 +153,18 @@ public: /** Agent configuration data asset. * When set, the AgentID is resolved from this asset at conversation start. * Create one via Content Browser → right-click → Miscellaneous → PS AI ConvAgent Agent Config. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "Agent configuration data asset.\nOverrides the AgentID string below when set.")) TObjectPtr AgentConfig; /** ElevenLabs Agent ID used for this conversation. Leave empty to use the default from Project Settings > PS AI ConvAgent - ElevenLabs. * Overridden by AgentConfig if set. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "ElevenLabs Agent ID. Leave empty to use the project default from Project Settings.\nOverridden by AgentConfig if set.")) FString AgentID; /** How turn-taking is managed between the user and the agent.\n- Server VAD (recommended): ElevenLabs automatically detects when the user stops speaking.\n- Client Controlled: You manually call StartListening/StopListening (push-to-talk with a key). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "Turn-taking mode.\n- Server VAD: ElevenLabs detects end-of-speech automatically (hands-free).\n- Client Controlled: You call StartListening/StopListening manually (push-to-talk).")) EPS_AI_ConvAgent_TurnMode_ElevenLabs TurnMode = EPS_AI_ConvAgent_TurnMode_ElevenLabs::Server; @@ -174,38 +174,38 @@ public: * The agent remembers the full conversation context between interactions. * When false (ephemeral), each StartConversation opens a fresh WebSocket session * and EndConversation closes it. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "Keep the WebSocket open across conversation cycles.\n- True: agent remembers the full conversation between interactions.\n- False: each StartConversation opens a new session.")) bool bPersistentSession = true; /** Automatically open the microphone as soon as the WebSocket connection is established. Only applies in Server VAD mode. In Client (push-to-talk) mode, you must call StartListening manually. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "Auto-open the microphone when the conversation starts.\nOnly applies in Server VAD mode. In push-to-talk mode, call StartListening() manually.")) bool bAutoStartListening = true; /** How many milliseconds of microphone audio to accumulate before sending a chunk to ElevenLabs. Lower values reduce latency but may degrade voice detection accuracy. Higher values are more reliable but add delay. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs|Latency", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Latency", meta = (ClampMin = "20", ClampMax = "500", ToolTip = "Mic audio chunk duration sent to ElevenLabs.\n- 50-80ms: lower latency, less reliable voice detection.\n- 100ms (default): good balance.\n- 150-250ms: more reliable, higher latency.")) int32 MicChunkDurationMs = 100; /** Allow the user to interrupt the agent while it is speaking.\n- In Server VAD mode: the microphone stays open during agent speech and the server detects interruptions automatically.\n- In Client (push-to-talk) mode: pressing the talk key while the agent speaks sends an interrupt signal.\n- When disabled: the user must wait for the agent to finish speaking before talking. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ToolTip = "Allow the user to interrupt the agent while it speaks.\n- Server VAD: mic stays open, server detects user voice automatically.\n- Push-to-talk: pressing the talk key interrupts the agent.\n- Disabled: user must wait for the agent to finish.")) bool bAllowInterruption = true; /** Enable the OnAgentTranscript event, which provides real-time speech-to-text of what the user is saying. Disable if you don't need to display user speech to reduce processing overhead. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fire OnAgentTranscript with real-time speech-to-text of user speech.\nDisable if you don't need to display what the user said.")) bool bEnableUserTranscript = true; /** Enable the OnAgentTextResponse event, which provides the agent's complete text response once fully generated. Disable if you only need the audio output. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fire OnAgentTextResponse with the agent's complete text once fully generated.\nDisable if you only need the audio output.")) bool bEnableAgentTextResponse = true; /** Enable the OnAgentPartialResponse event, which streams the agent's text word-by-word as the LLM generates it. Use this for real-time subtitles that appear while the agent speaks, rather than waiting for the full response. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fire OnAgentPartialResponse with streaming text fragments as the LLM generates them.\nIdeal for real-time subtitles. Each event gives one text chunk, not the accumulated text.")) bool bEnableAgentPartialResponse = false; @@ -214,7 +214,7 @@ public: * When adaptive mode is on, the system starts here and can only decrease * (never increase) as it measures that chunks arrive fast enough. * Set to 0 to disable pre-buffering entirely. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs|Latency", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Latency", meta = (ClampMin = "0", ClampMax = "4000", ToolTip = "Pre-buffer delay (ms) — your safe 'worst case' value.\nAdaptive mode can only decrease from here, never increase.\nSet 0 to disable pre-buffering entirely.")) int32 AudioPreBufferMs = 300; @@ -223,13 +223,13 @@ public: * lowers the pre-buffer when TTS chunks arrive fast enough. * The system can only decrease from AudioPreBufferMs — never increase. * Resets to AudioPreBufferMs at the start of each conversation. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs|Latency", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Latency", meta = (EditCondition = "AudioPreBufferMs > 0", ToolTip = "Automatically lower pre-buffer when connection is good.\nCan only decrease, never increase beyond AudioPreBufferMs.\nResets each conversation.")) bool bAdaptivePreBuffer = true; /** Safety timeout: if the server does not start generating a response within this many seconds after the user stops speaking, fire OnAgentResponseTimeout. Set to 0 to disable. A normal response starts within 0.1-0.8s. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ClampMin = "0.0", ToolTip = "Seconds to wait for a server response after the user stops speaking.\nFires OnAgentResponseTimeout if exceeded. Normal latency is 0.1-0.8s.\nSet to 0 to disable. Default: 10s.")) float ResponseTimeoutSeconds = 10.0f; @@ -237,7 +237,7 @@ public: /** Maximum auto-reconnection attempts on unexpected WebSocket disconnect. * Uses exponential backoff (1s, 2s, 4s, ... up to 30s). * Set to 0 to disable auto-reconnection. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs", meta = (ClampMin = "0", ToolTip = "Auto-reconnect attempts on unexpected disconnect.\n0 = disabled. Uses exponential backoff.")) int32 MaxReconnectAttempts = 5; @@ -248,7 +248,7 @@ public: * When set, the agent's audio playback will be spatialized in 3D — volume * and panning change based on distance and direction from the listener. * Leave null for non-spatialized (2D) audio (default, same as before). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|ElevenLabs|Audio", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Audio", meta = (ToolTip = "Sound attenuation for 3D spatialized audio.\nLeave null for non-spatialized (2D) playback.")) USoundAttenuation* SoundAttenuation = nullptr; @@ -256,104 +256,104 @@ public: /** Enable debug logging for this component. * When false, only key events (init, errors, state changes) are logged. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug") bool bDebug = false; /** Verbosity level when bDebug is true. * 0 = minimal extras, 1 = normal debug, 2 = detailed, 3 = per-frame data. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug", meta = (ClampMin = "0", ClampMax = "3", EditCondition = "bDebug")) int32 DebugVerbosity = 1; /** Show a separate latency debug HUD with detailed per-turn timing breakdown. * Independent from bDebug — can be enabled alone via CVar ps.ai.ConvAgent.Debug.Latency. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug") bool bDebugLatency = false; // ── Events ──────────────────────────────────────────────────────────────── /** Fired when the WebSocket connection is established and the conversation session is ready. Provides the ConversationID and AgentID. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when the connection to ElevenLabs is established and the conversation is ready to begin.")) FOnAgentConnected OnAgentConnected; /** Fired when the WebSocket connection is closed (gracefully or due to an error). Provides the status code and reason. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when the connection to ElevenLabs is closed. Check StatusCode and Reason for details.")) FOnAgentDisconnected OnAgentDisconnected; /** Fired on any connection or protocol error. The error message describes what went wrong. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires on connection or protocol errors. The ErrorMessage describes the issue.")) FOnAgentError OnAgentError; /** Fired with real-time speech-to-text of the user's voice. Includes both tentative (in-progress) and final transcripts. Requires bEnableUserTranscript to be true. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Real-time speech-to-text of the user's voice.\nIncludes tentative and final transcripts. Enable with bEnableUserTranscript.")) FOnAgentTranscript OnAgentTranscript; /** Fired once when the agent's complete text response is available. This is the full text that corresponds to the audio the agent speaks. Requires bEnableAgentTextResponse to be true. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "The agent's complete text response (matches the spoken audio).\nFires once when the full text is ready. Enable with bEnableAgentTextResponse.")) FOnAgentTextResponse OnAgentTextResponse; /** Fired repeatedly as the LLM generates text, providing one word/fragment at a time. Use for real-time subtitles. Each call gives a new fragment, NOT the accumulated text. Requires bEnableAgentPartialResponse to be true. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Streaming text fragments as the LLM generates them (word by word).\nIdeal for real-time subtitles. Enable with bEnableAgentPartialResponse.")) FOnAgentPartialResponse OnAgentPartialResponse; /** Fired when the agent begins playing audio (first audio chunk received). Use this to trigger speech animations or UI indicators. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when the agent starts speaking (first audio chunk). Use for lip-sync or UI feedback.")) FOnAgentStartedSpeaking OnAgentStartedSpeaking; /** Fired when audio playback actually starts — AFTER any pre-buffering delay. * Unlike OnAgentStartedSpeaking (first chunk arrival), this fires when audio is audible. * Use this for body/gesture animations that should be synced with audible speech. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when audio playback actually starts (after pre-buffering).\nSynced with audible speech. Use for body animations.")) FOnAudioPlaybackStarted OnAudioPlaybackStarted; /** Fired when the agent finishes playing all audio. Use this to re-open the microphone (in Server VAD mode without interruption) or update UI. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when the agent finishes speaking. Use to re-open the mic or update UI.")) FOnAgentStoppedSpeaking OnAgentStoppedSpeaking; /** Fired when the agent's speech is interrupted (either by the user speaking over it, or by a manual InterruptAgent call). The audio playback is automatically stopped. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when the agent is interrupted mid-speech. Audio is automatically stopped.")) FOnAgentInterrupted OnAgentInterrupted; /** Fired when the server starts generating a response (before any audio arrives). Use this for "thinking..." UI feedback. In push-to-talk mode, the microphone is automatically closed when this fires. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when the server starts generating (before audio arrives).\nUse for 'thinking...' UI. Mic is auto-closed in push-to-talk mode.")) FOnAgentStartedGenerating OnAgentStartedGenerating; /** Fired if the server does not start generating a response within ResponseTimeoutSeconds after the user stops speaking. Use this to show a "try again" message or automatically re-open the microphone. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires if the server doesn't respond within ResponseTimeoutSeconds.\nUse to show 'try again' or re-open the mic automatically.")) FOnAgentResponseTimeout OnAgentResponseTimeout; /** Fired when the agent changes emotion via the "set_emotion" client tool. The emotion is set BEFORE the corresponding audio arrives, giving you time to smoothly blend facial expressions. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when the agent sets an emotion (joy, sadness, surprise, fear, anger, disgust).\nDriven by the 'set_emotion' client tool. Arrives before the audio.")) FOnAgentEmotionChanged OnAgentEmotionChanged; /** Fired when the LLM triggers a perform_action tool call. * ActionName is the identifier from the ActionSet (e.g. "flee", "draw_weapon"). * Handle this in Blueprint to play animations, trigger AI behavior, etc. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when the agent triggers a physical action.\nActionName matches the Name field from the ActionSet Data Asset.")) FOnAgentActionRequested OnAgentActionRequested; /** Fired for client tool calls that are NOT automatically handled (i.e. not "set_emotion", not "perform_action"). You must call GetWebSocketProxy()->SendClientToolResult() to respond. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires for custom client tool calls (not set_emotion, not perform_action).\nYou must respond via GetWebSocketProxy()->SendClientToolResult().")) FOnAgentClientToolCall OnAgentClientToolCall; /** Fired when the active speaker changes in a multi-player shared conversation. * NewSpeaker is null when no one is speaking (idle timeout). */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when the speaking player changes.\nNewSpeaker is null when no one is speaking.")) FOnActiveSpeakerChanged OnActiveSpeakerChanged; @@ -361,26 +361,26 @@ public: * components back to neutral. The NPC is fully idle and the game can start * the physical action (flee, draw weapon, etc.). * Call ForceEnableConversation() later to allow conversation again. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when all animation components have returned to neutral after ForceDisableConversation().\nThe agent is ready for a game action.")) FOnReadyForAction OnReadyForAction; /** Fired when this agent starts passively tracking a nearby player (head/eyes gaze). */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when this agent starts passively tracking a nearby player.\nTriggered by proximity awareness — the agent is not selected for conversation.")) FOnAgentPassiveGazeStarted OnPassiveGazeStarted; /** Fired when this agent stops passively tracking a player (left range or selected). */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events", meta = (ToolTip = "Fires when this agent stops passively tracking a player.\nThe player left range or selected this agent for active conversation.")) FOnAgentPassiveGazeStopped OnPassiveGazeStopped; /** The current emotion of the agent, as set by the "set_emotion" client tool. Defaults to Neutral. */ - UPROPERTY(ReplicatedUsing = OnRep_Emotion, BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(ReplicatedUsing = OnRep_Emotion, BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") EPS_AI_ConvAgent_Emotion CurrentEmotion = EPS_AI_ConvAgent_Emotion::Neutral; /** The current emotion intensity. Defaults to Medium. */ - UPROPERTY(ReplicatedUsing = OnRep_Emotion, BlueprintReadOnly, Category = "PS AI ConvAgent|ElevenLabs") + UPROPERTY(ReplicatedUsing = OnRep_Emotion, BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") EPS_AI_ConvAgent_EmotionIntensity CurrentEmotionIntensity = EPS_AI_ConvAgent_EmotionIntensity::Medium; // ── Raw audio data (C++ only, used by LipSync component) ──────────────── @@ -392,26 +392,26 @@ public: /** True when one or more players are in conversation with this NPC. * Replicated to all clients for UI feedback, gaze, LOD, etc. */ - UPROPERTY(ReplicatedUsing = OnRep_ConversationState, BlueprintReadOnly, Category = "PS AI ConvAgent|Network") + UPROPERTY(ReplicatedUsing = OnRep_ConversationState, BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|Network") bool bNetIsConversing = false; /** All player pawns currently in conversation with this NPC. * Multiple players can share the same agent — audio is routed via speaker arbitration. * Replicated to ALL clients for gaze target and LOD distance checks. */ - UPROPERTY(ReplicatedUsing = OnRep_ConversationState, BlueprintReadOnly, Category = "PS AI ConvAgent|Network") + UPROPERTY(ReplicatedUsing = OnRep_ConversationState, BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|Network") TArray> NetConnectedPawns; /** The player currently speaking (active audio sender). Null if no one is speaking. * Used by GazeComponent for target switching — the NPC looks at whoever is talking. * Replicated to ALL clients so gaze updates everywhere. */ - UPROPERTY(ReplicatedUsing = OnRep_ActiveSpeaker, BlueprintReadOnly, Category = "PS AI ConvAgent|Network") + UPROPERTY(ReplicatedUsing = OnRep_ActiveSpeaker, BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|Network") TObjectPtr NetActiveSpeakerPawn = nullptr; // ── Multi-player speaker arbitration (server only) ────────────────────── /** Minimum seconds of silence from the current speaker before allowing a speaker switch. * Prevents rapid ping-pong switching when both players make brief sounds. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Network|MultiPlayer", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Network|MultiPlayer", meta = (ClampMin = "0.0", ClampMax = "5.0", ToolTip = "Minimum silence from current speaker before switching to another.\nPrevents rapid gaze flip-flop.")) float SpeakerSwitchHysteresis = 0.3f; @@ -419,7 +419,7 @@ public: /** Seconds after last speech before the active speaker is cleared. * When cleared, gaze returns to the last speaker position (or closest connected player). * Set to 0 to never clear (last speaker stays active indefinitely). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Network|MultiPlayer", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Network|MultiPlayer", meta = (ClampMin = "0.0", ToolTip = "Seconds of silence before clearing the active speaker.\n0 = last speaker stays active indefinitely.")) float SpeakerIdleTimeout = 3.0f; @@ -428,14 +428,14 @@ public: /** Distance (cm) beyond which remote clients stop receiving agent audio entirely. * The speaking player always receives full audio regardless of distance. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Network|LOD", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Network|LOD", meta = (ClampMin = "0", ToolTip = "Distance beyond which audio is culled for non-speaking players. 0 = no cull.")) float AudioLODCullDistance = 3000.f; /** Distance (cm) beyond which remote clients skip lip-sync / emotion processing. * Audio still plays (if within AudioLODCullDistance) but without facial animation. * The speaking player always gets full lip-sync regardless of distance. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Network|LOD", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Network|LOD", meta = (ClampMin = "0", ToolTip = "Distance beyond which lip-sync is skipped for non-speaking players. 0 = no LOD.")) float LipSyncLODDistance = 1500.f; @@ -514,25 +514,25 @@ public: * Open the WebSocket connection and start the conversation. * If bAutoStartListening is true, microphone capture also starts once connected. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void StartConversation(); /** Close the WebSocket and stop all audio. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void EndConversation(); /** * Start capturing microphone audio and streaming it to ElevenLabs. * In Client turn mode, also sends a UserTurnStart signal. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void StartListening(); /** * Stop capturing microphone audio. * In Client turn mode, also sends a UserTurnEnd signal. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void StopListening(); /** @@ -540,11 +540,11 @@ public: * The agent will respond with audio and text just as if it heard you speak. * Useful for testing in the Editor or for text-based interaction. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void SendTextMessage(const FString& Text); /** Interrupt the agent's current utterance. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void InterruptAgent(); /** @@ -565,7 +565,7 @@ public: * * @param ActionName The action to perform once neutral (passed through to OnReadyForAction). */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void ForceDisableConversation(const FString& ActionName); /** @@ -573,7 +573,7 @@ public: * Clears the disabled flag so the InteractionComponent can auto-start * a new conversation on the next tick (if the player is still looking). */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void ForceEnableConversation(); /** @@ -583,7 +583,7 @@ public: * The component must be connected and listening (StartListening called) for audio to be sent. * @param FloatPCM Float32 samples, 16000 Hz mono (same format as MicrophoneCaptureComponent output). */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void FeedExternalAudio(const TArray& FloatPCM); /** Receive mic audio from a specific player with speaker arbitration. @@ -592,13 +592,13 @@ public: // ── State queries ───────────────────────────────────────────────────────── - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") bool IsConnected() const; - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") bool IsListening() const { return bIsListening; } - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") bool IsAgentSpeaking() const { return bAgentSpeaking; } /** True when the agent is speaking OR the server hasn't confirmed the full response yet. @@ -606,21 +606,21 @@ public: * produce more audio (e.g. during TTS inter-batch gaps). */ bool IsAgentSpeakingOrPending() const { return bAgentSpeaking || (bAgentGenerating && !bAgentResponseReceived); } - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") const FPS_AI_ConvAgent_ConversationInfo_ElevenLabs& GetConversationInfo() const; /** True while conversation is force-disabled (ForceDisableConversation was called). * While disabled, StartConversation and ServerJoinConversation are blocked. */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") bool IsConversationDisabled() const { return bConversationDisabledByAction; } /** True while audio is being pre-buffered (playback hasn't started yet). * Used by the LipSync component to pause viseme queue consumption. */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") bool IsPreBuffering() const { return bPreBuffering; } /** Access the underlying WebSocket proxy (advanced use). */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") UPS_AI_ConvAgent_WebSocket_ElevenLabsProxy* GetWebSocketProxy() const { return WebSocketProxy; } // ───────────────────────────────────────────────────────────────────────── diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_FacialExpressionComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_FacialExpressionComponent.h index 5cfbe69..93d5173 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_FacialExpressionComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_FacialExpressionComponent.h @@ -35,7 +35,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnExpressionChanged, // 4. Emotion animations crossfade smoothly (configurable duration). // 5. The AnimNode reads GetCurrentEmotionCurves() and injects them into the pose. // ───────────────────────────────────────────────────────────────────────────── -UCLASS(ClassGroup = "PS AI ConvAgent", meta = (BlueprintSpawnableComponent), +UCLASS(ClassGroup = "ASTERION|PS_AI_ConvAgent", meta = (BlueprintSpawnableComponent), DisplayName = "PS AI ConvAgent Facial Expression") class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_FacialExpressionComponent : public UActorComponent { @@ -48,23 +48,23 @@ public: /** Emotion pose map asset containing emotion AnimSequences (Normal / Medium / Extreme per emotion). * Create a dedicated PS_AI_ConvAgent_EmotionPoseMap asset in the Content Browser. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|FacialExpression", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|FacialExpression", meta = (ToolTip = "Dedicated Emotion Pose Map asset.\nRight-click Content Browser → Miscellaneous → PS AI ConvAgent Emotion Pose Map.")) TObjectPtr EmotionPoseMap; /** Whether facial expressions are currently active (read-only). * Managed automatically: activates on conversation connect, deactivates on disconnect. * The underlying emotion playback keeps running so reactivation is seamless. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|FacialExpression") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|FacialExpression") bool bActive = true; /** How long (seconds) to blend in/out when bActive changes. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|FacialExpression", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|FacialExpression", meta = (ClampMin = "0.05", ClampMax = "3.0")) float ActivationBlendDuration = 0.5f; /** Emotion crossfade duration in seconds. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|FacialExpression", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|FacialExpression", meta = (ClampMin = "0.1", ClampMax = "3.0", ToolTip = "How long (seconds) to crossfade between emotions.\n0.5 = snappy, 1.5 = smooth.")) float EmotionBlendDuration = 0.5f; @@ -73,12 +73,12 @@ public: /** Enable debug logging for this component. * When false, only key events (init, errors, state changes) are logged. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug") bool bDebug = false; /** Verbosity level when bDebug is true. * 0 = minimal extras, 1 = normal debug, 2 = detailed, 3 = per-frame data. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug", meta = (ClampMin = "0", ClampMax = "3", EditCondition = "bDebug")) int32 DebugVerbosity = 1; @@ -86,7 +86,7 @@ public: /** Get the current emotion curves evaluated from the playing AnimSequence. * Returns a COPY — safe to call from any thread (AnimNode worker thread). */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|FacialExpression") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|FacialExpression") TMap GetCurrentEmotionCurves() const { FScopeLock Lock(&EmotionCurveLock); @@ -94,15 +94,15 @@ public: } /** Get the active emotion. */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|FacialExpression") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|FacialExpression") EPS_AI_ConvAgent_Emotion GetActiveEmotion() const { return ActiveEmotion; } /** Get the active emotion intensity. */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|FacialExpression") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|FacialExpression") EPS_AI_ConvAgent_EmotionIntensity GetActiveIntensity() const { return ActiveEmotionIntensity; } /** Check if a curve name belongs to the mouth area (overridden by lip sync). */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|FacialExpression") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|FacialExpression") static bool IsMouthCurve(const FName& CurveName); // ── Events ─────────────────────────────────────────────────────────────── @@ -111,7 +111,7 @@ public: * Subscribe in Blueprints to react to expression changes (trigger animations, * particles, sounds, etc.). Only fires when the emotion actually differs * from the previous one. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|FacialExpression|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|FacialExpression|Events") FOnExpressionChanged OnExpressionChanged; /** Current activation blend alpha (0 = fully inactive, 1 = fully active). diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_GazeComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_GazeComponent.h index 22f5fe2..c22c5f4 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_GazeComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_GazeComponent.h @@ -58,7 +58,7 @@ struct PS_AI_CONVAGENT_API FPS_AI_ConvAgent_NeckBoneEntry // 5. Set TargetOffset for actors without a skeleton (e.g. (0,0,160) for // eye-level on a simple actor). // ───────────────────────────────────────────────────────────────────────────── -UCLASS(ClassGroup = "PS AI ConvAgent", meta = (BlueprintSpawnableComponent), +UCLASS(ClassGroup = "ASTERION|PS_AI_ConvAgent", meta = (BlueprintSpawnableComponent), DisplayName = "PS AI ConvAgent Gaze") class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_GazeComponent : public UActorComponent { @@ -71,24 +71,24 @@ public: /** The actor to look at. Can be any actor (player, prop, etc.). * Set to null to smoothly return to neutral. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ToolTip = "Target actor to look at.\nSet to null to return to neutral.")) TObjectPtr TargetActor; /** When false, all gaze outputs smoothly blend to neutral (passthrough). * The underlying tracking keeps running so reactivation is seamless. * Managed automatically: activates on conversation connect, deactivates on disconnect. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|Gaze") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|Gaze") bool bActive = true; /** How long (seconds) to blend in/out when bActive changes. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0.05", ClampMax = "3.0")) float ActivationBlendDuration = 0.5f; /** When false, body rotation is frozen — only head and eyes track the target. * Useful to have the agent notice the player (eyes+head) before fully engaging (body). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ToolTip = "Enable body rotation toward the target.\nWhen false, only head and eyes track.")) bool bEnableBodyTracking = true; @@ -96,7 +96,7 @@ public: * When enabled, TargetOffset is ignored and the agent looks at the midpoint * between the target pawn's eye bones. * Fallback chain: eye bones → head bone → PawnViewLocation → FallbackEyeHeight. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ToolTip = "Auto-target the pawn's eye bones for eye contact.\nFallback: eye bones > head bone > PawnViewLocation > FallbackEyeHeight.")) bool bAutoTargetEyes = true; @@ -104,7 +104,7 @@ public: * AND the target is not a Pawn. For Pawns without skeleton, GetPawnViewLocation() * is used instead (accounts for BaseEyeHeight automatically). * Only applies to non-Pawn actors (props, triggers, etc.). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (EditCondition = "bAutoTargetEyes", ClampMin = "0", ToolTip = "Height offset (cm) for non-Pawn targets without skeleton.\nPawns use BaseEyeHeight automatically.\nOnly used as last-resort fallback for non-Pawn actors.")) float FallbackEyeHeight = 160.0f; @@ -112,7 +112,7 @@ public: /** Offset from the target actor's origin to aim at. * Only used when bAutoTargetEyes is false. * E.g. (0,0,160) for eye-level on a simple actor. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (EditCondition = "!bAutoTargetEyes", ToolTip = "Offset from target actor origin.\nE.g. (0,0,160) for eye-level.\nOnly used when Auto Target Eyes is disabled.")) FVector TargetOffset = FVector(0.0f, 0.0f, 0.0f); @@ -127,44 +127,44 @@ public: // so small movements around the target don't re-trigger higher layers. /** Maximum head yaw rotation in degrees. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0", ClampMax = "90")) float MaxHeadYaw = 40.0f; /** Maximum head pitch rotation in degrees. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0", ClampMax = "90")) float MaxHeadPitch = 30.0f; /** Maximum horizontal eye angle in degrees. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0", ClampMax = "90")) float MaxEyeHorizontal = 15.0f; /** Maximum vertical eye angle in degrees. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0", ClampMax = "90")) float MaxEyeVertical = 10.0f; // ── Smoothing speeds ───────────────────────────────────────────────────── /** Body rotation interpolation speed (lower = slower, more natural). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0.1", ClampMax = "20")) float BodyInterpSpeed = 4.0f; /** Head rotation interpolation speed. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0.1", ClampMax = "20")) float HeadInterpSpeed = 4.0f; /** Eye movement interpolation speed (higher = snappier). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0.1", ClampMax = "20")) float EyeInterpSpeed = 5.0f; /** Interpolation speed when returning to neutral (TargetActor is null). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0.1", ClampMax = "20")) float ReturnToNeutralSpeed = 3.0f; @@ -178,7 +178,7 @@ public: * 1.0 = head always points at target regardless of animation. * 0.0 = gaze is additive on top of animation (old behavior). * Default: 1.0 for conversational AI (always look at who you talk to). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0", ClampMax = "1")) float HeadAnimationCompensation = 0.9f; @@ -186,7 +186,7 @@ public: * 1.0 = eyes frozen on gaze target, animation's eye movement removed. * 0.0 = animation's eyes play through, gaze is additive. * Intermediate (e.g. 0.5) = smooth 50/50 blend. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0", ClampMax = "1")) float EyeAnimationCompensation = 0.6f; @@ -195,7 +195,7 @@ public: * counter-rotates the gaze to keep the head pointing at the target. * 1.0 = full compensation — head stays locked on target even during bows. * 0.0 = no compensation — head follows body movement naturally. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "0", ClampMax = "1")) float BodyDriftCompensation = 0.8f; @@ -204,12 +204,12 @@ public: /** Enable debug logging for this component. * When false, only key events (init, errors, state changes) are logged. * When true, additional diagnostic info is logged based on DebugVerbosity. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug") bool bDebug = false; /** Verbosity level when bDebug is true. * 0 = minimal extras, 1 = normal debug, 2 = detailed, 3 = per-frame data. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug", meta = (ClampMin = "0", ClampMax = "3", EditCondition = "bDebug")) int32 DebugVerbosity = 1; @@ -217,7 +217,7 @@ public: * Green = head bone → target (desired). * Cyan = computed gaze direction (body yaw + head + eyes). * Useful for verifying that eye contact is accurate. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug", meta = (EditCondition = "bDebug")) bool bDrawDebugGaze = false; @@ -228,14 +228,14 @@ public: * 0 = mesh faces +X (default UE convention) * 90 = mesh faces +Y * -90 = mesh faces -Y */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze", meta = (ClampMin = "-180", ClampMax = "180")) float MeshForwardYawOffset = 90.0f; // ── Head bone ──────────────────────────────────────────────────────────── /** Name of the head bone on the skeletal mesh (used for eye origin calculation). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze") FName HeadBoneName = FName(TEXT("head")); // ── Neck bone chain ───────────────────────────────────────────────────── @@ -244,7 +244,7 @@ public: * Order: root-to-tip (e.g. neck_01 → neck_02 → head). * Weights should sum to ~1.0. * If empty, falls back to single-bone behavior (HeadBoneName, weight 1.0). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Gaze") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Gaze") TArray NeckBoneChain; // ── Getters (read by AnimNode) ─────────────────────────────────────────── @@ -252,7 +252,7 @@ public: /** Get current eye gaze curves (8 ARKit eye look curves). * Returns a COPY scaled by activation alpha — safe to call from any thread. * NOTE: Prefer FillCurrentEyeCurves() for hot paths to avoid per-frame allocation. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|Gaze") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|Gaze") TMap GetCurrentEyeCurves() const { FScopeLock Lock(&GazeDataLock); diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h index 873a324..9f1d234 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h @@ -45,7 +45,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNoConvAgentInRange); // 4. Mic management is auto-detected (no manual flag needed on agents). // 5. Agents manage their own WebSocket connections independently. // ───────────────────────────────────────────────────────────────────────────── -UCLASS(ClassGroup = "PS AI ConvAgent", meta = (BlueprintSpawnableComponent), +UCLASS(ClassGroup = "ASTERION|PS_AI_ConvAgent", meta = (BlueprintSpawnableComponent), DisplayName = "PS AI ConvAgent Interaction") class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_InteractionComponent : public UActorComponent { @@ -57,7 +57,7 @@ public: // ── Configuration ───────────────────────────────────────────────────────── /** Maximum distance (cm) at which an agent can be selected. Agents beyond this are ignored. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction", meta = (ClampMin = "0", ToolTip = "Max distance in cm to interact with an agent.\nAgents beyond this distance are never selected.")) float MaxInteractionDistance = 300.0f; @@ -65,7 +65,7 @@ public: /** Half-angle (degrees) of the view cone used for agent selection. * The cone is centered on the pawn's view direction (camera or control rotation). * Agents outside this cone are not considered unless they are the current sticky selection. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction", meta = (ClampMin = "0", ClampMax = "180", ToolTip = "Half-angle of the view cone (degrees).\nAgents outside this cone are not selected.\n45 = 90-degree total FOV.")) float ViewConeHalfAngle = 45.0f; @@ -73,7 +73,7 @@ public: /** Wider half-angle (degrees) used for the currently selected agent. * This provides hysteresis so the player can look slightly away from * the current agent without losing selection (prevents flickering). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction", meta = (ClampMin = "0", ClampMax = "180", ToolTip = "Sticky cone half-angle for the current agent (degrees).\nMust be >= ViewConeHalfAngle for hysteresis to work.\n60 = agent stays selected until 120-degree total cone is exceeded.")) float SelectionStickyAngle = 60.0f; @@ -81,14 +81,14 @@ public: /** Height offset (cm) added to the agent's origin for the view cone check. * Agent origins are typically at foot level; this shifts the target point * up to chest/head height so close-range look-at checks don't fail. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction", meta = (ClampMin = "0", ToolTip = "Height offset (cm) above the agent origin for the view cone check.\nPrevents losing selection at close range because the camera looks above the agent's feet.\n150 = roughly chest height for a MetaHuman.")) float AgentEyeLevelOffset = 150.0f; /** When false, only distance matters for selection (no view cone check). * The closest agent within MaxInteractionDistance is always selected. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction", meta = (ToolTip = "Require the player to look at an agent to select it.\nWhen false, the closest agent within range is always selected.")) bool bRequireLookAt = true; @@ -97,20 +97,20 @@ public: /** Automatically set/clear the agent's GazeComponent TargetActor * when the agent is selected/deselected. When false, gaze must * be managed from Blueprint (e.g. on conversation start). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Gaze", meta = (ToolTip = "Automatically point the agent's gaze at the pawn on selection.\nDisable for manual control (e.g. set target only when conversation starts).")) bool bAutoManageGaze = true; /** Delay (seconds) before setting the agent's gaze target after selection. * 0 = immediate. Useful to let the agent "notice" the player with a beat. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Gaze", meta = (EditCondition = "bAutoManageGaze", ClampMin = "0", ToolTip = "Seconds to wait before the agent looks at the pawn.\n0 = immediate.")) float GazeAttachDelay = 0.0f; /** Delay (seconds) before clearing the agent's gaze target after deselection. * 0 = immediate. Useful to have the agent keep looking briefly as the player leaves. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Gaze", meta = (EditCondition = "bAutoManageGaze", ClampMin = "0", ToolTip = "Seconds to wait before the agent stops looking at the pawn.\n0 = immediate.")) float GazeDetachDelay = 0.0f; @@ -119,20 +119,20 @@ public: * with head+eyes (and optionally body). This creates natural "awareness" * without requiring an active conversation. * Also controls the passive gaze retained after switching conversations. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Gaze", meta = (EditCondition = "bAutoManageGaze", ToolTip = "Agents in range but not selected will passively track the player.\nAlso applies to retained gaze after switching conversations.")) bool bAutoPassiveGaze = true; /** In passive gaze mode, the agent's head and eyes follow the player. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Gaze", meta = (EditCondition = "bAutoManageGaze && bAutoPassiveGaze", ToolTip = "Enable head+eyes tracking in passive gaze mode.")) bool bPassiveGazeHeadEyes = true; /** In passive gaze mode, the agent's body also turns toward the player. * When false, only head and eyes track — the body stays in its default animation. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction|Gaze", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Gaze", meta = (EditCondition = "bAutoManageGaze && bAutoPassiveGaze", ToolTip = "Enable body rotation in passive gaze mode.\nWhen false, only head and eyes track.")) bool bPassiveGazeBody = false; @@ -142,7 +142,7 @@ public: /** How long (seconds) the player must look at a different agent before switching * during an active conversation. Prevents accidental switches when glancing around. * Only applies when a conversation is active (bNetIsConversing). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction", meta = (ClampMin = "0.0", ClampMax = "5.0", ToolTip = "Seconds the player must look at another agent before switching mid-conversation.\nPrevents accidental switches when glancing around.\n0 = instant switch.")) float ConversationSwitchDelay = 1.0f; @@ -153,7 +153,7 @@ public: * via StartConversationWithSelectedAgent() (e.g. on a key press). * Set to false when you have multiple agents in a scene to prevent them * all from greeting the player simultaneously. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction", meta = (ToolTip = "Auto-start the WebSocket when an agent is selected by proximity.\nSet to false to require explicit interaction (call StartConversationWithSelectedAgent).\nUseful with multiple agents to prevent simultaneous greetings.")) bool bAutoStartConversation = true; @@ -162,48 +162,48 @@ public: /** Automatically call StartListening/StopListening on the agent's * ElevenLabsComponent when selected/deselected. When false, listening * must be managed from Blueprint. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction|Listening", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Listening", meta = (ToolTip = "Automatically open/close the agent's mic turn on selection.\nDisable for manual control (e.g. push-to-talk).")) bool bAutoManageListening = true; // ── Debug ──────────────────────────────────────────────────────────────── /** Enable debug logging for this component. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug") bool bDebug = false; /** Verbosity level when bDebug is true. * 0 = selection changes only, 1 = candidate evaluation, 2 = per-frame data. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug", meta = (ClampMin = "0", ClampMax = "3", EditCondition = "bDebug")) int32 DebugVerbosity = 1; // ── Events ──────────────────────────────────────────────────────────────── /** Fired when a new agent enters selection. Use this to set gaze targets, show UI, etc. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|Interaction|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Events", meta = (ToolTip = "Fires when a new agent is selected.\nSet gaze targets, update UI, etc.")) FOnConvAgentSelected OnAgentSelected; /** Fired when the previously selected agent loses selection. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|Interaction|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Events", meta = (ToolTip = "Fires when the current agent is deselected.\nClear gaze targets, hide UI, etc.")) FOnConvAgentDeselected OnAgentDeselected; /** Fired when no agent is within range or view cone. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|Interaction|Events", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|Interaction|Events", meta = (ToolTip = "Fires when no agent meets the distance/view criteria.")) FOnNoConvAgentInRange OnNoAgentInRange; // ── Blueprint API ───────────────────────────────────────────────────────── /** Get the currently selected agent (null if none). */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|Interaction") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|Interaction") UPS_AI_ConvAgent_ElevenLabsComponent* GetSelectedAgent() const; /** Manually select a specific agent, bypassing distance/view checks. * Pass null to clear the selection. Automatic selection resumes next tick. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|Interaction") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|Interaction") void ForceSelectAgent(UPS_AI_ConvAgent_ElevenLabsComponent* Agent); /** Start the WebSocket conversation with the currently selected agent. @@ -211,18 +211,18 @@ public: * interacts (e.g. presses a key, enters a trigger zone). * Does nothing if no agent is selected or the agent is already connected. * Also starts mic capture and listening automatically. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|Interaction") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|Interaction") void StartConversationWithSelectedAgent(); /** Send a text message to the currently selected agent. * The agent responds with audio and text, just as if it heard the player speak. * Handles network relay automatically (works on both server and client). * Does nothing if no agent is selected or not connected. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|Interaction") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|Interaction") void SendTextToSelectedAgent(const FString& Text); /** Clear the current selection. Automatic selection resumes next tick. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|Interaction") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|Interaction") void ClearSelection(); // ── Network relay RPCs ─────────────────────────────────────────────────── diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionSubsystem.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionSubsystem.h index 486812f..1cec6a1 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionSubsystem.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionSubsystem.h @@ -29,11 +29,11 @@ public: void UnregisterAgent(UPS_AI_ConvAgent_ElevenLabsComponent* Agent); /** Get all currently registered agent components (weak pointers, may contain stale entries that are cleaned on access). */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|Interaction") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|Interaction") TArray GetRegisteredAgents() const; /** Get the number of registered agents. */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|Interaction") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|Interaction") int32 GetNumRegisteredAgents() const; private: diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_LipSyncComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_LipSyncComponent.h index ac6aa2c..d029674 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_LipSyncComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_LipSyncComponent.h @@ -39,7 +39,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPS_AI_ConvAgent_VisemesReady); * 3. Conversation starts → lip sync works automatically. * 4. (Optional) Bind OnVisemesReady for custom Blueprint handling. */ -UCLASS(ClassGroup = "PS AI ConvAgent", meta = (BlueprintSpawnableComponent), +UCLASS(ClassGroup = "ASTERION|PS_AI_ConvAgent", meta = (BlueprintSpawnableComponent), DisplayName = "PS AI ConvAgent Lip Sync") class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_LipSyncComponent : public UActorComponent { @@ -61,16 +61,16 @@ public: /** Whether lip sync is currently active (read-only). * Managed automatically: activates on conversation connect, deactivates on disconnect. * The underlying audio analysis keeps running so reactivation is seamless. */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|LipSync") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|LipSync") bool bActive = true; /** How long (seconds) to blend in/out when bActive changes. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ClampMin = "0.05", ClampMax = "3.0")) float ActivationBlendDuration = 0.5f; /** Overall mouth movement intensity multiplier. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ClampMin = "0.0", ClampMax = "3.0", ToolTip = "Lip sync intensity.\n1.0 = normal, higher = more expressive, lower = subtler.")) float LipSyncStrength = 1.0f; @@ -78,13 +78,13 @@ public: /** Scales the audio amplitude driving mouth movement. * Lower values produce subtler animation, higher values are more pronounced. * Use this to tone down overly strong lip movement without changing the shape. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ClampMin = "0.5", ClampMax = "1.0", ToolTip = "Audio amplitude scale.\n0.5 = subtle, 0.75 = balanced, 1.0 = full.\nReduces overall mouth movement without affecting viseme shape.")) float AmplitudeScale = 1.0f; /** How quickly viseme weights interpolate towards new values each frame. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ClampMin = "35.0", ClampMax = "65.0", ToolTip = "Smoothing speed for viseme transitions.\n35 = smooth and soft, 50 = balanced, 65 = sharp and responsive.")) float SmoothingSpeed = 50.0f; @@ -95,7 +95,7 @@ public: * When the agent is NOT speaking, lip sync releases all mouth curves so the * FacialExpressionComponent's emotion curves (including mouth) can show through. * When the agent starts speaking, lip sync smoothly takes over mouth curves. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ClampMin = "0.05", ClampMax = "1.0", ToolTip = "Blend duration when speech starts/stops.\nDuring silence, emotion facial curves control the mouth.\nDuring speech, lip sync takes over.")) float SpeechBlendDuration = 0.15f; @@ -108,7 +108,7 @@ public: * 0.0 = lip sync completely overrides expression curves (old behavior). * 1.0 = emotion fully additive on expression curves during speech. * 0.5 = balanced blend (recommended). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ClampMin = "0.0", ClampMax = "1.0", ToolTip = "Emotion bleed-through during speech.\n0 = pure lip sync, 0.5 = balanced, 1 = full emotion on expression curves.\nOnly affects expression curves (smile, frown, etc.), not lip shape.")) float EmotionExpressionBlend = 0.5f; @@ -118,7 +118,7 @@ public: /** Envelope attack time in milliseconds. * Controls how fast the mouth opens when speech starts or gets louder. * Lower = snappier onset, higher = gentler opening. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ClampMin = "1.0", ClampMax = "50.0", ToolTip = "Envelope attack (ms).\n5 = snappy, 10 = balanced, 25 = gentle.\nHow fast the mouth opens on speech onset.")) float EnvelopeAttackMs = 10.0f; @@ -126,7 +126,7 @@ public: /** Envelope release time in milliseconds. * Controls how slowly the mouth closes when speech gets quieter. * Higher = smoother, more natural decay. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ClampMin = "10.0", ClampMax = "200.0", ToolTip = "Envelope release (ms).\n25 = responsive, 50 = balanced, 120 = smooth/cinematic.\nHow slowly the mouth closes between syllables.")) float EnvelopeReleaseMs = 50.0f; @@ -135,12 +135,12 @@ public: /** Enable debug logging for this component. * When false, only key events (init, errors, state changes) are logged. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug") bool bDebug = false; /** Verbosity level when bDebug is true. * 0 = minimal extras, 1 = normal debug, 2 = detailed, 3 = per-frame data. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug", meta = (ClampMin = "0", ClampMax = "3", EditCondition = "bDebug")) int32 DebugVerbosity = 1; @@ -152,7 +152,7 @@ public: * When assigned, curve data is extracted at BeginPlay and replaces the * hardcoded ARKit blendshape mapping with artist-crafted poses. * Leave empty to use the built-in ARKit mapping (backward compatible). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ToolTip = "Reusable pose map asset.\nAssign your MHF_* AnimSequences once, reuse on all MetaHumans.\nLeave empty for built-in ARKit mapping.")) TObjectPtr PoseMap; @@ -160,19 +160,19 @@ public: /** Fires every tick when viseme data has been updated. * Use GetCurrentVisemes() or GetCurrentBlendshapes() to read values. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|LipSync", + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|LipSync", meta = (ToolTip = "Fires each frame with updated viseme data.\nCall GetCurrentVisemes() or GetCurrentBlendshapes() to read values.")) FOnPS_AI_ConvAgent_VisemesReady OnVisemesReady; // ── Getters ─────────────────────────────────────────────────────────────── /** Get current OVR viseme weights (15 values: sil, PP, FF, TH, DD, kk, CH, SS, nn, RR, aa, E, ih, oh, ou). */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|LipSync") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|LipSync") TMap GetCurrentVisemes() const { return SmoothedVisemes; } /** Get current ARKit blendshape weights (MetaHuman compatible: jawOpen, mouthFunnel, mouthClose, etc.). * Thread-safe: returns a snapshot updated each tick. Safe to call from anim worker threads. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|LipSync") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|LipSync") TMap GetCurrentBlendshapes() const { FScopeLock Lock(&BlendshapeLock); @@ -181,7 +181,7 @@ public: /** True when the agent is currently producing speech audio. * When false, lip sync releases mouth curves to let emotion curves through. */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|LipSync") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|LipSync") bool IsSpeaking() const { return bIsSpeaking; } /** Current speech blend alpha (0 = silent/emotion mouth, 1 = lip sync mouth). diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_MicrophoneCaptureComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_MicrophoneCaptureComponent.h index 68a4d02..adb09e3 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_MicrophoneCaptureComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_MicrophoneCaptureComponent.h @@ -22,7 +22,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPS_AI_ConvAgent_VoiceActivityChan * Modelled after Convai's ConvaiAudioCaptureComponent but stripped to the * minimal functionality needed for conversational AI agents. */ -UCLASS(ClassGroup = "PS AI ConvAgent", meta = (BlueprintSpawnableComponent), +UCLASS(ClassGroup = "ASTERION|PS_AI_ConvAgent", meta = (BlueprintSpawnableComponent), DisplayName = "PS AI ConvAgent Microphone Capture") class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_MicrophoneCaptureComponent : public UActorComponent { @@ -32,7 +32,7 @@ public: UPS_AI_ConvAgent_MicrophoneCaptureComponent(); /** Multiplier applied to the microphone input volume before sending to the agent. Increase if the agent has trouble hearing you, decrease if your audio is clipping. Default: 1.0 (no change). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|MicCapture", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|MicCapture", meta = (ClampMin = "0.0", ClampMax = "4.0", ToolTip = "Microphone volume multiplier.\n1.0 = no change. Increase if the agent can't hear you, decrease if audio clips.")) float VolumeMultiplier = 1.0f; @@ -52,50 +52,50 @@ public: // ── Local Voice Activity Detection ────────────────────────────────── /** RMS threshold above which audio is considered voice. Adjust based on mic sensitivity and environment noise. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Voice Activity", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Voice Activity", meta = (ClampMin = "0.001", ClampMax = "0.2")) float VoiceActivityThreshold = 0.015f; /** Time (seconds) RMS must stay above threshold before declaring speech onset. Prevents false triggers from clicks/bumps. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Voice Activity", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Voice Activity", meta = (ClampMin = "0.01", ClampMax = "1.0")) float VoiceOnsetTime = 0.1f; /** Time (seconds) RMS must stay below threshold before declaring speech ended. Prevents cutting off between words. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Voice Activity", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Voice Activity", meta = (ClampMin = "0.1", ClampMax = "3.0")) float VoiceSilenceTime = 0.5f; /** Whether the user is currently speaking (local VAD, independent of ElevenLabs server). */ - UPROPERTY(BlueprintReadOnly, Category = "PS AI ConvAgent|Voice Activity") + UPROPERTY(BlueprintReadOnly, Category = "ASTERION|PS_AI_ConvAgent|Voice Activity") bool bIsUserSpeaking = false; /** Fired when voice activity changes. Use to pause NPC movement, trigger animations, etc. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|Voice Activity") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|Voice Activity") FOnPS_AI_ConvAgent_VoiceActivityChanged OnUserVoiceActivityChanged; // ── Debug ──────────────────────────────────────────────────────────────── /** Enable debug logging for this component. * When false, only key events (init, errors, state changes) are logged. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug") bool bDebug = false; /** Verbosity level when bDebug is true. * 0 = minimal extras, 1 = normal debug, 2 = detailed, 3 = per-frame data. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Debug", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ASTERION|PS_AI_ConvAgent|Debug", meta = (ClampMin = "0", ClampMax = "3", EditCondition = "bDebug")) int32 DebugVerbosity = 1; /** Open the default capture device and begin streaming audio. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|MicCapture") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|MicCapture") void StartCapture(); /** Stop streaming and close the capture device. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|MicCapture") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|MicCapture") void StopCapture(); - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|MicCapture") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|MicCapture") bool IsCapturing() const { return bCapturing; } // ───────────────────────────────────────────────────────────────────────── diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_WebSocket_ElevenLabsProxy.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_WebSocket_ElevenLabsProxy.h index 6654443..f0cdfe4 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_WebSocket_ElevenLabsProxy.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_WebSocket_ElevenLabsProxy.h @@ -69,31 +69,31 @@ public: // ── Events ──────────────────────────────────────────────────────────────── /** Called once the WebSocket handshake succeeds and the agent sends its initiation metadata. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_Connected OnConnected; /** Called when the WebSocket closes (graceful or remote). */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_Disconnected OnDisconnected; /** Called on any connection or protocol error. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_Error OnError; /** Raw PCM audio coming from the agent — feed this into your audio component. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_AudioReceived OnAudioReceived; /** User or agent transcript (may be tentative while the conversation is ongoing). */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_Transcript OnTranscript; /** Final text response from the agent (complements audio). */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_AgentResponse OnAgentResponse; /** The agent was interrupted by new user speech. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_Interrupted OnInterrupted; /** @@ -101,15 +101,15 @@ public: * starts generating. Fires well before audio. The component uses this to stop the * microphone if it was restarted before the server finished processing the previous turn. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_ResponseStarted OnAgentResponseStarted; /** Fired for every agent_chat_response_part with the streaming text fragment. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_ResponsePart OnAgentResponsePart; /** Fired when the agent invokes a client tool. Handle the call and reply with SendClientToolResult. */ - UPROPERTY(BlueprintAssignable, Category = "PS AI ConvAgent|ElevenLabs|Events") + UPROPERTY(BlueprintAssignable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs|Events") FOnPS_AI_ConvAgent_ElevenLabs_ClientToolCall OnClientToolCall; // ── Lifecycle ───────────────────────────────────────────────────────────── @@ -121,21 +121,21 @@ public: * @param AgentID ElevenLabs agent ID. Overrides the project-level default when non-empty. * @param APIKey API key. Overrides the project-level default when non-empty. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void Connect(const FString& AgentID = TEXT(""), const FString& APIKey = TEXT("")); /** * Gracefully close the WebSocket connection. * OnDisconnected will fire after the server acknowledges. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void Disconnect(); /** Current connection state. */ - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") EPS_AI_ConvAgent_ConnectionState_ElevenLabs GetConnectionState() const { return ConnectionState; } - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") bool IsConnected() const { return ConnectionState == EPS_AI_ConvAgent_ConnectionState_ElevenLabs::Connected; } // ── Audio sending ───────────────────────────────────────────────────────── @@ -148,7 +148,7 @@ public: * * @param PCMData Raw PCM bytes (16-bit LE, 16kHz, mono). */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void SendAudioChunk(const TArray& PCMData); // ── Turn control (only relevant in Client turn mode) ────────────────────── @@ -158,7 +158,7 @@ public: * Sends a { "type": "user_activity" } message to the server. * Call this periodically while the user is speaking (e.g. every audio chunk). */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void SendUserTurnStart(); /** @@ -166,7 +166,7 @@ public: * No explicit API message — simply stop sending user_activity. * The server detects silence and hands the turn to the agent. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void SendUserTurnEnd(); /** @@ -174,11 +174,11 @@ public: * Useful for testing or text-only interaction. * Sends: { "type": "user_message", "text": "..." } */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void SendTextMessage(const FString& Text); /** Ask the agent to stop the current utterance. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void SendInterrupt(); /** @@ -189,12 +189,12 @@ public: * @param Result A string result to return to the agent. * @param bIsError True if the tool execution failed. */ - UFUNCTION(BlueprintCallable, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") void SendClientToolResult(const FString& ToolCallId, const FString& Result, bool bIsError = false); // ── Info ────────────────────────────────────────────────────────────────── - UFUNCTION(BlueprintPure, Category = "PS AI ConvAgent|ElevenLabs") + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs") const FPS_AI_ConvAgent_ConversationInfo_ElevenLabs& GetConversationInfo() const { return ConversationInfo; } /** Latest WebSocket round-trip latency reported by the server (ms). diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_BodyExpression.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_BodyExpression.cpp index 4fa27e8..57e3da6 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_BodyExpression.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_BodyExpression.cpp @@ -21,7 +21,7 @@ FText UAnimGraphNode_PS_AI_ConvAgent_BodyExpression::GetTooltipText() const FString UAnimGraphNode_PS_AI_ConvAgent_BodyExpression::GetNodeCategory() const { - return TEXT("PS AI ConvAgent"); + return TEXT("ASTERION|PS_AI_ConvAgent"); } FLinearColor UAnimGraphNode_PS_AI_ConvAgent_BodyExpression::GetNodeTitleColor() const diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_FacialExpression.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_FacialExpression.cpp index d031b5c..06b8d79 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_FacialExpression.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_FacialExpression.cpp @@ -20,7 +20,7 @@ FText UAnimGraphNode_PS_AI_ConvAgent_FacialExpression::GetTooltipText() const FString UAnimGraphNode_PS_AI_ConvAgent_FacialExpression::GetNodeCategory() const { - return TEXT("PS AI ConvAgent"); + return TEXT("ASTERION|PS_AI_ConvAgent"); } FLinearColor UAnimGraphNode_PS_AI_ConvAgent_FacialExpression::GetNodeTitleColor() const diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_Gaze.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_Gaze.cpp index b784537..9c43df9 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_Gaze.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_Gaze.cpp @@ -21,7 +21,7 @@ FText UAnimGraphNode_PS_AI_ConvAgent_Gaze::GetTooltipText() const FString UAnimGraphNode_PS_AI_ConvAgent_Gaze::GetNodeCategory() const { - return TEXT("PS AI ConvAgent"); + return TEXT("ASTERION|PS_AI_ConvAgent"); } FLinearColor UAnimGraphNode_PS_AI_ConvAgent_Gaze::GetNodeTitleColor() const diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_LipSync.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_LipSync.cpp index 827916b..623a86b 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_LipSync.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/AnimGraphNode_PS_AI_ConvAgent_LipSync.cpp @@ -20,7 +20,7 @@ FText UAnimGraphNode_PS_AI_ConvAgent_LipSync::GetTooltipText() const FString UAnimGraphNode_PS_AI_ConvAgent_LipSync::GetNodeCategory() const { - return TEXT("PS AI ConvAgent"); + return TEXT("ASTERION|PS_AI_ConvAgent"); } FLinearColor UAnimGraphNode_PS_AI_ConvAgent_LipSync::GetNodeTitleColor() const diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_BodyExpression.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_BodyExpression.h index 545066c..d76b721 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_BodyExpression.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_BodyExpression.h @@ -10,7 +10,7 @@ /** * AnimGraph editor node for the PS AI ConvAgent Body Expression AnimNode. * - * This node appears in the AnimBP graph editor under the "PS AI ConvAgent" category. + * This node appears in the AnimBP graph editor under the "ASTERION|PS_AI_ConvAgent" category. * Place it in the character's Body AnimBP after upstream body animations * (idle, locomotion). It blends emotion-driven body poses onto the upper body * while passing the lower body through unchanged. diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_FacialExpression.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_FacialExpression.h index 683cc13..e893990 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_FacialExpression.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_FacialExpression.h @@ -10,7 +10,7 @@ /** * AnimGraph editor node for the PS AI ConvAgent Facial Expression AnimNode. * - * This node appears in the AnimBP graph editor under the "PS AI ConvAgent" category. + * This node appears in the AnimBP graph editor under the "ASTERION|PS_AI_ConvAgent" category. * Place it BEFORE the PS AI ConvAgent Lip Sync node in the MetaHuman Face AnimBP. * It auto-discovers the PS_AI_ConvAgent_FacialExpressionComponent on the owning Actor * and injects CTRL_expressions_* curves for emotion-driven facial expressions. diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_Gaze.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_Gaze.h index bbb5491..12165f5 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_Gaze.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_Gaze.h @@ -10,7 +10,7 @@ /** * AnimGraph editor node for the PS AI ConvAgent Gaze AnimNode. * - * This node appears in the AnimBP graph editor under the "PS AI ConvAgent" category. + * This node appears in the AnimBP graph editor under the "ASTERION|PS_AI_ConvAgent" category. * Place it AFTER the PS AI ConvAgent Facial Expression node and BEFORE the * PS AI ConvAgent Lip Sync node in the MetaHuman Face AnimBP. * diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_LipSync.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_LipSync.h index 3b64e14..cdb0e2d 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_LipSync.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Public/AnimGraphNode_PS_AI_ConvAgent_LipSync.h @@ -10,7 +10,7 @@ /** * AnimGraph editor node for the PS AI ConvAgent Lip Sync AnimNode. * - * This node appears in the AnimBP graph editor under the "PS AI ConvAgent" category. + * This node appears in the AnimBP graph editor under the "ASTERION|PS_AI_ConvAgent" category. * Drop it into your MetaHuman Face AnimBP between the pose source and the * Control Rig node. It auto-discovers the PS_AI_ConvAgent_LipSyncComponent on the * owning Actor and injects CTRL_expressions_* curves for lip sync.