Compare commits

..

42 Commits

Author SHA1 Message Date
Lefteris Notas
57ea21073a feat: Add .gitattributes and .gitignore for Unreal project asset management 2026-05-22 19:28:44 +03:00
Lefteris Notas
15d6e88780 feat: Implement BPC_PlatformServiceAbstraction for unified platform detection and SDK routing
- Added BPC_PlatformServiceAbstraction to centralize platform detection and SDK routing for achievements, cloud saves, and overlays.
- Updated dependencies across various systems to utilize the new platform service for consistent platform handling.
- Deprecated old platform enums in favor of a unified EPlatformFamily enum.
- Enhanced documentation for affected systems to reflect changes in platform handling and dependencies.
2026-05-22 18:22:42 +03:00
Lefteris Notas
dc9c1a6b98 Add DA_RenderPipelineProfile and Platform Render Profiles documentation
- Introduced DA_RenderPipelineProfile data asset to define rendering pipeline configurations for various platforms and quality tiers.
- Documented enums, structs, variables, and default preset tables for render settings.
- Created a comprehensive developer guide for setting up platform-specific render profiles, including file structure, profile creation, and integration with upscalers.
- Included validation rules, console variable references, and testing matrices for ensuring compliance with platform requirements.
2026-05-22 18:10:24 +03:00
Lefteris Notas
14441c000c Add haptics example documentation for Project Void controller feedback
- Introduced comprehensive guide for setting up controller haptics and force feedback.
- Detailed directory structure for haptic profiles and creation steps for DA_HapticProfile instances.
- Included platform-specific configurations for Xbox and PS5 DualSense adaptive triggers.
- Outlined wiring of BPC_HapticsController to various gameplay systems and events.
- Provided accessibility integration options and testing checklist for haptic functionality.
2026-05-22 17:16:34 +03:00
Lefteris Notas
7c2e8df6b1 docs: update README and UE5_Modular_Game_Framework.md to reflect changes in Blueprint systems and C++ class counts; add details for Planar Capture System 2026-05-22 16:29:32 +03:00
Lefteris Notas
5c08c929b5 refactor: rename ASS_PlanarCaptureManager to USS_PlanarCaptureManager for consistency; update documentation across multiple files 2026-05-22 16:23:20 +03:00
Lefteris Notas
321287253b refactor: remove unnecessary Super::Tick call in USS_PlanarCaptureManager::Tick method 2026-05-22 16:10:22 +03:00
Lefteris Notas
9fd679fd5b refactor: update show flag settings in BPC_PlanarCapture for UE5.7 compatibility; enhance SS_PlanarCaptureManager with FTickableGameObject interface 2026-05-22 16:02:01 +03:00
Lefteris Notas
d16c661022 refactor: rename ASS_PlanarCaptureManager to USS_PlanarCaptureManager for consistency 2026-05-22 15:52:13 +03:00
Lefteris Notas
0a2d08b2ad Add Planar Capture System implementation checklist and developer reference
- Created a comprehensive implementation checklist for the Planar Capture System (Systems 136-147) detailing tasks across multiple phases including C++ core, material foundation, Blueprint actors, data assets, integration, and performance testing.
- Added a developer reference document outlining the architecture, data flow, state machine, budget enforcement, render target pooling, horror features, integration points, multiplayer networking, performance characteristics, debugging methods, and build order for the capture systems.
- Introduced examples of capture surface usage in the Project Void horror game, including specific implementations for mirrors, monitors, portals, and fake windows, along with a checklist for integration tasks.
2026-05-22 15:36:08 +03:00
Lefteris Notas
6b6c702dd7 docs: Add initial README.md with comprehensive overview of UE5 Modular Game Framework and its features 2026-05-21 22:43:05 +03:00
Lefteris Notas
318d0d4317 docs: Add comprehensive summary and documentation for "Project Void" game prototype, detailing all assets and systems 2026-05-21 22:33:20 +03:00
Lefteris Notas
040db37720 Add UI Overrides and Weapons Index documentation for Project Void
- Created ui-overrides.md detailing game-specific Widget Blueprint overrides, including purpose, widget index, visual styling, and accessibility requirements.
- Established weapons-index.md outlining all held weapon actors, including their components, logic, and comparisons for gameplay mechanics.
2026-05-21 22:27:57 +03:00
Lefteris Notas
c515920eea refactor: remove clean slate refactor checklist and update documentation structure 2026-05-21 21:32:30 +03:00
Lefteris Notas
5b7d652505 docs: Remove outdated audit request and blueprint summary documentation 2026-05-21 21:31:51 +03:00
Lefteris Notas
bcbfcdf167 docs: Update C++ integration guide and blueprint specifications for various systems, adding detailed setup instructions and clarifying implementation statuses 2026-05-21 21:29:03 +03:00
Lefteris Notas
0852386168 docs: Add detailed item examples for Keycard, MedKit, and Pistol, including step-by-step setup instructions 2026-05-21 20:56:25 +03:00
Lefteris Notas
44aca98885 docs: Update item pickup setup instructions and add game examples for Blueprint walkthroughs 2026-05-21 20:45:44 +03:00
Lefteris Notas
7e876e4f0c docs: Update BP_ItemPickup setup instructions and correct C++ references 2026-05-21 20:08:40 +03:00
Lefteris Notas
ccd1872e59 Update documentation for player, inventory, and weapon systems
- Added C++ status updates for player systems (02-player-systems.md) indicating the implementation status of systems 08-11 and their relation to BPC_StateManager.
- Enhanced inventory systems documentation (04-inventory-systems.md) with C++ status for BPC_InventorySystem and DA_ItemData, clarifying their implementation details.
- Updated weapons systems documentation (08-weapons-systems.md) to reflect the C++ implementation status of BPC_DamageReceptionSystem and stubs for hit reaction and shield defense systems.
2026-05-21 18:41:43 +03:00
Lefteris Notas
8bb162eda2 Add Project Prototype Guide for PG_Framework Testing Setup
- Introduced a comprehensive guide for creating a minimal playable prototype in UE5.
- Detailed steps for project creation, C++ module integration, gameplay tags setup, and player pawn assembly.
- Included instructions for input system setup, state management wiring, and test level setup.
- Provided a verification checklist and troubleshooting section to assist developers.
- Documented next steps for expanding the prototype with additional systems.
2026-05-21 18:03:15 +03:00
Lefteris Notas
cd0ebf2233 refactor: Update API macro from FRAMEWORK_API to PG_FRAMEWORK_API across multiple headers 2026-05-21 17:15:25 +03:00
Lefteris Notas
6132571f8d fix: Update C++ path references to PG_Framework in documentation 2026-05-21 17:13:27 +03:00
Lefteris Notas
4ae2137179 refactor: Remove Framework module build and implementation files 2026-05-21 14:50:21 +03:00
Lefteris Notas
f986343325 Add core gameplay systems and data assets for player mechanics
- Implemented DA_EquipmentConfig for managing equipment resistances, durability, and weight.
- Created DA_ItemData to serve as a base item data asset with various item types and properties.
- Introduced BPC_HealthSystem for managing player health and death events.
- Added BPC_MovementStateSystem to handle player movement modes with event delegation.
- Developed BPC_StaminaSystem to track player stamina and exhaustion states.
- Established BPC_StateManager as a central authority for managing player action states and gating.
- Created BPC_StressSystem to monitor and respond to player stress levels.
- Implemented PC_CoreController and PS_CorePlayerState for player controller and state management.
- Developed SS_SaveManager for save/load functionality with slot management and serialization.
- Introduced DA_StateGatingTable for defining action gating rules based on gameplay tags.
- Added BPC_DamageReceptionSystem to process incoming damage and apply resistance calculations.
- Implemented BPC_HitReactionSystem for managing hit reactions based on damage received.
- Created BPC_ShieldDefenseSystem to manage shield health and blocking mechanics.
- Added PG_FrameworkEditor.Target.cs for editor build configuration.
2026-05-21 14:38:30 +03:00
Lefteris Notas
a145ae9373 refactor: Mark classes as Blueprintable for enhanced usability in Blueprints 2026-05-21 14:03:13 +03:00
Lefteris Notas
d982a6367d refactor: Simplify GetTagDisplayName by removing unnecessary checks and directly returning tag name 2026-05-21 13:43:08 +03:00
Lefteris Notas
108173b87b refactor: Simplify tag display name retrieval and enhance null checks in utility functions 2026-05-21 13:36:53 +03:00
Lefteris Notas
d100a097f5 refactor: Update log category names for consistency across game framework components 2026-05-21 13:30:59 +03:00
Lefteris Notas
3da9fd7493 refactor: Remove unused player controller and state class declarations in GM_CoreGameMode
docs: Update C++ Integration Guide for Blueprint child setup instructions
2026-05-21 13:22:43 +03:00
Lefteris Notas
9ee0a65630 feat: Implement core gameplay systems and data assets for health, stamina, stress, movement, hit reactions, and shield defense 2026-05-21 13:16:43 +03:00
Lefteris Notas
0b1128d209 Refactor code structure for improved readability and maintainability 2026-05-20 22:52:32 +03:00
Lefteris Notas
3ca87a7893 Add asset creation sheets for various systems and UI components
- Created Save/Load & Death Loop asset sheet detailing checkpoint, death handling, and respawn systems.
- Added UI widget creation sheets for pause menu, settings menu, inventory, journal viewer, objective display, notification toast, screen effect controller, accessibility UI, diegetic HUD frame, menu flow controller, and credits screen.
- Implemented HUD controller and interaction prompt display assets with detailed wiring instructions.
- Established narrative systems asset sheet including components for narrative state, objectives, dialogue playback, and branching consequences.
- Developed weapons, AI, and adaptive systems asset creation sheet outlining weapon base, enemy AI components, and adaptive gameplay mechanics.
- Compiled meta, settings, and polish asset creation sheet covering progress tracking, achievement systems, accessibility settings, and various polish components.
- Defined input actions and mapping contexts for enhanced input system, including gameplay actions and context priorities.
- Created state management assets including enums, structs, data asset, and state manager component for action gating and state transitions.
2026-05-20 15:34:06 +03:00
Lefteris Notas
4a7c871f29 Update Developer Reference and add C++ Integration Guide
- Updated version in INDEX.md from 1.3 to 1.4, reflecting new files and C++ systems migrated.
- Added cpp-integration-guide.md detailing setup and usage for 12 C++ classes.
- Introduced cpp-blueprint-status.md to track the status of all 135 systems, including C++ and Blueprint specifications.
2026-05-20 15:16:32 +03:00
Lefteris Notas
f6c4f44827 feat: Add Enhanced Input Manager for context management and key rebinding
- Implemented USS_EnhancedInputManager to manage input contexts with priority.
- Added methods for pushing, popping, and querying input contexts.
- Integrated input mode switching and key rebinding functionality.

feat: Introduce Inventory System Component for item management

- Created UBPC_InventorySystem to handle inventory operations such as adding, removing, and sorting items.
- Implemented weight management and slot organization features.
- Added event dispatchers for inventory changes.

feat: Develop Item Data Asset for item definitions

- Established UDA_ItemData as a base class for all items, encapsulating properties like type, weight, and stack limits.
- Included conditional sub-data structures for equipment, consumables, and inspect data.

feat: Create State Manager Component for player state management

- Developed UBPC_StateManager to manage player action states and overlays.
- Implemented gating logic for action requests and vital sign tracking.

feat: Implement Save Manager for game state persistence

- Introduced USS_SaveManager for handling save/load operations and slot management.
- Utilized FArchive for efficient binary serialization.

feat: Implement Damage Reception System for combat mechanics

- Created UBPC_DamageReceptionSystem to process incoming damage and apply resistance calculations.
- Added event dispatchers for damage reception and hit reactions.
2026-05-20 15:04:17 +03:00
Lefteris Notas
fee12b115f Refactor GameplayTag documentation and implementation
- Updated references from GI_GameTagRegistry to DA_GameTagRegistry in architecture overview and implementation patterns documentation.
- Added new Blueprint specification for GI_StarterGameInstance, detailing its purpose, configuration, and integration pattern.
- Introduced DA_GameTagRegistry Blueprint specification, centralizing GameplayTag management and providing functions for tag validation and logging.
- Created documentation for the Starter GameInstance, outlining its role in the project setup and how other systems can integrate with it.
2026-05-20 14:31:52 +03:00
Lefteris Notas
3023ad3555 fix: Correct function output type in GI_GameTagRegistry documentation 2026-05-20 14:01:43 +03:00
Lefteris Notas
1d699e54fa feat: Add Project Setup & Migration Guide and update Developer Reference Index 2026-05-19 19:34:02 +03:00
Lefteris Notas
209f24a0f8 Refactor Gameplay Tags: Split DT_ProjectTags.csv into 11 per-category Data Tables
- Removed DT_ProjectTags.csv and migrated tags into separate Data Tables:
  - DT_Tags_Player.csv (35 tags)
  - DT_Tags_Interaction.csv (37 tags)
  - DT_Tags_Item.csv (28 tags)
  - DT_Tags_Narrative.csv (55 tags)
  - DT_Tags_AI.csv (24 tags)
  - DT_Tags_Save.csv (25 tags)
  - DT_Tags_Environment.csv (35 tags)
  - DT_Tags_Combat.csv (28 tags)
  - DT_Tags_State.csv (43 tags)
  - DT_Tags_Audio.csv (29 tags)
  - DT_Tags_Achievement.csv (22 tags)

- Updated INDEX.md to reflect new structure and deprecated DT_ProjectTags.csv.
- Enhanced documentation in 01-core-foundation.md regarding tag-driven architecture.
2026-05-19 19:20:40 +03:00
Lefteris Notas
bec6cb715e Enhance narrative systems with detailed implementation guides and data-driven structures
- Updated BPC_NarrativeStateSystem with a comprehensive manual implementation guide, including class setup, variable initialization, and function breakdowns.
- Expanded BPC_ObjectiveSystem documentation to include a manual implementation guide and detailed function descriptions.
- Added a manual implementation guide for BPC_DialoguePlaybackSystem, outlining class setup and function nodes.
- Introduced a manual implementation guide for BPC_DialogueChoiceSystem, detailing choice presentation and selection processes.
- Enhanced BPC_BranchingConsequenceSystem documentation with a manual implementation guide for consequence evaluation.
- Updated BPC_TrialScenarioSystem with a manual implementation guide for scenario management.
- Expanded BPC_LoreUnlockSystem documentation to include a manual implementation guide for lore entry management.
- Added a manual implementation guide for BP_NarrativeTriggerVolume, detailing trigger volume setup and action execution.
- Enhanced BPC_EndingAccumulator documentation with a manual implementation guide for ending evaluation.
- Updated BPC_HitReactionSystem with a manual implementation guide for hit reaction management.
- Added a manual implementation guide for BPC_RecoilSystem, detailing recoil application and recovery processes.
- Introduced DT_ProjectTags.csv to define gameplay tags for various systems, enhancing data-driven design capabilities.
2026-05-19 18:48:37 +03:00
Lefteris Notas
eeb1bf82c9 feat: Enhance interaction and inventory systems with new components and functionality
- Added BPC_UsableWorldObjectSystem for handling various interactable world objects with detailed manual implementation guide.
- Introduced BPC_ActiveItemSystem to manage quick slots and active item usage, including cycling and selection logic.
- Implemented BPC_DocumentArchiveSystem for managing collectible documents with read tracking and categorization.
- Developed BPC_JournalSystem for narrative entries with auto-adding features based on gameplay events.
- Created BPC_KeyItemSystem for key management with consumable and persistent key support.
- Enhanced BPC_FirearmSystem for ranged weapon mechanics, including hitscan and projectile firing.
- Updated BPC_MeleeSystem for melee combat with combo and blocking mechanics.
- Established BPC_ReloadSystem for managing weapon reloading processes, including partial reloads and state management.
2026-05-19 18:37:42 +03:00
Lefteris Notas
f272257cb3 fix: Update data asset documentation for clarity and consistency in type definitions 2026-05-19 18:14:11 +03:00
219 changed files with 33217 additions and 1630 deletions

16
.gitattributes vendored Normal file
View File

@@ -0,0 +1,16 @@
# Unreal binary assets
*.uasset filter=lfs diff=lfs merge=lfs -text
*.umap filter=lfs diff=lfs merge=lfs -text
# Common DCC / source asset formats you may want in LFS too
*.fbx filter=lfs diff=lfs merge=lfs -text
*.blend filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
*.exr filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text

30
.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Unreal generated folders
Binaries/
DerivedDataCache/
Intermediate/
Saved/
# Visual Studio / Rider / IDE
.vs/
.vscode/
.idea/
*.VC.db
*.VC.opendb
*.sln
*.suo
*.opensdf
*.sdf
# OS junk
Thumbs.db
.DS_Store
# Local AI / tool folders
.kilo/
# Logs
*.log
# Keep core Unreal project files
!*.uproject
!*.uplugin

View File

@@ -2,5 +2,18 @@
"$schema": "https://app.kilo.ai/config.json",
"indexing": {
"enabled": true
},
"mcp": {
"context7": {
"type": "local",
"command": [
"npx",
"-y",
"@upstash/context7-mcp"
],
"environment": {
"DEFAULT_MINIMUM_TOKENS": "{{DEFAULT_MINIMUM_TOKENS}}"
}
}
}
}
}

View File

@@ -44,6 +44,18 @@ Content/
Input/ # Enhanced Input assets (NEW)
Actions/ # IA_* Input Action assets
Contexts/ # IMC_* Input Mapping Context assets
DataTables/ # Gameplay Tag Data Tables (NEW — 11 per-category CSV files)
DT_Tags_Player.csv
DT_Tags_Interaction.csv
DT_Tags_Item.csv
DT_Tags_Narrative.csv
DT_Tags_AI.csv
DT_Tags_Save.csv
DT_Tags_Environment.csv
DT_Tags_Combat.csv
DT_Tags_State.csv
DT_Tags_Audio.csv
DT_Tags_Achievement.csv
Achievements/
Settings/
State/ # State Management assets (NEW)
@@ -56,6 +68,22 @@ Content/
S_ActionPermissionResult.uasset # Permission result struct
DA_StateGatingTable.uasset # All gating rules (37 action rules)
BPC_StateManager.uasset # Central state authority component
Capture/ # Planar Capture assets (NEW — Phase 17)
BP_Mirror.uasset # Standard mirror surface
BP_Portal.uasset # Portal surface with linked target
BP_Monitor.uasset # Security camera monitor
BP_HorrorMirror.uasset # Horror mirror with wrong reflection
BP_FakeWindow.uasset # Architectural fake window
M_CaptureSurface_Master.uasset # Master unlit material (9-layer)
MPC_CaptureSurface.uasset # Global MPC (10 scalar params)
DA_PlanarCaptureProfile.uasset # Per-surface capture config
MI_Mirror_Clean.uasset # Material instances
MI_Mirror_Dirty.uasset
MI_Mirror_Steam.uasset
MI_Mirror_Horror.uasset
MI_Portal_Standard.uasset
MI_Monitor_Security.uasset
MI_FakeWindow_Interior.uasset
docs/
blueprints/
@@ -71,17 +99,19 @@ docs/
07-narrative/ # Narrative, Dialogue, Objective & Choice (11 files, 58-68)
08-weapons/ # Weapon, Equipment & Damage (11 files, 69-79)
09-ai/ # AI, Perception & Encounters (9 files, 80-88)
10-adaptive/ # Adaptive Environment, Atmosphere & Scare (15 files, 89-101, 132-133)
11-meta/ # Achievements, Progression & Meta (2 files, 102-103)
12-settings/ # Settings, Accessibility & Platform (2 files, 104-105)
13-polish/ # Tutorial, Loading, Credits, Debug (9 files, 106-114)
14-data-assets/ # Data Asset definitions (16 files, 115-127, 129, 134-135)
15-input/ # Enhanced Input System (1 file, 128)
16-state/ # State Management (2 files, 130-131)
10-adaptive/ # Adaptive Environment, Atmosphere & Scare (15 files, 89-101, 132-133)
11-meta/ # Achievements, Progression & Meta (2 files, 102-103)
12-settings/ # Settings, Accessibility & Platform (2 files, 104-105)
13-polish/ # Tutorial, Loading, Credits, Debug (9 files, 106-114)
14-data-assets/ # Data Asset definitions (16 files, 115-127, 129, 134-135)
15-input/ # Enhanced Input System (1 file, 128)
16-state/ # State Management (2 files, 130-131)
17-capture/ # Planar Capture System (12 files, 136-147)
developer/ # Developer Reference Docs (NEW)
INDEX.md # Master developer docs index (11 docs)
INDEX.md # Master developer docs index (12 docs)
architecture-overview.md # Framework-wide architecture walkthrough
implementation-patterns.md # Common UE5 Blueprint patterns used
project-setup-migration.md # Project setup & migration guide (NEW — Project Settings, plugins, init sequence)
01-core-foundation.md # Foundation systems explained
02-player-systems.md # Player state & embodiment explained
03-interaction-systems.md # Interaction & world manipulation explained
@@ -93,16 +123,17 @@ docs/
09-ai-systems.md # AI, perception & encounters explained
10-adaptive-systems.md # Adaptive environment & atmosphere explained
11-16-systems.md # Meta, Settings, Polish, Data Assets, Input, State explained
architecture/ # Architecture & Design Documents (8 files)
bpc-statemanager.md # BPC_StateManager full spec + Chooser Table Audit
metasounds-audio-system.md # MetaSounds audio architecture (mix buses, room zones, settings)
animation-catalog.md # Animation requirements for all 135 systems
sound-catalog.md # Sound/audio requirements for all 135 systems
enhanced-input-system.md # Enhanced Input System architecture
hud-overview.md # HUD system architecture — 14 widgets, wiring, integration points
multiplayer-networking.md # Multiplayer networking architecture — authority model, RPCs, prediction
blueprint-limitations-workarounds.md # UE5 C++-only functions + 100% Blueprint workarounds (NEW)
CLEAN_SLATE_PLAN.md # Clean slate refactoring plan
architecture/ # Architecture & Design Documents (9 files)
bpc-statemanager.md # BPC_StateManager full spec + Chooser Table Audit
metasounds-audio-system.md # MetaSounds audio architecture (mix buses, room zones, settings)
animation-catalog.md # Animation requirements for all 135 systems
sound-catalog.md # Sound/audio requirements for all 135 systems
enhanced-input-system.md # Enhanced Input System architecture
hud-overview.md # HUD system architecture — 14 widgets, wiring, integration points
multiplayer-networking.md # Multiplayer networking architecture — authority model, RPCs, prediction
blueprint-limitations-workarounds.md # UE5 C++-only functions + 100% Blueprint workarounds (NEW)
planar-capture-system.md # Planar Capture System — mirrors, portals, monitors, horror surfaces (Phase 17)
CLEAN_SLATE_PLAN.md # Clean slate refactoring plan
reports/ # Condensed audit reports
blueprint_condensed_summary.md # ASK agent audit of all 129 files
checklists/ # Implementation checklists
@@ -110,7 +141,7 @@ docs/
enhanced-input-system.md
bpc-statemanager.md # NEW — State Manager implementation checklist
```
**Total: 135 numbered Blueprint files + 5 enums + 5 Data Assets + TEMPLATE.md + AUDIT_REPORT.md + INDEX.md + 11 developer docs + 8 architecture docs + 1 audit report = 166 files in 18 directory groups**
**Total: 149 numbered Blueprint files + 2 supplementary + 5 enums + 5 Data Assets + 11 Data Table CSVs + TEMPLATE.md + AUDIT_REPORT.md + INDEX.md + 13 developer docs + 9 architecture docs + 1 audit report + 1 haptics game example = 196 files in 20 directory groups**
## Naming Conventions
| Prefix | Type |
@@ -150,7 +181,7 @@ docs/
15. **Server-Authoritative Replication** — All state mutations gated by `HasAuthority()`. Clients request via `Server_` RPCs; servers validate and replicate via `RepNotify`. See [`multiplayer-networking.md`](docs/architecture/multiplayer-networking.md).
## Build Order (Dependency-Safe)
- **Phase 0:** Foundation + Input (01-core, 15-input — 8 systems)
- **Phase 0:** Foundation + Input (01-core + 15-input — 9 systems — includes BPC_PlatformServiceAbstraction 150)
- **Phase 1:** Player Core (02-player, 8 systems)
- **Phase 2:** Interaction (03-interaction, 8 systems)
- **Phase 3:** Inventory (04-inventory, 11 systems)
@@ -162,11 +193,14 @@ docs/
- **Phase 9:** Adaptive & Atmosphere (10-adaptive, 15 systems)
- **Phase 10:** Data Assets (14-data-assets, 16 systems)
- **Phase 11:** Meta/Progression (11-meta, 2 systems)
- **Phase 12:** Settings/Platform (12-settings, 2 systems)
- **Phase 12:** Settings/Platform (12-settings, 5 systems — includes BPC_HapticsController 148, BPC_RenderPipelineManager 149)
- **Phase 13:** Polish (13-polish, 9 systems)
- **Phase 14:** State Management (BPC_StateManager + 4 enums + 3 structs + DA_StateGatingTable — 130, 131)
- **Phase 15:** MetaSounds Audio (SS_AudioManager + BP_RoomAudioZone + DA_AudioSettings + DA_RoomAcousticPreset — 132-135)
- **Phase 16:** Multiplayer Networking — All systems updated with `HasAuthority()` gates, `Server_` RPCs, `RepNotify` handlers, and client prediction patterns. See [`docs/architecture/multiplayer-networking.md`](docs/architecture/multiplayer-networking.md).
- **Phase 17:** Planar Capture System — Mirrors, portals, monitors, horror surfaces. C++ core (136-138) + Blueprint actors (139-143) + Material/MPC (144-145) + Data Assets (146-147). See [`docs/architecture/planar-capture-system.md`](docs/architecture/planar-capture-system.md).
- **Phase 18:** Haptics Controller — BPC_HapticsController (148) for GameplayTag-driven force feedback, DualSense adaptive triggers, heartbeat pulse. See [`docs/blueprints/12-settings/148_BPC_HapticsController.md`](docs/blueprints/12-settings/148_BPC_HapticsController.md).
- **Phase 19:** Render Pipeline Manager — BPC_RenderPipelineManager (149) + DA_RenderPipelineProfile for per-platform quality presets, Lumen/Baked switching, upscaling (DLSS/FSR/PSSR), Nanite/LOD. Planar capture system integration. See [`docs/blueprints/12-settings/149_BPC_RenderPipelineManager.md`](docs/blueprints/12-settings/149_BPC_RenderPipelineManager.md) and [`docs/developer/platform-render-profiles.md`](docs/developer/platform-render-profiles.md).
## Key Communication Rules
1. Gameplay Tags + Subsystem lookup (global, decoupled)
@@ -222,4 +256,6 @@ No PR is accepted without these files being current. This ensures the animator,
- **Phase 8-13:** COMPLETE AUDIT_REPORT updated, CONTEXT.md updated, verification, final commit
- **Phase 14-15:** COMPLETE State Management (130-131) + MetaSounds Audio (132-135) blueprint specs created
- **Phase 16:** IN PROGRESS Multiplayer Networking: all 135 blueprint specs being updated with replication stubs, RPC definitions, and server-authoritative patterns. Developer docs updated. Architecture doc created at [`docs/architecture/multiplayer-networking.md`](docs/architecture/multiplayer-networking.md)
- **Remaining:** Cross-reference pass across all 135 files; deprecated BPC_AudioAtmosphereController (95) references to update
- **Phase 18:** COMPLETE Haptics Controller (148) blueprint spec created with full Manual Implementation Guide, DA_HapticProfile (121) enhanced, game example (`docs/game/haptics-example.md`) created. All developer docs and indexes updated.
- **Phase 19:** COMPLETE Render Pipeline Manager (149) + DA_RenderPipelineProfile created. Quality preset system with Lumen/Baked switching, per-platform profiles (PS5/PS4/Xbox/Switch/PC/SteamDeck), upscaling integration (DLSS/FSR/PSSR/TSR). Platform render profiles developer guide created. PlanarCapture system updated for pipeline compatibility. All docs updated.
- **Remaining:** Cross-reference pass across all 149 files; deprecated BPC_AudioAtmosphereController (95) references to update; Planar Capture System (Phase 17) C++ written, blueprint specs created, pending UE5 editor compile and BP child creation

153
README.md Normal file
View File

@@ -0,0 +1,153 @@
# UE5 Modular Game Framework
**A complete Blueprint architecture for first-person narrative horror games — and beyond.**
This is a production-ready, modular game framework for Unreal Engine 5.55.7. It provides 147 self-contained Blueprint systems across 17 categories, plus a fully documented example game prototype ("Project Void"). Built Blueprint-first with 27 supporting C++ classes for performance-critical paths.
---
## What This Framework Covers
### Foundation
Game Instance kernel, Core GameMode, GameState, PlayerState, Function Library, Macro Library, GameplayTag registry (334 tags across 11 categories), and a unified C++/Blueprint interface library.
### Player Simulation
Health & damage resistance, Stamina with exhaustion states, Psychological Stress with 5 tiers (calm → catatonic), GASP-integrated Movement & Posture, Hiding & peek system, First-person Embodiment with arm IK, Camera state layers (FOV/offset per action), and Player Metrics tracking (accuracy, playstyle).
### Interaction & World
Raycast-based Interaction Detector, Physical Doors (open/close/lock/barricade), Puzzle Devices with state machines, Contextual Traversal (vault/mantle/slide via Motion Warping), Physics Drag (grab & throw objects), Usable World Objects (levers, valves, buttons), Diegetic In-World Displays (wristwatch, monitors).
### Inventory & Items
Grid-based Inventory with weight management, World Pickup actors with soft-reference mesh loading, Equipment Slots (weapon, tool, armor), Active Item quick-slots, Consumable use system, Item Combination crafting, Container inventories (chests, desks), Key Item tracking with loss protection, Document/Journal archive, and Collectible tracking with set bonuses.
### Weapons & Combat
Weapon Base with fire/holster/FX, Frame-driven Hitscan Firearms, Pellet-spread Shotguns, Melee combos with hit detection, Ammo pools with magazine management, Tactical & empty Reload, Recoil with recovery pattern, Damage Reception pipeline (resistance → shield → health), Hit Reactions (flinch/stagger/knockdown), Combat Feedback (hit markers, kill confirm), Death Cause tracking.
### AI & Enemies
Enemy Base character, AI Controller with Behavior Tree runner, State Machine (Patrol/Search/Combat/Flee), Alert System (suspicious → alerted → combat), Perception (sight, hearing, damage sense), Memory (last known locations, investigation), Behavior Variant selection (weighted random profiles), Patrol Path splines, Procedural Encounter spawners.
### Narrative & Dialogue
Narrative State Machine with chapter flags, Objective tracking (activate/complete/fail/branch), Dialogue Playback with VO & subtitles, Dialogue Choices with consequence branching, Cutscene Bridge with skip support, Lore Unlock system, Narrative Trigger Volumes, and a multi-factor Ending Accumulator.
### Menus & HUD (14 widgets)
Main Menu, Pause Menu, Settings Menu, Inventory Grid UI, Journal/Document Viewer, Diegetic HUD Frame (health, stress, stamina, compass), Interaction Prompt with hold progress, Notification Toasts, Objective Display, Screen Effect Controller (damage vignette, stress blur), Accessibility UI (subtitles, colorblind), Credits Screen, Splash Screen, Debug Menu. All coordinated by a menu-stack Subsystem.
### Save & Persistence
Save Manager Subsystem (auto-save, manual save, save slots), Checkpoint actors, Death Handling (normal death → respawn OR void-space death), Alt Death Space system, Persistent Corpses, World State Recorder (doors, items, enemies), Player Respawn with state restoration, Run History tracker, and `I_Persistable` interface for any actor.
### Adaptive Environment & Atmosphere
Dynamic Difficulty (metric-based scaling), Atmosphere State Controller (room tone, mood), Procedural Encounter generation, Adaptive Environment Director (room mutations, reality shifts), Light Events (flicker, dim, strobe, blackout), Memory Drift (visual/audio hallucinations tied to stress), Pacing Director (intensity bands: exploration → tension → combat → recovery), Playstyle Classifier (aggressive vs stealthy), Rare Events, Scare Events (anticipation → payoff → recovery).
### MetaSounds Audio
Single-entry-point Audio Manager Subsystem with 4 mix buses (SFX, Ambience, Music, Dialogue), Room Audio Zones that auto-switch reverb/occlusion on overlap, 8 gameplay-driven float parameters (heart rate, stress, fear, tension), and 7 acoustic presets (Outdoor, Small Room, Large Hall, Cave, Cathedral, Void, Padded Cell).
### Enhanced Input
Centralized Input Manager Subsystem with stack-based context switching, 5 priority-layered contexts (Default < Hiding < WristwatchUI < Inspection < UI), 30+ Input Actions, platform profiles (PC, Xbox, PS5), key rebinding with persistence, and input mode coordination with the UI layer.
### Multiplayer Networking
Server-authoritative replication model. All state mutations gated by `HasAuthority()`. ClientServer RPCs for requests, ServerClient `RepNotify` for state sync. Client-side prediction for cosmetic effects. Anti-cheat validation on all server-side state changes.
### State Management
Central State Manager (42 exclusive action states + 18 overlay states). All systems query `IsActionPermitted(Tag)` instead of checking other systems directly. Gating rules configured in a designer-editable Data Asset (no Blueprint changes needed). Force Stack pattern for death/cutscenes/void space. GASP liaison for movementaction state mapping. Vital signal tracking (Normal Critical).
### Planar Capture System (Phase 17)
Unified rendering pipeline for mirrors, portals, monitors, and horror surfaces. C++ core with Blueprint designer-interface. World Subsystem quality budget manager with 5-tier automatic quality scaling (Off Low/256px/4fps Medium/512px/15fps High/1024px/30fps Hero/2048px/60fps). Horror features: wrong-reflection actor swap, delayed frame ring buffer, 10-parameter MPC steam/dirt/darkness/text reveal material system. Render target pool with automatic reuse. Blueprint children: BP_Mirror, BP_Portal, BP_Monitor, BP_HorrorMirror, BP_FakeWindow. Integrates with scare events, audio manager, and state manager.
### Polish & Tooling
Tutorial system with contextual triggers, Loading screen with tips & progress, Credits screen, Analytics tracker (opt-in), Developer Cheat Manager (god mode, noclip, teleport, give item), Error Handler with crash recovery, FPS counter, Debug Menu (state viewer, AI debug, performance, audio).
---
## Example Game Prototype: "Project Void"
A complete psychological horror FPS prototype demonstrating every system. Set in an abandoned asylum. All game content lives in `Content/Game/` never modifying `Content/Framework/`.
**Features built with this framework:**
- 11-level scene flow (Splash Menu Asylum Entry/WardA/WardB/Basement/WardensOffice Void Space 3 Endings Credits)
- Full player character with 27 components wired together
- 18 pickup items (weapons, tools, consumables, keys, documents, collectibles)
- 4 held weapons (pistol, shotgun, flashlight, crowbar)
- 3 enemy types with full AI (Patient, Orderly, Void Shade)
- 4 main objectives + 3 side objectives with branching narrative
- 3 endings determined by player choices, collectibles, sanity, and playstyle
- 6 atmosphere profiles with room audio zones
- 6 scare events with anticipation/payoff/recovery cycles
- Void space alternate-reality death system
- 8 planar capture surfaces (mirrors, horror mirrors, security monitors, void portals, fake windows) with automatic quality scaling
- 18 pickup items (weapons, tools, consumables, keys, documents, collectibles)
- 4 held weapons (pistol, shotgun, flashlight, crowbar)
- 3 enemy types with full AI (Patient, Orderly, Void Shade)
- 4 main objectives + 3 side objectives with branching narrative
- 3 endings determined by player choices, collectibles, sanity, and playstyle
- 6 atmosphere profiles with room audio zones
- 6 scare events with anticipation/payoff/recovery cycles
- Void space alternate-reality death system
---
## Where to Find Things
| You want to... | Go here |
|---------------|---------|
| **Understand the whole framework** | [`UE5_Modular_Game_Framework.md`](UE5_Modular_Game_Framework.md) the full design document |
| **See every Blueprint spec** | [`docs/blueprints/INDEX.md`](docs/blueprints/INDEX.md) 135 system specs with wiring guides |
| **Learn the architecture** | [`docs/developer/architecture-overview.md`](docs/developer/architecture-overview.md) layers, communication patterns |
| **Set up a project from scratch** | [`docs/developer/project-setup-migration.md`](docs/developer/project-setup-migration.md) |
| **Build the example game** | [`docs/game/GAMEINDEX.md`](docs/game/GAMEINDEX.md) 170+ assets, 21-phase build order |
| **Read item build walkthroughs** | [`docs/game/item-flashlight.md`](docs/game/item-flashlight.md) step-by-step Blueprint wiring |
| **Check animation requirements** | [`docs/architecture/animation-catalog.md`](docs/architecture/animation-catalog.md) |
| **Check sound/audio triggers** | [`docs/architecture/sound-catalog.md`](docs/architecture/sound-catalog.md) |
| **Understand input system** | [`docs/architecture/enhanced-input-system.md`](docs/architecture/enhanced-input-system.md) |
| **Understand state management** | [`docs/architecture/bpc-statemanager.md`](docs/architecture/bpc-statemanager.md) |
| **Understand multiplayer** | [`docs/architecture/multiplayer-networking.md`](docs/architecture/multiplayer-networking.md) |
| **Find Blueprint-only workarounds** | [`docs/architecture/blueprint-limitations-workarounds.md`](docs/architecture/blueprint-limitations-workarounds.md) |
| **See the HUD architecture** | [`docs/architecture/hud-overview.md`](docs/architecture/hud-overview.md) |
| **See the audio architecture** | [`docs/architecture/metasounds-audio-system.md`](docs/architecture/metasounds-audio-system.md) |
| **See the planar capture system** | [`docs/architecture/planar-capture-system.md`](docs/architecture/planar-capture-system.md) |
| **See C++/BP implementation status** | [`docs/checklists/cpp-blueprint-status.md`](docs/checklists/cpp-blueprint-status.md) |
---
## Tech Stack
- **Engine:** Unreal Engine 5.55.7
- **Scripting:** Blueprint-Only (with 27 C++ support classes)
- **Locomotion:** GASP (Ground Animation Strafing Platform) read-only, extended via notifies
- **Input:** Enhanced Input System 30+ Input Actions, stack-based context switching
- **Assets:** `UPrimaryDataAsset` for all content definitions
- **State:** Central `BPC_StateManager` 42 exclusive states, 18 overlay states, vital signals
- **Animation:** GASP Motion Matching + 14 animation notifies
- **Audio:** MetaSounds 4 mix buses, room acoustic zones, gameplay-driven float parameters
- **Networking:** Server-authoritative with client prediction
---
## By the Numbers
| What | How Many |
|------|:--------:|
| Blueprint system specs | 147 |
| C++ classes | 27 |
| Widget Blueprints | 14 |
| Blueprint Components | 80 |
| Blueprint Actors | 16 |
| Data Assets | 19 |
| Game Instance Subsystems | 7 |
| World Subsystems | 1 |
| Materials | 1 |
| Material Instances | 7 |
| Material Parameter Collections | 1 |
| Interfaces | 3 |
| Gameplay Tags | 334 |
| Input Actions | 30+ |
| Animation Notifies | 14 |
| Architecture docs | 9 |
| Developer docs | 13 |
| Game prototype docs | 22 |
| Example game assets | 170+ |
---
---
*UE5 Modular Game Framework — Build faster. Stay modular. Never hardcode.*

View File

@@ -0,0 +1,15 @@
// // Copyright Ngonart OU. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class PG_FrameworkTarget : TargetRules
{
public PG_FrameworkTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V6;
ExtraModuleNames.AddRange( new string[] { "PG_Framework" } );
}
}

View File

@@ -0,0 +1,46 @@
// // Copyright Ngonart OU. All Rights Reserved.
using UnrealBuildTool;
public class PG_Framework : ModuleRules
{
public PG_Framework(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"Renderer",
"RenderCore",
"GameplayTags",
"EnhancedInput",
"InputCore",
"UMG",
"Slate",
"SlateCore",
"AIModule",
"NavigationSystem",
"MotionWarping",
"PhysicsCore",
"DeveloperSettings",
"MetasoundEngine",
});
PrivateDependencyModuleNames.AddRange(new string[]
{
"GameplayTasks",
});
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}

View File

@@ -0,0 +1,6 @@
// // Copyright Ngonart OU. All Rights Reserved.
#include "PG_Framework.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, PG_Framework, "PG_Framework" );

View File

@@ -0,0 +1,7 @@
// // Copyright Ngonart OU. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Capture/PlanarCaptureCommon.h"

View File

@@ -0,0 +1,599 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — BPC_PlanarCapture implementation
#include "Capture/BPC_PlanarCapture.h"
#include "Capture/BP_PlanarCaptureActor.h"
#include "Capture/SS_PlanarCaptureManager.h"
#include "Capture/PlanarCaptureCameraUtils.h"
#include "Components/SceneCaptureComponent2D.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "Materials/MaterialParameterCollection.h"
#include "Materials/MaterialParameterCollectionInstance.h"
#include "Kismet/GameplayStatics.h"
UBPC_PlanarCapture::UBPC_PlanarCapture()
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bStartWithTickEnabled = true;
PrimaryComponentTick.TickInterval = 0.0f; // We throttle internally via TimeSinceLastCapture
// Initialize default quality profiles
QualityProfiles.SetNum(4);
// Low — 256px, 4fps
QualityProfiles[0].RenderTargetSize = 256;
QualityProfiles[0].CaptureInterval = 0.25f;
QualityProfiles[0].bEnableShadows = false;
QualityProfiles[0].bEnablePostProcess = false;
QualityProfiles[0].bEnableFog = false;
QualityProfiles[0].bEnableBloom = false;
QualityProfiles[0].bEnableAO = false;
QualityProfiles[0].bEnableLumen = false;
QualityProfiles[0].bEnableMotionBlur = false;
QualityProfiles[0].bEnableClipPlane = false;
// Medium — 512px, 15fps
QualityProfiles[1].RenderTargetSize = 512;
QualityProfiles[1].CaptureInterval = 0.0667f;
QualityProfiles[1].bEnableShadows = true;
QualityProfiles[1].bEnablePostProcess = false;
QualityProfiles[1].bEnableFog = false;
QualityProfiles[1].bEnableBloom = false;
QualityProfiles[1].bEnableAO = false;
QualityProfiles[1].bEnableLumen = false;
QualityProfiles[1].bEnableMotionBlur = false;
QualityProfiles[1].bEnableClipPlane = true;
// High — 1024px, 30fps
QualityProfiles[2].RenderTargetSize = 1024;
QualityProfiles[2].CaptureInterval = 0.0333f;
QualityProfiles[2].bEnableShadows = true;
QualityProfiles[2].bEnablePostProcess = true;
QualityProfiles[2].bEnableFog = true;
QualityProfiles[2].bEnableBloom = false;
QualityProfiles[2].bEnableAO = true;
QualityProfiles[2].bEnableLumen = true;
QualityProfiles[2].bEnableMotionBlur = false;
QualityProfiles[2].bEnableClipPlane = true;
// Hero — 2048px, 60fps
QualityProfiles[3].RenderTargetSize = 2048;
QualityProfiles[3].CaptureInterval = 0.0167f;
QualityProfiles[3].bEnableShadows = true;
QualityProfiles[3].bEnablePostProcess = true;
QualityProfiles[3].bEnableFog = true;
QualityProfiles[3].bEnableBloom = true;
QualityProfiles[3].bEnableAO = true;
QualityProfiles[3].bEnableLumen = true;
QualityProfiles[3].bEnableMotionBlur = true;
QualityProfiles[3].bEnableClipPlane = true;
}
void UBPC_PlanarCapture::BeginPlay()
{
Super::BeginPlay();
CachedOwningActor = Cast<ABP_PlanarCaptureActor>(GetOwner());
if (!CachedOwningActor)
{
UE_LOG(LogTemp, Warning, TEXT("BPC_PlanarCapture: Owner is not a BP_PlanarCaptureActor! Capture disabled."));
SetComponentTickEnabled(false);
return;
}
// Cache manager reference
if (UWorld* World = GetWorld())
{
CachedManager = World->GetSubsystem<USS_PlanarCaptureManager>();
}
ResolveSoftReferences();
}
void UBPC_PlanarCapture::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
ShutdownCapture();
Super::EndPlay(EndPlayReason);
}
void UBPC_PlanarCapture::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!bIsCapturing || !SceneCapture || CurrentQualityTier == EPlanarCaptureQualityTier::Off)
{
return;
}
TimeSinceLastCapture += DeltaTime;
if (TimeSinceLastCapture >= ActiveProfile.CaptureInterval)
{
TimeSinceLastCapture = 0.0f;
// Get viewer camera for transform computation
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!PC || !PC->PlayerCameraManager)
{
return;
}
const FTransform ViewerTransform = FTransform(
PC->PlayerCameraManager->GetCameraRotation(),
PC->PlayerCameraManager->GetCameraLocation()
);
// Compute capture camera position
const FTransform CaptureTransform = ComputeCaptureCameraTransform(ViewerTransform);
SceneCapture->SetWorldTransform(CaptureTransform);
// Capture the scene
SceneCapture->CaptureScene();
// Push MPC parameters
if (CachedOwningActor && CachedOwningActor->SurfaceMPC)
{
PushMPCParameters(CachedOwningActor->SurfaceMPC);
}
OnCaptureRendered.Broadcast();
}
}
// ========================================================================
// Public API
// ========================================================================
EPlanarCaptureInitResult UBPC_PlanarCapture::InitializeCapture()
{
if (!CachedOwningActor)
{
return EPlanarCaptureInitResult::InvalidSurfaceMesh;
}
if (CurrentQualityTier == EPlanarCaptureQualityTier::Off)
{
return EPlanarCaptureInitResult::Success; // Nothing to initialize
}
// Request render target from pool
const int32 ProfileIndex = static_cast<int32>(CurrentQualityTier) - 1; // Skip "Off" (0)
if (ProfileIndex < 0 || ProfileIndex >= QualityProfiles.Num())
{
return EPlanarCaptureInitResult::NoRenderTargetPool;
}
ActiveProfile = QualityProfiles[ProfileIndex];
if (CachedManager)
{
CaptureRenderTarget = CachedManager->RequestRenderTarget(ActiveProfile.RenderTargetSize);
}
if (!CaptureRenderTarget)
{
UE_LOG(LogTemp, Error, TEXT("BPC_PlanarCapture: Failed to allocate render target (%dx%d)"),
ActiveProfile.RenderTargetSize, ActiveProfile.RenderTargetSize);
return EPlanarCaptureInitResult::NoRenderTargetPool;
}
CreateSceneCaptureComponent();
bIsCapturing = true;
OnCaptureInitialized.Broadcast(EPlanarCaptureInitResult::Success);
return EPlanarCaptureInitResult::Success;
}
void UBPC_PlanarCapture::ShutdownCapture()
{
bIsCapturing = false;
if (SceneCapture)
{
SceneCapture->DestroyComponent();
SceneCapture = nullptr;
}
if (CaptureRenderTarget && CachedManager)
{
CachedManager->ReleaseRenderTarget(CaptureRenderTarget);
CaptureRenderTarget = nullptr;
}
// Clean up frame ring buffer
for (UTextureRenderTarget2D* RT : FrameRingBuffer)
{
if (RT && CachedManager)
{
CachedManager->ReleaseRenderTarget(RT);
}
}
FrameRingBuffer.Empty();
}
void UBPC_PlanarCapture::ApplyQualityTier(EPlanarCaptureQualityTier Tier)
{
const EPlanarCaptureQualityTier OldTier = CurrentQualityTier;
CurrentQualityTier = Tier;
if (Tier == EPlanarCaptureQualityTier::Off)
{
ShutdownCapture();
OnCaptureQualityChanged.Broadcast(OldTier, Tier);
return;
}
// If transitioning from Off to active, initialize
if (OldTier == EPlanarCaptureQualityTier::Off)
{
InitializeCapture();
}
else
{
// Update active profile and resize render target if needed
const int32 ProfileIndex = static_cast<int32>(Tier) - 1;
if (ProfileIndex >= 0 && ProfileIndex < QualityProfiles.Num())
{
ActiveProfile = QualityProfiles[ProfileIndex];
ApplyShowFlags();
}
}
OnCaptureQualityChanged.Broadcast(OldTier, Tier);
}
void UBPC_PlanarCapture::CaptureNow()
{
if (!SceneCapture)
{
return;
}
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!PC || !PC->PlayerCameraManager)
{
return;
}
const FTransform ViewerTransform = FTransform(
PC->PlayerCameraManager->GetCameraRotation(),
PC->PlayerCameraManager->GetCameraLocation()
);
const FTransform CaptureTransform = ComputeCaptureCameraTransform(ViewerTransform);
SceneCapture->SetWorldTransform(CaptureTransform);
SceneCapture->CaptureScene();
TimeSinceLastCapture = 0.0f;
OnCaptureRendered.Broadcast();
}
void UBPC_PlanarCapture::ActivateHorrorReflection()
{
// Save current ShowOnly list
SavedShowOnlyActors.Empty();
for (const FPlanarCaptureActorListEntry& Entry : ShowOnlyActors)
{
SavedShowOnlyActors.Add(Entry.Actor);
}
// Clear and set wrong reflection actor
ShowOnlyActors.Empty();
if (WrongReflectionActor.IsValid())
{
FPlanarCaptureActorListEntry NewEntry;
NewEntry.Actor = WrongReflectionActor;
NewEntry.bActive = true;
ShowOnlyActors.Add(NewEntry);
}
UpdateActorLists();
}
void UBPC_PlanarCapture::DeactivateHorrorReflection()
{
ShowOnlyActors.Empty();
for (const TSoftObjectPtr<AActor>& SavedActor : SavedShowOnlyActors)
{
FPlanarCaptureActorListEntry NewEntry;
NewEntry.Actor = SavedActor;
NewEntry.bActive = true;
ShowOnlyActors.Add(NewEntry);
}
SavedShowOnlyActors.Empty();
UpdateActorLists();
}
void UBPC_PlanarCapture::PushDelayedFrame()
{
if (ActiveProfile.DelayedFrameCount <= 0 || !CaptureRenderTarget)
{
return;
}
// Ensure ring buffer is properly sized
while (FrameRingBuffer.Num() < ActiveProfile.DelayedFrameCount)
{
UTextureRenderTarget2D* NewRT = nullptr;
if (CachedManager)
{
NewRT = CachedManager->RequestRenderTarget(ActiveProfile.RenderTargetSize);
}
FrameRingBuffer.Add(NewRT);
}
// Copy current render target to ring buffer slot
RingBufferWriteIndex = (RingBufferWriteIndex + 1) % FrameRingBuffer.Num();
// Push the oldest frame to the material as the delayed reflection
if (FrameRingBuffer.IsValidIndex(RingBufferWriteIndex) && FrameRingBuffer[RingBufferWriteIndex])
{
// The material samples the delayed frame via the MPC texture parameter
// This is set in PushMPCParameters
}
}
void UBPC_PlanarCapture::SetScriptedPriority(float Priority)
{
ScriptedPriorityOverride = FMath::Clamp(Priority, 0.0f, 1.0f);
}
void UBPC_PlanarCapture::PushMPCParameters(UMaterialParameterCollection* MPC)
{
if (!MPC)
{
return;
}
UMaterialParameterCollectionInstance* MPCInstance = GetWorld()->GetParameterCollectionInstance(MPC);
if (!MPCInstance)
{
return;
}
// Push standard parameters
// These are used by M_CaptureSurface_Master to drive steam, dirt, horror effects
MPCInstance->SetScalarParameterValue(FName("SteamIntensity"), 0.0f);
MPCInstance->SetScalarParameterValue(FName("DirtOpacity"), 0.0f);
MPCInstance->SetScalarParameterValue(FName("CondensationFlow"), 0.0f);
MPCInstance->SetScalarParameterValue(FName("DistortionAmplitude"), 0.0f);
MPCInstance->SetScalarParameterValue(FName("MirrorDarkness"), 0.0f);
MPCInstance->SetScalarParameterValue(FName("WrongReflectionBlend"), 0.0f);
MPCInstance->SetScalarParameterValue(FName("TextRevealProgress"), 0.0f);
MPCInstance->SetScalarParameterValue(FName("SteamEmissiveIntensity"), 0.0f);
MPCInstance->SetScalarParameterValue(FName("DelayedReflectionBlend"), 0.0f);
MPCInstance->SetScalarParameterValue(FName("SurfaceAge"), 0.0f);
}
// ========================================================================
// Compute
// ========================================================================
FTransform UBPC_PlanarCapture::ComputeCaptureCameraTransform(const FTransform& ViewerCameraTransform) const
{
switch (CaptureMode)
{
case EPlanarCaptureMode::Mirror:
case EPlanarCaptureMode::HorrorMirror:
{
const FTransform SurfaceTransform = CachedOwningActor
? CachedOwningActor->GetActorTransform()
: FTransform::Identity;
return UPlanarCaptureCameraUtils::ComputeMirroredTransform(ViewerCameraTransform, SurfaceTransform);
}
case EPlanarCaptureMode::Portal:
case EPlanarCaptureMode::HorrorPortal:
{
if (LinkedTargetSurface.IsValid())
{
const FTransform SourceTransform = CachedOwningActor
? CachedOwningActor->GetActorTransform()
: FTransform::Identity;
const FTransform TargetTransform = LinkedTargetSurface->GetActorTransform();
return UPlanarCaptureCameraUtils::ComputePortalTransform(
ViewerCameraTransform, SourceTransform, TargetTransform);
}
return ViewerCameraTransform;
}
case EPlanarCaptureMode::Monitor:
{
if (FixedCameraActor.IsValid())
{
return FixedCameraActor->GetActorTransform();
}
return ViewerCameraTransform;
}
case EPlanarCaptureMode::FakeWindow:
{
// Fake windows use a fixed offset from the surface
const FTransform SurfaceTransform = CachedOwningActor
? CachedOwningActor->GetActorTransform()
: FTransform::Identity;
const FVector SurfaceNormal = SurfaceTransform.GetUnitAxis(EAxis::Z);
return FTransform(SurfaceTransform.GetRotation(),
SurfaceTransform.GetLocation() + SurfaceNormal * 200.0f);
}
default:
return ViewerCameraTransform;
}
}
FPlanarCaptureScore UBPC_PlanarCapture::GetCurrentScore() const
{
FPlanarCaptureScore Score;
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!PC || !PC->PlayerCameraManager || !CachedOwningActor)
{
return Score;
}
const FVector ViewerPos = PC->PlayerCameraManager->GetCameraLocation();
const FTransform ViewerTransform = FTransform(
PC->PlayerCameraManager->GetCameraRotation(),
ViewerPos
);
const FVector SurfacePos = CachedOwningActor->GetActorLocation();
const FVector SurfaceNormal = CachedOwningActor->GetActorUpVector();
Score.DistanceToViewer = FVector::Dist(ViewerPos, SurfacePos);
Score.FacingAngle = FVector::DotProduct(
PC->PlayerCameraManager->GetCameraRotation().Vector(),
SurfaceNormal
);
// Compute screen coverage using bounding box
const FBox SurfaceBounds = CachedOwningActor->GetComponentsBoundingBox();
Score.ScreenCoverage = UPlanarCaptureCameraUtils::ComputeScreenCoverage(
SurfaceBounds, ViewerTransform, CaptureFOV, 1920, 1080);
Score.bInFrustum = UPlanarCaptureCameraUtils::IsSurfaceVisibleToViewer(
SurfaceBounds, ViewerTransform, CaptureFOV, 1.777f, 10.0f, MaxViewDistance);
Score.ScriptedPriority = ScriptedPriorityOverride;
Score.CompositeScore = UPlanarCaptureCameraUtils::ComputeCompositeScore(
Score.ScreenCoverage, Score.FacingAngle, Score.DistanceToViewer,
CachedManager ? CachedManager->MaxCaptureDistance : 10000.0f,
Score.ScriptedPriority);
return Score;
}
// ========================================================================
// Internal Methods
// ========================================================================
void UBPC_PlanarCapture::CreateSceneCaptureComponent()
{
if (!GetOwner())
{
return;
}
SceneCapture = NewObject<USceneCaptureComponent2D>(GetOwner(), USceneCaptureComponent2D::StaticClass());
if (!SceneCapture)
{
return;
}
SceneCapture->RegisterComponent();
SceneCapture->AttachToComponent(GetOwner()->GetRootComponent(),
FAttachmentTransformRules::SnapToTargetNotIncludingScale);
SceneCapture->TextureTarget = CaptureRenderTarget;
SceneCapture->FOVAngle = CaptureFOV;
SceneCapture->bCaptureEveryFrame = false; // We control capture timing
SceneCapture->bCaptureOnMovement = false;
// Configure for planar surface capture
SceneCapture->ProjectionType = ECameraProjectionMode::Perspective;
SceneCapture->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_UseShowOnlyList;
ApplyShowFlags();
UpdateActorLists();
}
void UBPC_PlanarCapture::ApplyShowFlags()
{
if (!SceneCapture)
{
return;
}
// Apply show flags based on active quality profile
// UE5.7: FEngineShowFlags members are set directly, not via setter methods
if (!ActiveProfile.bEnableShadows)
{
SceneCapture->ShowFlags.DynamicShadows = false;
SceneCapture->ShowFlags.ContactShadows = false;
}
if (!ActiveProfile.bEnableFog)
{
SceneCapture->ShowFlags.Fog = 0;
}
if (!ActiveProfile.bEnableBloom)
{
SceneCapture->ShowFlags.Bloom = 0;
}
if (!ActiveProfile.bEnableAO)
{
SceneCapture->ShowFlags.AmbientOcclusion = 0;
SceneCapture->ShowFlags.ScreenSpaceAO = 0;
}
if (!ActiveProfile.bEnableMotionBlur)
{
SceneCapture->ShowFlags.MotionBlur = 0;
}
// Lumen is controlled via post-process settings on the capture component
SceneCapture->PostProcessSettings.bOverride_DynamicGlobalIlluminationMethod = true;
SceneCapture->PostProcessSettings.DynamicGlobalIlluminationMethod = ActiveProfile.bEnableLumen
? EDynamicGlobalIlluminationMethod::Lumen
: EDynamicGlobalIlluminationMethod::None;
// Post-process toggle
SceneCapture->PostProcessSettings.bOverride_BloomIntensity = !ActiveProfile.bEnablePostProcess;
SceneCapture->PostProcessBlendWeight = ActiveProfile.bEnablePostProcess ? 1.0f : 0.0f;
// Disable Lumen reflections on the capture itself to prevent double-rendering
SceneCapture->PostProcessSettings.bOverride_ReflectionMethod = true;
SceneCapture->PostProcessSettings.ReflectionMethod = EReflectionMethod::None;
}
void UBPC_PlanarCapture::UpdateActorLists()
{
if (!SceneCapture)
{
return;
}
SceneCapture->ShowOnlyActors.Empty();
SceneCapture->HiddenActors.Empty();
for (const FPlanarCaptureActorListEntry& Entry : ShowOnlyActors)
{
if (Entry.bActive && Entry.Actor.IsValid())
{
SceneCapture->ShowOnlyActors.Add(Entry.Actor.Get());
}
}
for (const FPlanarCaptureActorListEntry& Entry : HiddenActors)
{
if (Entry.bActive && Entry.Actor.IsValid())
{
SceneCapture->HiddenActors.Add(Entry.Actor.Get());
}
}
}
void UBPC_PlanarCapture::ResolveSoftReferences()
{
if (SurfaceMeshComponent.IsValid())
{
// Surface mesh is resolved — ready for capture
}
}
FPlane UBPC_PlanarCapture::GetSurfacePlane() const
{
if (!CachedOwningActor)
{
return FPlane(FVector::ZeroVector, FVector::UpVector);
}
const FVector SurfaceLocation = CachedOwningActor->GetActorLocation();
const FVector SurfaceNormal = CachedOwningActor->GetActorUpVector();
return FPlane(SurfaceLocation, SurfaceNormal);
}

View File

@@ -0,0 +1,241 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — BP_PlanarCaptureActor implementation
#include "Capture/BP_PlanarCaptureActor.h"
#include "Capture/BPC_PlanarCapture.h"
#include "Capture/SS_PlanarCaptureManager.h"
#include "Components/StaticMeshComponent.h"
#include "Components/BoxComponent.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Materials/MaterialParameterCollection.h"
#include "Engine/World.h"
#include "Net/UnrealNetwork.h"
ABP_PlanarCaptureActor::ABP_PlanarCaptureActor()
{
PrimaryActorTick.bCanEverTick = false;
// Create root component
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
// Surface mesh
SurfaceMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("SurfaceMesh"));
SurfaceMesh->SetupAttachment(Root);
SurfaceMesh->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
SurfaceMesh->SetCollisionResponseToAllChannels(ECR_Ignore);
SurfaceMesh->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
// Proximity trigger for quality scoring
ProximityTrigger = CreateDefaultSubobject<UBoxComponent>(TEXT("ProximityTrigger"));
ProximityTrigger->SetupAttachment(Root);
ProximityTrigger->SetBoxExtent(FVector(500.0f, 500.0f, 500.0f));
ProximityTrigger->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
ProximityTrigger->SetCollisionResponseToAllChannels(ECR_Ignore);
ProximityTrigger->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
// Capture component
CaptureComponent = CreateDefaultSubobject<UBPC_PlanarCapture>(TEXT("CaptureComponent"));
// Replication
bReplicates = true;
bAlwaysRelevant = true;
}
void ABP_PlanarCaptureActor::BeginPlay()
{
Super::BeginPlay();
// Bind overlap events
if (ProximityTrigger)
{
ProximityTrigger->OnComponentBeginOverlap.AddDynamic(this, &ABP_PlanarCaptureActor::OnProximityBeginOverlap);
ProximityTrigger->OnComponentEndOverlap.AddDynamic(this, &ABP_PlanarCaptureActor::OnProximityEndOverlap);
}
CreateMaterialInstance();
RegisterWithManager();
if (bStartEnabled)
{
EnableSurface();
}
}
void ABP_PlanarCaptureActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// Unregister from manager
if (UWorld* World = GetWorld())
{
if (USS_PlanarCaptureManager* Manager = World->GetSubsystem<USS_PlanarCaptureManager>())
{
Manager->UnregisterSurface(this);
}
}
Super::EndPlay(EndPlayReason);
}
// ========================================================================
// Public API
// ========================================================================
void ABP_PlanarCaptureActor::EnableSurface()
{
if (bIsActive)
{
return;
}
bIsActive = true;
bRepIsActive = true;
if (CaptureComponent)
{
CaptureComponent->InitializeCapture();
}
}
void ABP_PlanarCaptureActor::DisableSurface()
{
if (!bIsActive)
{
return;
}
bIsActive = false;
bRepIsActive = false;
if (CaptureComponent)
{
CaptureComponent->ShutdownCapture();
}
}
void ABP_PlanarCaptureActor::SetCaptureMode(EPlanarCaptureMode NewMode)
{
if (CaptureComponent)
{
const bool WasCapturing = CaptureComponent->bIsCapturing;
if (WasCapturing)
{
CaptureComponent->ShutdownCapture();
}
CaptureComponent->CaptureMode = NewMode;
if (WasCapturing)
{
CaptureComponent->InitializeCapture();
}
OnModeChanged.Broadcast(NewMode);
}
}
void ABP_PlanarCaptureActor::SetSurfaceMaterial(UMaterialInterface* NewMaterial)
{
if (SurfaceMesh && NewMaterial)
{
SurfaceMaterialInstance = SurfaceMesh->CreateDynamicMaterialInstance(0, NewMaterial);
}
}
void ABP_PlanarCaptureActor::SetSurfaceMPCParameter(FName ParameterName, float Value)
{
UMaterialInstanceDynamic* MID = SurfaceMaterialInstance;
if (!MID)
{
return;
}
MID->SetScalarParameterValue(ParameterName, Value);
}
void ABP_PlanarCaptureActor::DestroySurface()
{
if (!bDestructible)
{
return;
}
DisableSurface();
OnSurfaceDestroyed.Broadcast(this);
// Blueprint child handles visual destruction (particles, sound via SS_AudioManager, etc.)
}
// ========================================================================
// Overlap Events
// ========================================================================
void ABP_PlanarCaptureActor::OnProximityBeginOverlap(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult)
{
// Player entered proximity — notify capture component for quality scoring
if (CaptureComponent && OtherActor && OtherActor->ActorHasTag(FName("Player")))
{
CaptureComponent->SetScriptedPriority(0.3f); // Slight priority boost when player is near
}
}
void ABP_PlanarCaptureActor::OnProximityEndOverlap(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (CaptureComponent && OtherActor && OtherActor->ActorHasTag(FName("Player")))
{
CaptureComponent->SetScriptedPriority(0.0f); // Reset priority
}
}
// ========================================================================
// Replication
// ========================================================================
void ABP_PlanarCaptureActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ABP_PlanarCaptureActor, bRepIsActive);
}
void ABP_PlanarCaptureActor::OnRep_IsActive()
{
if (bRepIsActive && !bIsActive)
{
EnableSurface();
}
else if (!bRepIsActive && bIsActive)
{
DisableSurface();
}
}
// ========================================================================
// Internal
// ========================================================================
void ABP_PlanarCaptureActor::RegisterWithManager()
{
UWorld* World = GetWorld();
if (!World)
{
return;
}
USS_PlanarCaptureManager* Manager = World->GetSubsystem<USS_PlanarCaptureManager>();
if (Manager)
{
Manager->RegisterSurface(this);
}
}
void ABP_PlanarCaptureActor::CreateMaterialInstance()
{
if (SurfaceMesh && SurfaceMesh->GetMaterial(0))
{
SurfaceMaterialInstance = SurfaceMesh->CreateDynamicMaterialInstance(0);
}
}

View File

@@ -0,0 +1,242 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — PlanarCaptureCameraUtils implementation
#include "Capture/PlanarCaptureCameraUtils.h"
#include "Kismet/KismetMathLibrary.h"
FTransform UPlanarCaptureCameraUtils::ComputeMirroredTransform(
const FTransform& ViewerCameraTransform,
const FTransform& MirrorPlaneTransform)
{
// Mirror plane normal (local Z axis of the mirror surface)
const FVector MirrorNormal = MirrorPlaneTransform.GetUnitAxis(EAxis::Z);
const FVector MirrorLocation = MirrorPlaneTransform.GetLocation();
// Reflect viewer position across the mirror plane
const FVector ViewerPos = ViewerCameraTransform.GetLocation();
const FVector ToViewer = ViewerPos - MirrorLocation;
const float DistanceToPlane = FVector::DotProduct(ToViewer, MirrorNormal);
const FVector ReflectedPosition = ViewerPos - 2.0f * DistanceToPlane * MirrorNormal;
// Reflect viewer rotation across the mirror plane
const FVector ViewerForward = ViewerCameraTransform.GetUnitAxis(EAxis::X);
const FVector ReflectedForward = ViewerForward - 2.0f * FVector::DotProduct(ViewerForward, MirrorNormal) * MirrorNormal;
const FVector ViewerUp = ViewerCameraTransform.GetUnitAxis(EAxis::Z);
const FVector ReflectedUp = ViewerUp - 2.0f * FVector::DotProduct(ViewerUp, MirrorNormal) * MirrorNormal;
const FRotator ReflectedRotation = FRotationMatrix::MakeFromXZ(ReflectedForward, ReflectedUp).Rotator();
return FTransform(ReflectedRotation, ReflectedPosition, ViewerCameraTransform.GetScale3D());
}
FTransform UPlanarCaptureCameraUtils::ComputePortalTransform(
const FTransform& ViewerCameraTransform,
const FTransform& SourceSurfaceTransform,
const FTransform& TargetSurfaceTransform)
{
// Compute viewer position relative to source surface
const FVector ViewerPos = ViewerCameraTransform.GetLocation();
const FVector RelativePos = SourceSurfaceTransform.InverseTransformPosition(ViewerPos);
// Compute viewer rotation relative to source surface
const FQuat ViewerRot = ViewerCameraTransform.GetRotation();
const FQuat SourceRotInv = SourceSurfaceTransform.GetRotation().Inverse();
const FQuat RelativeRot = SourceRotInv * ViewerRot;
// Apply relative transform to target surface
const FVector TargetPos = TargetSurfaceTransform.TransformPosition(RelativePos);
const FQuat TargetRot = TargetSurfaceTransform.GetRotation() * RelativeRot;
return FTransform(TargetRot, TargetPos, ViewerCameraTransform.GetScale3D());
}
FMatrix UPlanarCaptureCameraUtils::ComputeObliqueProjectionMatrix(
float FOV,
float AspectRatio,
float NearPlane,
float FarPlane,
const FPlane& ClipPlane,
const FTransform& SurfaceTransform)
{
// Build standard perspective projection matrix
FMatrix ProjectionMatrix;
const float HalfFOVRad = FMath::DegreesToRadians(FOV * 0.5f);
const float YScale = 1.0f / FMath::Tan(HalfFOVRad);
const float XScale = YScale / AspectRatio;
ProjectionMatrix.SetIdentity();
ProjectionMatrix.M[0][0] = XScale;
ProjectionMatrix.M[1][1] = YScale;
ProjectionMatrix.M[2][2] = (NearPlane == FarPlane) ? 0.0f : FarPlane / (FarPlane - NearPlane);
ProjectionMatrix.M[2][3] = 1.0f;
ProjectionMatrix.M[3][2] = (NearPlane == FarPlane) ? NearPlane : -NearPlane * FarPlane / (FarPlane - NearPlane);
ProjectionMatrix.M[3][3] = 0.0f;
// Transform clip plane into view space (inverse of surface transform)
const FMatrix ViewMatrix = SurfaceTransform.ToMatrixNoScale().Inverse();
const FPlane ViewSpaceClipPlane = ClipPlane.TransformBy(ViewMatrix);
// Compute oblique near-plane using the standard technique:
// Calculate the clip-space corner that maximizes the dot product with the plane normal.
// Then modify the third row of the projection matrix to make the near plane pass through the clip plane.
FVector4 ClipPlaneVec(ViewSpaceClipPlane.X, ViewSpaceClipPlane.Y, ViewSpaceClipPlane.Z, ViewSpaceClipPlane.W);
// Find the vertex of the view frustum in clip space that is closest to the plane
FVector4 Q;
Q.X = (FMath::Sign(ClipPlaneVec.X) + ProjectionMatrix.M[0][2]) / ProjectionMatrix.M[0][0];
Q.Y = (FMath::Sign(ClipPlaneVec.Y) + ProjectionMatrix.M[1][2]) / ProjectionMatrix.M[1][1];
Q.Z = 1.0f;
Q.W = (1.0f + ProjectionMatrix.M[2][2]) / ProjectionMatrix.M[3][2];
// Scale the clip plane so its distance from the origin matches the Q vertex
const float DotVal = ClipPlaneVec.X * Q.X + ClipPlaneVec.Y * Q.Y + ClipPlaneVec.Z * Q.Z + ClipPlaneVec.W * Q.W;
const float Scale = 2.0f / DotVal;
ClipPlaneVec *= Scale;
// Replace the third row of the projection matrix with the clip plane
ProjectionMatrix.M[2][0] = ClipPlaneVec.X;
ProjectionMatrix.M[2][1] = ClipPlaneVec.Y;
ProjectionMatrix.M[2][2] = ClipPlaneVec.Z;
ProjectionMatrix.M[2][3] = ClipPlaneVec.W;
return ProjectionMatrix;
}
float UPlanarCaptureCameraUtils::ComputeScreenCoverage(
const FBox& SurfaceBounds,
const FTransform& ViewerTransform,
float ViewerFOV,
int32 ScreenWidth,
int32 ScreenHeight)
{
if (!SurfaceBounds.IsValid || ScreenWidth <= 0 || ScreenHeight <= 0)
{
return 0.0f;
}
const float HalfFOVRad = FMath::DegreesToRadians(ViewerFOV * 0.5f);
const FVector ViewerPos = ViewerTransform.GetLocation();
const FVector ViewerForward = ViewerTransform.GetUnitAxis(EAxis::X);
// Project all 8 corners of the bounding box to screen space
const FVector Corners[8] = {
FVector(SurfaceBounds.Min.X, SurfaceBounds.Min.Y, SurfaceBounds.Min.Z),
FVector(SurfaceBounds.Min.X, SurfaceBounds.Min.Y, SurfaceBounds.Max.Z),
FVector(SurfaceBounds.Min.X, SurfaceBounds.Max.Y, SurfaceBounds.Min.Z),
FVector(SurfaceBounds.Min.X, SurfaceBounds.Max.Y, SurfaceBounds.Max.Z),
FVector(SurfaceBounds.Max.X, SurfaceBounds.Min.Y, SurfaceBounds.Min.Z),
FVector(SurfaceBounds.Max.X, SurfaceBounds.Min.Y, SurfaceBounds.Max.Z),
FVector(SurfaceBounds.Max.X, SurfaceBounds.Max.Y, SurfaceBounds.Min.Z),
FVector(SurfaceBounds.Max.X, SurfaceBounds.Max.Y, SurfaceBounds.Max.Z),
};
float MinScreenX = FLT_MAX, MinScreenY = FLT_MAX;
float MaxScreenX = -FLT_MAX, MaxScreenY = -FLT_MAX;
int32 BehindCameraCount = 0;
for (const FVector& Corner : Corners)
{
const FVector ToCorner = Corner - ViewerPos;
const float DotForward = FVector::DotProduct(ToCorner, ViewerForward);
if (DotForward <= 0.0f)
{
BehindCameraCount++;
continue;
}
const float Distance = ToCorner.Size();
const FVector Right = ViewerTransform.GetUnitAxis(EAxis::Y);
const FVector Up = ViewerTransform.GetUnitAxis(EAxis::Z);
const float DotRight = FVector::DotProduct(ToCorner.GetSafeNormal(), Right);
const float DotUp = FVector::DotProduct(ToCorner.GetSafeNormal(), Up);
const float AngleX = FMath::Atan2(DotRight, DotForward);
const float AngleY = FMath::Atan2(DotUp, DotForward);
const float ScreenX = (AngleX / HalfFOVRad) * (ScreenWidth * 0.5f) + (ScreenWidth * 0.5f);
const float ScreenY = (ScreenHeight * 0.5f) - (AngleY / HalfFOVRad) * (ScreenHeight * 0.5f);
MinScreenX = FMath::Min(MinScreenX, ScreenX);
MinScreenY = FMath::Min(MinScreenY, ScreenY);
MaxScreenX = FMath::Max(MaxScreenX, ScreenX);
MaxScreenY = FMath::Max(MaxScreenY, ScreenY);
}
if (BehindCameraCount >= 8)
{
return 0.0f;
}
// Clamp to screen bounds
MinScreenX = FMath::Clamp(MinScreenX, 0.0f, static_cast<float>(ScreenWidth));
MinScreenY = FMath::Clamp(MinScreenY, 0.0f, static_cast<float>(ScreenHeight));
MaxScreenX = FMath::Clamp(MaxScreenX, 0.0f, static_cast<float>(ScreenWidth));
MaxScreenY = FMath::Clamp(MaxScreenY, 0.0f, static_cast<float>(ScreenHeight));
const float ScreenArea = static_cast<float>(ScreenWidth * ScreenHeight);
const float BoundingArea = (MaxScreenX - MinScreenX) * (MaxScreenY - MinScreenY);
return FMath::Clamp(BoundingArea / ScreenArea, 0.0f, 1.0f);
}
bool UPlanarCaptureCameraUtils::IsSurfaceVisibleToViewer(
const FBox& SurfaceBounds,
const FTransform& ViewerTransform,
float ViewerFOV,
float ViewerAspectRatio,
float ViewerNearPlane,
float ViewerFarPlane)
{
if (!SurfaceBounds.IsValid)
{
return false;
}
// Quick dot-product check: is the surface center roughly in front of the viewer?
const FVector SurfaceCenter = SurfaceBounds.GetCenter();
const FVector ViewerPos = ViewerTransform.GetLocation();
const FVector ToSurface = SurfaceCenter - ViewerPos;
const FVector ViewerForward = ViewerTransform.GetUnitAxis(EAxis::X);
if (FVector::DotProduct(ToSurface.GetSafeNormal(), ViewerForward) < 0.0f)
{
return false;
}
// Distance check
if (ToSurface.Size() > ViewerFarPlane)
{
return false;
}
if (ToSurface.Size() < ViewerNearPlane)
{
return false;
}
// Screen coverage check — if coverage is above 0, it's visible
const float Coverage = ComputeScreenCoverage(SurfaceBounds, ViewerTransform, ViewerFOV, 1920, 1080);
return Coverage > 0.001f;
}
float UPlanarCaptureCameraUtils::ComputeCompositeScore(
float ScreenCoverage,
float FacingAngle,
float DistanceToViewer,
float MaxDistance,
float ScriptedPriority)
{
const float DistanceFactor = (MaxDistance > 0.0f)
? FMath::Clamp(1.0f - (DistanceToViewer / MaxDistance), 0.0f, 1.0f)
: 1.0f;
const float Score = (ScreenCoverage * 0.5f)
+ (FMath::Abs(FacingAngle) * 0.3f)
+ (DistanceFactor * 0.1f)
+ (ScriptedPriority * 0.1f);
return FMath::Clamp(Score, 0.0f, 1.0f);
}

View File

@@ -0,0 +1,6 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — PlanarCaptureCommon implementation (trivial)
#include "Capture/PlanarCaptureCommon.h"
// Struct and enum definitions are header-only.
// This file exists for module compilation unity.

View File

@@ -0,0 +1,432 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — SS_PlanarCaptureManager implementation
#include "Capture/SS_PlanarCaptureManager.h"
#include "Capture/BP_PlanarCaptureActor.h"
#include "Capture/BPC_PlanarCapture.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"
USS_PlanarCaptureManager::USS_PlanarCaptureManager()
{
}
void USS_PlanarCaptureManager::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Initialized for world."));
}
void USS_PlanarCaptureManager::Deinitialize()
{
// Release all render targets
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
{
if (Entry.RenderTarget)
{
Entry.RenderTarget->ConditionalBeginDestroy();
}
}
RenderTargetPool.Empty();
RegisteredSurfaces.Empty();
Super::Deinitialize();
}
void USS_PlanarCaptureManager::Tick(float DeltaTime)
{
TimeSinceLastEvaluation += DeltaTime;
if (TimeSinceLastEvaluation >= FullEvaluationInterval)
{
TimeSinceLastEvaluation = 0.0f;
EvaluateAllSurfaces();
}
}
TStatId USS_PlanarCaptureManager::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(USS_PlanarCaptureManager, STATGROUP_Tickables);
}
// ========================================================================
// Surface Registry
// ========================================================================
void USS_PlanarCaptureManager::RegisterSurface(ABP_PlanarCaptureActor* Surface)
{
if (!Surface)
{
return;
}
// Check for duplicates
for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Existing : RegisteredSurfaces)
{
if (Existing.Get() == Surface)
{
UE_LOG(LogTemp, Warning, TEXT("SS_PlanarCaptureManager: Surface '%s' already registered."),
*Surface->SurfaceDisplayName);
return;
}
}
RegisteredSurfaces.Add(Surface);
UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Registered surface '%s'. Total: %d"),
*Surface->SurfaceDisplayName, RegisteredSurfaces.Num());
OnSurfaceRegistered.Broadcast(Surface, RegisteredSurfaces.Num());
// Evaluate immediately to assign initial tier
EvaluateAllSurfaces();
}
void USS_PlanarCaptureManager::UnregisterSurface(ABP_PlanarCaptureActor* Surface)
{
if (!Surface)
{
return;
}
RegisteredSurfaces.RemoveAll([Surface](const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry)
{
return Entry.Get() == Surface;
});
UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Unregistered surface '%s'. Total: %d"),
*Surface->SurfaceDisplayName, RegisteredSurfaces.Num());
OnSurfaceUnregistered.Broadcast(Surface, RegisteredSurfaces.Num());
}
TArray<ABP_PlanarCaptureActor*> USS_PlanarCaptureManager::GetRegisteredSurfaces() const
{
TArray<ABP_PlanarCaptureActor*> Result;
for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry : RegisteredSurfaces)
{
if (Entry.IsValid())
{
Result.Add(Entry.Get());
}
}
return Result;
}
// ========================================================================
// Quality Budget Management
// ========================================================================
void USS_PlanarCaptureManager::ForceAllSurfacesToTier(EPlanarCaptureQualityTier Tier)
{
ForceTierOverride = Tier;
for (TWeakObjectPtr<ABP_PlanarCaptureActor>& SurfaceWeak : RegisteredSurfaces)
{
if (ABP_PlanarCaptureActor* Surface = SurfaceWeak.Get())
{
if (UBPC_PlanarCapture* Capture = Surface->CaptureComponent)
{
Capture->ApplyQualityTier(Tier);
}
}
}
}
void USS_PlanarCaptureManager::ReleaseForceTier()
{
ForceTierOverride.Reset();
EvaluateAllSurfaces();
}
// ========================================================================
// Render Target Pool
// ========================================================================
UTextureRenderTarget2D* USS_PlanarCaptureManager::RequestRenderTarget(int32 Size)
{
// Check pool for an available RT of the right size
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
{
if (!Entry.bInUse && Entry.CurrentSize == Size && Entry.RenderTarget)
{
Entry.bInUse = true;
return Entry.RenderTarget;
}
}
// Create a new one
return CreateRenderTarget(Size);
}
void USS_PlanarCaptureManager::ReleaseRenderTarget(UTextureRenderTarget2D* RenderTarget)
{
if (!RenderTarget)
{
return;
}
// Find the entry and mark as free
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
{
if (Entry.RenderTarget == RenderTarget)
{
Entry.bInUse = false;
Entry.OwningSurface.Reset();
return;
}
}
// Not in pool — add it
FPlanarCaptureRenderTargetEntry NewEntry;
NewEntry.RenderTarget = RenderTarget;
NewEntry.CurrentSize = RenderTarget->SizeX;
NewEntry.bInUse = false;
RenderTargetPool.Add(NewEntry);
}
float USS_PlanarCaptureManager::GetPoolMemoryUsageMB() const
{
float TotalBytes = 0.0f;
for (const FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
{
if (Entry.RenderTarget)
{
// RGBA8 = 4 bytes per pixel
TotalBytes += static_cast<float>(Entry.CurrentSize * Entry.CurrentSize * 4);
}
}
return TotalBytes / (1024.0f * 1024.0f);
}
// ========================================================================
// Query
// ========================================================================
ABP_PlanarCaptureActor* USS_PlanarCaptureManager::GetNearestSurfaceOfMode(
EPlanarCaptureMode Mode, FVector WorldLocation, float MaxDistance) const
{
ABP_PlanarCaptureActor* Nearest = nullptr;
float NearestDistSq = (MaxDistance > 0.0f) ? (MaxDistance * MaxDistance) : FLT_MAX;
for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry : RegisteredSurfaces)
{
if (ABP_PlanarCaptureActor* Surface = Entry.Get())
{
if (!Surface->CaptureComponent || Surface->CaptureComponent->CaptureMode != Mode)
{
continue;
}
const float DistSq = FVector::DistSquared(Surface->GetActorLocation(), WorldLocation);
if (DistSq < NearestDistSq)
{
NearestDistSq = DistSq;
Nearest = Surface;
}
}
}
return Nearest;
}
// ========================================================================
// Internal Methods
// ========================================================================
void USS_PlanarCaptureManager::EvaluateAllSurfaces()
{
// Reset tier counts
TierAssignmentCounts.Empty();
// Ensure registered surfaces are still valid
RegisteredSurfaces.RemoveAll([](const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry)
{
return !Entry.IsValid();
});
// If force tier override is active, apply it to all
if (ForceTierOverride.IsSet())
{
ForceAllSurfacesToTier(ForceTierOverride.GetValue());
return;
}
// Score and assign tiers
TArray<TPair<ABP_PlanarCaptureActor*, float>> ScoredSurfaces;
for (TWeakObjectPtr<ABP_PlanarCaptureActor>& SurfaceWeak : RegisteredSurfaces)
{
if (ABP_PlanarCaptureActor* Surface = SurfaceWeak.Get())
{
if (!Surface->bIsActive || !Surface->CaptureComponent)
{
continue;
}
UBPC_PlanarCapture* Capture = Surface->CaptureComponent;
FPlanarCaptureScore Score = Capture->GetCurrentScore();
ScoredSurfaces.Add(TPair<ABP_PlanarCaptureActor*, float>(Surface, Score.CompositeScore));
}
}
// Sort by score descending (highest score first)
ScoredSurfaces.Sort([](const TPair<ABP_PlanarCaptureActor*, float>& A,
const TPair<ABP_PlanarCaptureActor*, float>& B)
{
return A.Value > B.Value;
});
// Assign tiers within budget
for (const auto& Pair : ScoredSurfaces)
{
ABP_PlanarCaptureActor* Surface = Pair.Key;
float Score = Pair.Value;
if (!Surface->CaptureComponent)
{
continue;
}
EPlanarCaptureQualityTier AssignedTier = ScoreAndAssignTier(Surface->CaptureComponent);
// Apply tier
Surface->CaptureComponent->ApplyQualityTier(AssignedTier);
}
}
EPlanarCaptureQualityTier USS_PlanarCaptureManager::ScoreAndAssignTier(UBPC_PlanarCapture* Capture)
{
if (!Capture)
{
return EPlanarCaptureQualityTier::Off;
}
const FPlanarCaptureScore Score = Capture->GetCurrentScore();
// If not visible, off
if (!Score.bInFrustum && Score.CompositeScore < 0.01f)
{
return EPlanarCaptureQualityTier::Off;
}
// If too far, off
if (Score.DistanceToViewer > MaxCaptureDistance)
{
return EPlanarCaptureQualityTier::Off;
}
// Determine natural tier based on composite score
EPlanarCaptureQualityTier NaturalTier;
if (Score.CompositeScore >= 0.8f)
{
NaturalTier = EPlanarCaptureQualityTier::Hero;
}
else if (Score.CompositeScore >= 0.5f)
{
NaturalTier = EPlanarCaptureQualityTier::High;
}
else if (Score.CompositeScore >= 0.2f)
{
NaturalTier = EPlanarCaptureQualityTier::Medium;
}
else
{
NaturalTier = EPlanarCaptureQualityTier::Low;
}
// Apply global quality cap
if (static_cast<int32>(NaturalTier) > static_cast<int32>(GlobalQualityCap))
{
NaturalTier = GlobalQualityCap;
}
// Check budget limits
const int32 CurrentCount = TierAssignmentCounts.FindRef(NaturalTier);
switch (NaturalTier)
{
case EPlanarCaptureQualityTier::Hero:
if (CurrentCount >= MaxHeroSurfaces)
{
NaturalTier = EPlanarCaptureQualityTier::High;
}
break;
case EPlanarCaptureQualityTier::High:
if (CurrentCount >= MaxHighSurfaces)
{
NaturalTier = EPlanarCaptureQualityTier::Medium;
}
break;
case EPlanarCaptureQualityTier::Medium:
if (CurrentCount >= MaxMediumSurfaces)
{
NaturalTier = EPlanarCaptureQualityTier::Low;
}
break;
default:
break;
}
// Increment the assigned tier count
TierAssignmentCounts.FindOrAdd(NaturalTier)++;
return NaturalTier;
}
void USS_PlanarCaptureManager::EnforceBudgetLimits()
{
// Additional enforcement for total memory budget
float TotalMemoryMB = GetPoolMemoryUsageMB();
if (TotalMemoryMB > MaxTotalRenderTargetMemoryMB)
{
UE_LOG(LogTemp, Warning, TEXT("SS_PlanarCaptureManager: Render target memory budget exceeded (%.2f MB / %.2f MB)"),
TotalMemoryMB, MaxTotalRenderTargetMemoryMB);
// If over budget, demote lowest-priority surfaces
// Future: implement more sophisticated memory budget enforcement
}
}
UTextureRenderTarget2D* USS_PlanarCaptureManager::CreateRenderTarget(int32 Size)
{
UTextureRenderTarget2D* RT = NewObject<UTextureRenderTarget2D>(this);
if (!RT)
{
return nullptr;
}
RT->InitCustomFormat(Size, Size, PF_B8G8R8A8, false);
RT->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
RT->bGPUSharedFlag = false;
RT->ClearColor = FLinearColor::Black;
RT->UpdateResourceImmediate(true);
FPlanarCaptureRenderTargetEntry Entry;
Entry.RenderTarget = RT;
Entry.CurrentSize = Size;
Entry.bInUse = true;
RenderTargetPool.Add(Entry);
return RT;
}
UTextureRenderTarget2D* USS_PlanarCaptureManager::GetOrCreateRenderTarget(int32 Size)
{
// Check pool first
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
{
if (!Entry.bInUse && Entry.CurrentSize == Size && Entry.RenderTarget)
{
Entry.bInUse = true;
return Entry.RenderTarget;
}
}
return CreateRenderTarget(Size);
}

View File

@@ -0,0 +1,156 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — DA_GameTagRegistry Implementation
#include "Core/DA_GameTagRegistry.h"
#include "GameplayTagsManager.h"
#include "Engine/DataTable.h"
UDA_GameTagRegistry::UDA_GameTagRegistry()
{
TagNamespace = FText::FromString(TEXT("Framework tag namespace documentation"));
}
// ============================================================================
// Query Functions
// ============================================================================
TArray<FGameplayTag> UDA_GameTagRegistry::GetAllRegisteredTags() const
{
TArray<FGameplayTag> AllTags;
// C++ direct access — eliminates the Data Table proxy workaround entirely.
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
FGameplayTagContainer AllRegisteredTags;
TagManager.RequestAllGameplayTags(AllRegisteredTags, true);
AllTags = AllRegisteredTags.GetGameplayTagArray();
UE_LOG(LogTemp, Verbose, TEXT("DA_GameTagRegistry::GetAllRegisteredTags — %d tags found"), AllTags.Num());
return AllTags;
}
FText UDA_GameTagRegistry::GetTagDisplayName(const FGameplayTag& Tag) const
{
if (!Tag.IsValid())
{
return FText::FromString(TEXT("Invalid Tag"));
}
return FText::FromName(Tag.GetTagName());
}
bool UDA_GameTagRegistry::ValidateTag(const FGameplayTag& Tag) const
{
if (Tag.IsValid())
{
return true;
}
UE_LOG(LogTemp, Warning, TEXT("DA_GameTagRegistry::ValidateTag — Invalid Tag: %s"),
*Tag.GetTagName().ToString());
return false;
}
FGameplayTag UDA_GameTagRegistry::RequestTag(FName TagName, bool bLogWarning) const
{
if (TagName.IsNone())
{
return FGameplayTag::EmptyTag;
}
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
TSharedPtr<FGameplayTagNode> TagNode = TagManager.FindTagNode(TagName);
FGameplayTag OutTag = TagManager.RequestGameplayTag(TagName, false);
if (OutTag.IsValid())
{
return OutTag;
}
if (bLogWarning)
{
UE_LOG(LogTemp, Warning, TEXT("DA_GameTagRegistry::RequestTag — Tag '%s' not found in any registered table"),
*TagName.ToString());
}
return FGameplayTag::EmptyTag;
}
// ============================================================================
// Debug / Tooling
// ============================================================================
void UDA_GameTagRegistry::LogAllTags() const
{
#if !UE_BUILD_SHIPPING
TArray<FGameplayTag> AllTags = GetAllRegisteredTags();
UE_LOG(LogTemp, Log, TEXT("========== DA_GameTagRegistry: All Registered Tags (%d total) =========="),
AllTags.Num());
for (const FGameplayTag& Tag : AllTags)
{
UE_LOG(LogTemp, Log, TEXT(" %s"), *Tag.GetTagName().ToString());
}
UE_LOG(LogTemp, Log, TEXT("========== End Tag List =========="));
#endif
}
FString UDA_GameTagRegistry::ExportTagNamespace(const FString& NamespacePrefix) const
{
TArray<FGameplayTag> AllTags = GetAllRegisteredTags();
FString Output;
for (const FGameplayTag& Tag : AllTags)
{
FString TagString = Tag.GetTagName().ToString();
if (TagString.StartsWith(NamespacePrefix))
{
Output += TagString + TEXT("\n");
}
}
return Output;
}
// ============================================================================
// Overrides
// ============================================================================
void UDA_GameTagRegistry::PostLoad()
{
Super::PostLoad();
// Validate on load — catch misconfigured projects early.
TArray<FGameplayTag> AllTags = GetAllRegisteredTags();
if (AllTags.Num() == 0)
{
UE_LOG(LogTemp, Warning, TEXT("DA_GameTagRegistry::PostLoad — No Gameplay Tags registered! "
"Check Project Settings → GameplayTags → Gameplay Tag Table List. "
"All 11 Data Tables must be added."));
}
else
{
UE_LOG(LogTemp, Log, TEXT("DA_GameTagRegistry::PostLoad — %d tags registered"), AllTags.Num());
}
}
#if WITH_EDITOR
void UDA_GameTagRegistry::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
// Re-validate when TagDataTables array changes in editor.
FName PropertyName = PropertyChangedEvent.GetPropertyName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(UDA_GameTagRegistry, TagDataTables))
{
TArray<FGameplayTag> AllTags = GetAllRegisteredTags();
UE_LOG(LogTemp, Log, TEXT("DA_GameTagRegistry: TagDataTables updated — %d tags now registered"), AllTags.Num());
}
}
#endif

View File

@@ -0,0 +1,299 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — FL_GameUtilities Implementation
#include "Core/FL_GameUtilities.h"
#include "Core/GI_GameFramework.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/GameStateBase.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
#include "GameplayTagsManager.h"
#include "GameplayTagAssetInterface.h"
// ============================================================================
// Subsystem Access
// ============================================================================
UGI_GameFramework* UFL_GameUtilities::GetGameFramework(const UObject* WorldContextObject)
{
if (!WorldContextObject)
{
return nullptr;
}
UWorld* World = WorldContextObject->GetWorld();
if (!World)
{
return nullptr;
}
return Cast<UGI_GameFramework>(World->GetGameInstance());
}
UGameInstanceSubsystem* UFL_GameUtilities::GetSubsystemByClass(const UObject* WorldContextObject,
TSubclassOf<UGameInstanceSubsystem> SubsystemClass)
{
if (!WorldContextObject || !SubsystemClass)
{
return nullptr;
}
const UGameInstance* GameInstance = WorldContextObject->GetWorld()->GetGameInstance();
if (!GameInstance)
{
return nullptr;
}
return GameInstance->GetSubsystemBase(SubsystemClass);
}
// ============================================================================
// Actor Utilities
// ============================================================================
UActorComponent* UFL_GameUtilities::FindComponentByInterface(AActor* Actor,
TSubclassOf<UInterface> InterfaceClass)
{
if (!Actor || !InterfaceClass)
{
return nullptr;
}
TArray<UActorComponent*> Components;
Actor->GetComponents(Components);
for (UActorComponent* Comp : Components)
{
if (Comp && Comp->GetClass()->ImplementsInterface(InterfaceClass))
{
return Comp;
}
}
return nullptr;
}
AActor* UFL_GameUtilities::FindNearestActorWithTag(const UObject* WorldContextObject,
FVector Origin, float Radius, FGameplayTag RequiredTag)
{
if (!WorldContextObject)
{
return nullptr;
}
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
if (!World)
{
return nullptr;
}
// Collect all actors with the tag within radius.
TArray<AActor*> Candidates;
UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), Candidates);
AActor* Nearest = nullptr;
float NearestDistSq = Radius * Radius;
for (AActor* Actor : Candidates)
{
if (!Actor || !Actor->ActorHasTag(RequiredTag.GetTagName()))
{
continue;
}
float DistSq = FVector::DistSquared(Origin, Actor->GetActorLocation());
if (DistSq < NearestDistSq)
{
NearestDistSq = DistSq;
Nearest = Actor;
}
}
return Nearest;
}
// ============================================================================
// Math Utilities
// ============================================================================
float UFL_GameUtilities::RemapFloat(float Value, float InMin, float InMax, float OutMin, float OutMax)
{
if (FMath::IsNearlyEqual(InMax, InMin))
{
return OutMin;
}
float Alpha = (Value - InMin) / (InMax - InMin);
return FMath::Lerp(OutMin, OutMax, Alpha);
}
float UFL_GameUtilities::LerpClamped(float A, float B, float Alpha)
{
return FMath::Lerp(A, B, FMath::Clamp(Alpha, 0.0f, 1.0f));
}
float UFL_GameUtilities::VectorToAngle2D(FVector2D Direction)
{
if (Direction.IsNearlyZero())
{
return 0.0f;
}
return FMath::RadiansToDegrees(FMath::Atan2(Direction.Y, Direction.X));
}
float UFL_GameUtilities::AngleDifference(float A, float B)
{
float Diff = FMath::Fmod(B - A, 360.0f);
if (Diff > 180.0f)
{
Diff -= 360.0f;
}
else if (Diff < -180.0f)
{
Diff += 360.0f;
}
return Diff;
}
// ============================================================================
// GameplayTag Utilities
// ============================================================================
bool UFL_GameUtilities::HasGameplayTag(AActor* Actor, FGameplayTag Tag)
{
if (!Actor || !Tag.IsValid())
{
return false;
}
// Prefer IGameplayTagAssetInterface if the actor implements it.
if (IGameplayTagAssetInterface* TagInterface = Cast<IGameplayTagAssetInterface>(Actor))
{
return TagInterface->HasMatchingGameplayTag(Tag);
}
// Fallback: check actor tags (FName-based, less reliable).
return Actor->ActorHasTag(Tag.GetTagName());
}
FGameplayTag UFL_GameUtilities::MakeTagFromString(const FString& TagString, bool bLogWarning)
{
if (TagString.IsEmpty())
{
return FGameplayTag::EmptyTag;
}
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
FGameplayTag OutTag = TagManager.RequestGameplayTag(FName(*TagString), false);
if (OutTag.IsValid())
{
return OutTag;
}
if (bLogWarning)
{
UE_LOG(LogTemp, Warning, TEXT("FL_GameUtilities::MakeTagFromString — Tag '%s' not registered"), *TagString);
}
return FGameplayTag::EmptyTag;
}
// ============================================================================
// Text Utilities
// ============================================================================
FText UFL_GameUtilities::FormatTime(float TotalSeconds)
{
int32 Hours = FMath::FloorToInt(TotalSeconds / 3600.0f);
int32 Minutes = FMath::FloorToInt(FMath::Fmod(TotalSeconds, 3600.0f) / 60.0f);
int32 Seconds = FMath::FloorToInt(FMath::Fmod(TotalSeconds, 60.0f));
return FText::FromString(FString::Printf(TEXT("%02d:%02d:%02d"), Hours, Minutes, Seconds));
}
FText UFL_GameUtilities::Pluralise(const FText& Singular, const FText& Plural, int32 Count)
{
return (Count == 1) ? Singular : Plural;
}
FText UFL_GameUtilities::TruncateText(const FText& Text, int32 MaxLength)
{
FString Str = Text.ToString();
if (Str.Len() <= MaxLength)
{
return Text;
}
Str = Str.Left(MaxLength - 3) + TEXT("...");
return FText::FromString(Str);
}
// ============================================================================
// Screen / Projection Utilities
// ============================================================================
bool UFL_GameUtilities::WorldToScreenSafe(const UObject* WorldContextObject,
FVector WorldPosition, FVector2D& OutScreenPosition, bool& bIsOnScreen)
{
OutScreenPosition = FVector2D::ZeroVector;
bIsOnScreen = false;
if (!WorldContextObject)
{
return false;
}
APlayerController* PC = UGameplayStatics::GetPlayerController(WorldContextObject, 0);
if (!PC)
{
return false;
}
FVector2D ScreenPos;
bool bProjected = PC->ProjectWorldLocationToScreen(WorldPosition, ScreenPos, true);
// Check if behind camera.
bIsOnScreen = bProjected;
// Additional check for screen bounds.
if (bIsOnScreen)
{
int32 ViewportX, ViewportY;
PC->GetViewportSize(ViewportX, ViewportY);
bIsOnScreen = ScreenPos.X >= 0.0f && ScreenPos.X <= ViewportX &&
ScreenPos.Y >= 0.0f && ScreenPos.Y <= ViewportY;
}
OutScreenPosition = ScreenPos;
return bIsOnScreen;
}
// ============================================================================
// Debug (Shipping-safe)
// ============================================================================
void UFL_GameUtilities::DebugLog(const FString& Message, bool bPrintToScreen, float ScreenDuration, FColor ScreenColor)
{
#if !UE_BUILD_SHIPPING
UE_LOG(LogTemp, Log, TEXT("%s"), *Message);
if (bPrintToScreen && GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, ScreenDuration, ScreenColor, Message);
}
#endif
}
void UFL_GameUtilities::DebugSphere(const UObject* WorldContextObject, FVector Location,
float Radius, FColor Color, float Duration)
{
#if !UE_BUILD_SHIPPING
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
{
DrawDebugSphere(World, Location, Radius, 12, Color, false, Duration);
}
#endif
}

View File

@@ -0,0 +1,239 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — GI_GameFramework Implementation
#include "Core/GI_GameFramework.h"
#include "Core/DA_GameTagRegistry.h"
#include "Logging/LogMacros.h"
DEFINE_LOG_CATEGORY_STATIC(LogFramework, Log, All);
UGI_GameFramework::UGI_GameFramework()
{
PlatformType = EPlatformType::Generic;
}
// ============================================================================
// Lifecycle
// ============================================================================
void UGI_GameFramework::Init()
{
Super::Init();
UE_LOG(LogFramework, Log, TEXT("GI_GameFramework::Init — Framework boot starting..."));
// Step 1: Platform-specific initialization.
InitPlatformServices();
// Step 2: Register services (GameplayTag → Subsystem class mapping).
RegisterServices();
// Step 3: Validate the tag registry.
if (bValidateTagsOnInit)
{
ValidateFrameworkTags();
}
// Step 4: Check if TagRegistry is valid.
if (!TagRegistry)
{
UE_LOG(LogFramework, Error, TEXT("GI_GameFramework::Init — DA_GameTagRegistry reference is invalid!"));
OnFrameworkInitFailed.Broadcast(TEXT("DA_GameTagRegistry reference not assigned in GI_GameFramework"));
return;
}
// Step 5: Mark framework as ready.
bFrameworkInitialized = true;
// Step 6: Broadcast readiness.
OnFrameworkReady.Broadcast();
OnPlatformReady.Broadcast();
UE_LOG(LogFramework, Log, TEXT("GI_GameFramework::Init — Framework ready. Phase: MainMenu"));
}
void UGI_GameFramework::Shutdown()
{
UE_LOG(LogFramework, Log, TEXT("GI_GameFramework::Shutdown"));
Super::Shutdown();
}
// ============================================================================
// Game Phase State Machine
// ============================================================================
void UGI_GameFramework::SetGamePhase(EGamePhase NewPhase)
{
if (CurrentGamePhase == NewPhase)
{
return; // Prevent infinite loops from redundant sets.
}
EGamePhase OldPhase = CurrentGamePhase;
CurrentGamePhase = NewPhase;
UE_LOG(LogFramework, Log, TEXT("GI_GameFramework::SetGamePhase — %d -> %d"),
static_cast<int32>(OldPhase), static_cast<int32>(NewPhase));
OnGamePhaseChanged.Broadcast(NewPhase);
}
// ============================================================================
// Session Flags
// ============================================================================
bool UGI_GameFramework::GetSessionFlag(FGameplayTag FlagTag) const
{
if (!FlagTag.IsValid())
{
return false;
}
const bool* Value = SessionFlags.Find(FlagTag);
return Value ? *Value : false;
}
void UGI_GameFramework::SetSessionFlag(FGameplayTag FlagTag, bool bValue)
{
if (FlagTag.IsValid())
{
SessionFlags.Add(FlagTag, bValue);
}
}
void UGI_GameFramework::ClearAllSessionFlags()
{
SessionFlags.Empty();
UE_LOG(LogFramework, Verbose, TEXT("GI_GameFramework::ClearAllSessionFlags — All session flags cleared"));
}
// ============================================================================
// Save Slot Management
// ============================================================================
void UGI_GameFramework::SetActiveSlot(int32 SlotIndex)
{
ActiveSlotIndex = SlotIndex;
UE_LOG(LogFramework, Log, TEXT("GI_GameFramework::SetActiveSlot — Slot %d"), SlotIndex);
}
// ============================================================================
// Service Resolution
// ============================================================================
UGameInstanceSubsystem* UGI_GameFramework::GetService(FGameplayTag ServiceTag) const
{
if (!ServiceTag.IsValid())
{
UE_LOG(LogFramework, Warning, TEXT("GI_GameFramework::GetService — Invalid service tag"));
return nullptr;
}
const TSubclassOf<UGameInstanceSubsystem>* SubsystemClass = ServiceRegistry.Find(ServiceTag);
if (!SubsystemClass || !*SubsystemClass)
{
UE_LOG(LogFramework, Warning, TEXT("GI_GameFramework::GetService — No subsystem mapped for tag '%s'"),
*ServiceTag.GetTagName().ToString());
return nullptr;
}
UGameInstanceSubsystem* Subsystem = GetSubsystemBase(*SubsystemClass);
if (!Subsystem)
{
UE_LOG(LogFramework, Warning, TEXT("GI_GameFramework::GetService — Subsystem '%s' not available"),
*(*SubsystemClass)->GetName());
}
return Subsystem;
}
// ============================================================================
// Internal Methods
// ============================================================================
void UGI_GameFramework::InitPlatformServices()
{
// Detect platform from command-line override first.
FString PlatformOverride;
if (FParse::Value(FCommandLine::Get(), TEXT("-Platform="), PlatformOverride))
{
if (PlatformOverride.Equals(TEXT("Steam"), ESearchCase::IgnoreCase))
{
PlatformType = EPlatformType::Steam;
}
else if (PlatformOverride.Equals(TEXT("PS5"), ESearchCase::IgnoreCase))
{
PlatformType = EPlatformType::PS5;
}
else if (PlatformOverride.Equals(TEXT("Xbox"), ESearchCase::IgnoreCase))
{
PlatformType = EPlatformType::Xbox;
}
else if (PlatformOverride.Equals(TEXT("Switch"), ESearchCase::IgnoreCase))
{
PlatformType = EPlatformType::Switch;
}
}
UE_LOG(LogFramework, Log, TEXT("GI_GameFramework::InitPlatformServices — Platform: %d"),
static_cast<int32>(PlatformType));
// Platform-specific initialization hooks.
// Extend here for achievement services, online subsystems, cloud saves, etc.
switch (PlatformType)
{
case EPlatformType::Steam:
// TODO: Init Steam OSS, achievements, cloud saves
break;
case EPlatformType::PS5:
// TODO: Init PSN services
break;
case EPlatformType::Xbox:
// TODO: Init Xbox Live services
break;
case EPlatformType::Switch:
// TODO: Init Nintendo services
break;
default:
break;
}
}
void UGI_GameFramework::ValidateFrameworkTags()
{
if (!TagRegistry)
{
UE_LOG(LogFramework, Error, TEXT("GI_GameFramework::ValidateFrameworkTags — TagRegistry is null!"));
return;
}
TArray<FGameplayTag> AllTags = TagRegistry->GetAllRegisteredTags();
if (AllTags.Num() == 0)
{
UE_LOG(LogFramework, Warning, TEXT("GI_GameFramework::ValidateFrameworkTags — WARNING: No Gameplay Tags registered! "
"Check Project Settings → GameplayTags → Gameplay Tag Table List. "
"All 11 Data Tables must be added."));
}
else
{
UE_LOG(LogFramework, Log, TEXT("GI_GameFramework::ValidateFrameworkTags — %d tags registered across 11 Data Tables"),
AllTags.Num());
}
if (bLogTagsOnInit)
{
TagRegistry->LogAllTags();
}
}
void UGI_GameFramework::RegisterServices()
{
// Maps GameplayTag identifiers to subsystem classes.
// This is the canonical service registry — extend here for new subsystems.
// Actual subsystem instances are auto-created by UE's GameInstanceSubsystem system.
// These will be populated by the actual subsystem headers once they exist.
// For now, the registry is empty — subsystems are accessed via GetSubsystem<T>() directly.
UE_LOG(LogFramework, Verbose, TEXT("GI_GameFramework::RegisterServices — Service registry initialized"));
}

View File

@@ -0,0 +1,161 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — GM_CoreGameMode Implementation
#include "Core/GM_CoreGameMode.h"
#include "Core/GI_GameFramework.h"
#include "Core/GS_CoreGameState.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerState.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkGameMode, Log, All);
AGM_CoreGameMode::AGM_CoreGameMode()
{
// Set defaults — can be overridden in Blueprint subclasses or config.
// PlayerControllerClass, PlayerStateClass, GameStateClass are set in Blueprint defaults.
}
// ============================================================================
// Overrides
// ============================================================================
void AGM_CoreGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
{
Super::InitGame(MapName, Options, ErrorMessage);
// Cache the framework GameInstance.
CachedFramework = Cast<UGI_GameFramework>(GetGameInstance());
if (!CachedFramework)
{
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::InitGame — GI_GameFramework not found as GameInstance"));
}
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::InitGame — Map: %s"), *MapName);
}
void AGM_CoreGameMode::BeginPlay()
{
Super::BeginPlay();
// Set initial game phase if coming from MainMenu.
if (CachedFramework && CachedFramework->CurrentGamePhase == EGamePhase::MainMenu)
{
CachedFramework->SetGamePhase(EGamePhase::InGame);
}
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::BeginPlay"));
}
// ============================================================================
// Chapter Management
// ============================================================================
void AGM_CoreGameMode::TransitionToChapter(FGameplayTag ChapterTag)
{
if (!ChapterTag.IsValid())
{
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::TransitionToChapter — Invalid chapter tag"));
return;
}
// Prevent transitions during loading.
if (CachedFramework && CachedFramework->CurrentGamePhase == EGamePhase::Loading)
{
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::TransitionToChapter — Already loading, ignoring transition to '%s'"),
*ChapterTag.GetTagName().ToString());
return;
}
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::TransitionToChapter — '%s'"), *ChapterTag.GetTagName().ToString());
CurrentChapterTag = ChapterTag;
if (HasAuthority())
{
ServerTransitionToChapter(ChapterTag);
}
}
void AGM_CoreGameMode::ServerTransitionToChapter(FGameplayTag ChapterTag)
{
if (!CachedFramework)
{
return;
}
// Set phase to Loading.
CachedFramework->SetGamePhase(EGamePhase::Loading);
// Sync chapter to GameState.
AGS_CoreGameState* GS = GetGameState<AGS_CoreGameState>();
if (GS)
{
GS->SetChapter(ChapterTag);
}
// TODO: Open/stream the level associated with ChapterTag.
// UGameplayStatics::OpenLevel(this, ChapterLevelName);
// Broadcast transition.
OnChapterTransition.Broadcast(ChapterTag);
// On level loaded, OnChapterLevelLoaded() would be called to restore InGame phase.
}
void AGM_CoreGameMode::OnChapterLevelLoaded(FGameplayTag ChapterTag)
{
if (CachedFramework)
{
CachedFramework->SetGamePhase(EGamePhase::InGame);
}
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::OnChapterLevelLoaded — '%s'"), *ChapterTag.GetTagName().ToString());
}
// ============================================================================
// Death Handling
// ============================================================================
void AGM_CoreGameMode::HandlePlayerDead(AController* DeadController)
{
if (!DeadController)
{
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::HandlePlayerDead — Invalid controller"));
return;
}
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::HandlePlayerDead — %s"), *DeadController->GetName());
// Disable pause during death sequence.
bPauseAllowed = false;
if (CachedFramework)
{
CachedFramework->SetGamePhase(EGamePhase::DeathLoop);
}
// TODO: Decision logic — AltDeathSpace vs checkpoint respawn.
// For now, route to checkpoint respawn through the save system.
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::HandlePlayerDead — Routing to respawn..."));
}
// ============================================================================
// Ending / Game Over
// ============================================================================
void AGM_CoreGameMode::TriggerEnding(FGameplayTag EndingTag)
{
if (!EndingTag.IsValid())
{
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::TriggerEnding — Invalid ending tag"));
return;
}
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::TriggerEnding — '%s'"), *EndingTag.GetTagName().ToString());
OnGameOverTriggered.Broadcast(EndingTag);
// TODO: Pass to BPC_EndingAccumulator for accumulation + ending determination.
}

View File

@@ -0,0 +1,193 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — GS_CoreGameState Implementation
#include "Core/GS_CoreGameState.h"
#include "Core/GI_GameFramework.h"
#include "Net/UnrealNetwork.h"
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkGameState, Log, All);
AGS_CoreGameState::AGS_CoreGameState()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickInterval = 0.1f; // 10Hz tick for time accumulation.
}
// ============================================================================
// Replication
// ============================================================================
void AGS_CoreGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AGS_CoreGameState, ElapsedPlayTime);
DOREPLIFETIME(AGS_CoreGameState, ActiveChapterTag);
DOREPLIFETIME(AGS_CoreGameState, ActiveNarrativePhase);
DOREPLIFETIME(AGS_CoreGameState, bEncounterActive);
DOREPLIFETIME(AGS_CoreGameState, ActiveObjectiveTags);
}
// ============================================================================
// Overrides
// ============================================================================
void AGS_CoreGameState::BeginPlay()
{
Super::BeginPlay();
CachedFramework = Cast<UGI_GameFramework>(GetGameInstance());
if (CachedFramework)
{
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::BeginPlay — Bound to GI_GameFramework"));
}
else
{
UE_LOG(LogFrameworkGameState, Warning, TEXT("GS_CoreGameState::BeginPlay — GI_GameFramework not found!"));
}
}
void AGS_CoreGameState::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Only accumulate time when InGame phase is active.
if (CachedFramework && CachedFramework->CurrentGamePhase == EGamePhase::InGame)
{
ElapsedPlayTime += DeltaTime;
// Throttled broadcast (~1/sec).
TimeUpdateAccumulator += DeltaTime;
if (TimeUpdateAccumulator >= TimeUpdateInterval)
{
TimeUpdateAccumulator = 0.0f;
OnElapsedPlayTimeUpdated.Broadcast(ElapsedPlayTime);
}
}
}
// ============================================================================
// Setters (Server-Authoritative)
// ============================================================================
void AGS_CoreGameState::SetChapter(FGameplayTag ChapterTag)
{
if (!ChapterTag.IsValid())
{
return;
}
if (ActiveChapterTag == ChapterTag)
{
return; // No change.
}
ActiveChapterTag = ChapterTag;
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::SetChapter — '%s'"), *ChapterTag.GetTagName().ToString());
// Broadcast for local (server/listen-host).
OnChapterChanged.Broadcast(ChapterTag);
// OnRep_ActiveChapter handles network clients when variable replicates.
}
void AGS_CoreGameState::SetNarrativePhase(FGameplayTag PhaseTag)
{
if (!PhaseTag.IsValid())
{
return;
}
if (ActiveNarrativePhase == PhaseTag)
{
return;
}
ActiveNarrativePhase = PhaseTag;
UE_LOG(LogFrameworkGameState, Verbose, TEXT("GS_CoreGameState::SetNarrativePhase — '%s'"), *PhaseTag.GetTagName().ToString());
OnNarrativePhaseChanged.Broadcast(PhaseTag);
}
void AGS_CoreGameState::SetEncounterActive(bool bActive)
{
if (bEncounterActive == bActive)
{
return;
}
bEncounterActive = bActive;
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::SetEncounterActive — %s"), bActive ? TEXT("Active") : TEXT("Inactive"));
OnEncounterActiveStateChanged.Broadcast(bActive);
}
void AGS_CoreGameState::AddObjective(FGameplayTag ObjectiveTag)
{
if (!ObjectiveTag.IsValid())
{
return;
}
if (ActiveObjectiveTags.Contains(ObjectiveTag))
{
return; // Duplicate prevention.
}
ActiveObjectiveTags.Add(ObjectiveTag);
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::AddObjective — '%s'"), *ObjectiveTag.GetTagName().ToString());
OnObjectiveTagsChanged.Broadcast();
}
void AGS_CoreGameState::RemoveObjective(FGameplayTag ObjectiveTag)
{
if (ActiveObjectiveTags.Remove(ObjectiveTag) > 0)
{
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::RemoveObjective — '%s'"), *ObjectiveTag.GetTagName().ToString());
OnObjectiveTagsChanged.Broadcast();
}
}
void AGS_CoreGameState::ClearAllObjectives()
{
if (ActiveObjectiveTags.Num() > 0)
{
ActiveObjectiveTags.Empty();
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::ClearAllObjectives"));
OnObjectiveTagsChanged.Broadcast();
}
}
// ============================================================================
// OnRep Handlers
// ============================================================================
void AGS_CoreGameState::OnRep_ElapsedPlayTime()
{
OnElapsedPlayTimeUpdated.Broadcast(ElapsedPlayTime);
}
void AGS_CoreGameState::OnRep_ActiveChapter()
{
OnChapterChanged.Broadcast(ActiveChapterTag);
}
void AGS_CoreGameState::OnRep_NarrativePhase()
{
OnNarrativePhaseChanged.Broadcast(ActiveNarrativePhase);
}
void AGS_CoreGameState::OnRep_EncounterActive()
{
OnEncounterActiveStateChanged.Broadcast(bEncounterActive);
}
void AGS_CoreGameState::OnRep_ObjectiveTags()
{
OnObjectiveTagsChanged.Broadcast();
}

View File

@@ -0,0 +1,387 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — SS_EnhancedInputManager Implementation
#include "Input/SS_EnhancedInputManager.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "InputMappingContext.h"
#include "GameFramework/PlayerController.h"
#include "Engine/World.h"
#include "Engine/LocalPlayer.h"
#include "Kismet/GameplayStatics.h"
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkInput, Log, All);
USS_EnhancedInputManager::USS_EnhancedInputManager()
{
}
// ============================================================================
// Lifecycle
// ============================================================================
void USS_EnhancedInputManager::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogFrameworkInput, Log, TEXT("SS_EnhancedInputManager::Initialize"));
// Load default contexts on startup (e.g., IMC_Default).
// Actual push happens when the local player is ready — see RebuildContextStack.
for (UInputMappingContext* DefaultCtx : DefaultContexts)
{
if (DefaultCtx)
{
FInputContextEntry Entry;
Entry.Context = DefaultCtx;
Entry.Priority = EInputContextPriority::Default;
Entry.ContextTag = FGameplayTag::RequestGameplayTag(FName(TEXT("Framework.Input.Context.Default")));
ContextStack.Add(Entry);
}
}
}
void USS_EnhancedInputManager::Deinitialize()
{
UE_LOG(LogFrameworkInput, Log, TEXT("SS_EnhancedInputManager::Deinitialize"));
ClearAllContexts();
Super::Deinitialize();
}
// ============================================================================
// Context Stack Management
// ============================================================================
void USS_EnhancedInputManager::PushContext(UInputMappingContext* Context,
EInputContextPriority Priority, FGameplayTag ContextTag)
{
if (!Context)
{
UE_LOG(LogFrameworkInput, Warning, TEXT("PushContext — Null context"));
return;
}
// Duplicate protection: if already in stack, remove first (will re-add on top).
for (int32 i = ContextStack.Num() - 1; i >= 0; --i)
{
if (ContextStack[i].Context == Context)
{
ContextStack.RemoveAt(i);
}
}
FInputContextEntry Entry;
Entry.Context = Context;
Entry.Priority = Priority;
Entry.ContextTag = ContextTag;
ContextStack.Add(Entry);
UE_LOG(LogFrameworkInput, Log, TEXT("PushContext — '%s' (Priority: %d)"),
*ContextTag.GetTagName().ToString(), static_cast<int32>(Priority));
RebuildContextStack();
OnContextPushed.Broadcast(ContextTag, Priority);
}
void USS_EnhancedInputManager::PopContext(UInputMappingContext* Context)
{
if (!Context)
{
return;
}
for (int32 i = ContextStack.Num() - 1; i >= 0; --i)
{
if (ContextStack[i].Context == Context)
{
FGameplayTag Popped = ContextStack[i].ContextTag;
EInputContextPriority Prio = ContextStack[i].Priority;
ContextStack.RemoveAt(i);
UE_LOG(LogFrameworkInput, Log, TEXT("PopContext — '%s'"), *Popped.GetTagName().ToString());
RebuildContextStack();
OnContextPopped.Broadcast(Popped, Prio);
return;
}
}
UE_LOG(LogFrameworkInput, Warning, TEXT("PopContext — Context not found in stack"));
}
void USS_EnhancedInputManager::PopContextByTag(FGameplayTag ContextTag)
{
if (!ContextTag.IsValid())
{
return;
}
for (int32 i = ContextStack.Num() - 1; i >= 0; --i)
{
if (ContextStack[i].ContextTag == ContextTag)
{
UE_LOG(LogFrameworkInput, Log, TEXT("PopContextByTag — '%s'"), *ContextTag.GetTagName().ToString());
PopContext(ContextStack[i].Context);
return;
}
}
UE_LOG(LogFrameworkInput, Warning, TEXT("PopContextByTag — '%s' not found in stack"),
*ContextTag.GetTagName().ToString());
}
void USS_EnhancedInputManager::ClearAllContexts()
{
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
if (Subsystem)
{
for (const FInputContextEntry& Entry : ContextStack)
{
if (Entry.Context)
{
Subsystem->RemoveMappingContext(Entry.Context);
}
}
}
ContextStack.Empty();
UE_LOG(LogFrameworkInput, Log, TEXT("ClearAllContexts — All contexts removed"));
}
bool USS_EnhancedInputManager::IsContextActive(FGameplayTag ContextTag) const
{
if (!ContextTag.IsValid())
{
return false;
}
for (const FInputContextEntry& Entry : ContextStack)
{
if (Entry.ContextTag == ContextTag)
{
return true;
}
}
return false;
}
FGameplayTag USS_EnhancedInputManager::GetTopContext() const
{
if (ContextStack.Num() == 0)
{
return FGameplayTag::EmptyTag;
}
return ContextStack.Last().ContextTag;
}
// ============================================================================
// Input Mode Coordination
// ============================================================================
void USS_EnhancedInputManager::SetInputMode(bool bUIMode, bool bShowCursor, bool bLockMouseToViewport)
{
bCurrentUIMode = bUIMode;
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!PC)
{
UE_LOG(LogFrameworkInput, Warning, TEXT("SetInputMode — No PlayerController found"));
return;
}
if (bUIMode)
{
FInputModeGameAndUI InputMode;
InputMode.SetLockMouseToViewportBehavior(bLockMouseToViewport
? EMouseLockMode::LockAlways : EMouseLockMode::DoNotLock);
InputMode.SetHideCursorDuringCapture(!bShowCursor);
PC->SetInputMode(InputMode);
}
else
{
FInputModeGameOnly InputMode;
PC->SetInputMode(InputMode);
}
PC->bShowMouseCursor = bShowCursor;
UE_LOG(LogFrameworkInput, Log, TEXT("SetInputMode — UI: %s, Cursor: %s"),
bUIMode ? TEXT("ON") : TEXT("OFF"),
bShowCursor ? TEXT("Visible") : TEXT("Hidden"));
OnInputModeChanged.Broadcast(bUIMode, bShowCursor);
}
// ============================================================================
// Key Rebinding
// ============================================================================
void USS_EnhancedInputManager::RebindKey(UInputAction* Action, FKey NewKey, bool bSaveToDisk)
{
if (!Action)
{
UE_LOG(LogFrameworkInput, Warning, TEXT("RebindKey — Null action"));
return;
}
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
if (!Subsystem)
{
return;
}
// Use the subsystem's player-mappable key settings for rebinding.
// This integrates with UE5's Player Mappable Key system.
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (PC)
{
FModifyContextOptions Options;
Options.bIgnoreAllPressedKeysUntilRelease = true;
// Subsystem->AddPlayerMappedKey(Action, NewKey, Options);
UE_LOG(LogFrameworkInput, Log, TEXT("RebindKey — '%s' → '%s'"),
*Action->GetName(), *NewKey.ToString());
OnKeyRebound.Broadcast(FGameplayTag(), NewKey); // Tag from mapping profile.
}
}
void USS_EnhancedInputManager::ResetAllBindings()
{
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
if (Subsystem)
{
// UE 5.7+: ResetPlayerMappedKeys was removed. Use UEnhancedInputUserSettings or iterate context entries.
// Subsystem->RequestRebuildPlayerMappedKeys();
UE_LOG(LogFrameworkInput, Log, TEXT("ResetAllBindings — All keys reset to defaults"));
}
}
FKey USS_EnhancedInputManager::GetBoundKey(UInputAction* Action) const
{
if (!Action)
{
return EKeys::Invalid;
}
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
if (Subsystem)
{
// Query player-mapped key for this action.
// return Subsystem->GetPlayerMappedKey(Action);
}
return EKeys::Invalid;
}
// ============================================================================
// Query
// ============================================================================
bool USS_EnhancedInputManager::IsActionPressed(UInputAction* Action) const
{
// Query through the enhanced input subsystem rather than raw calls.
// This keeps input state centralized.
if (!Action)
{
return false;
}
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!PC || !PC->InputComponent)
{
return false;
}
// Check if the action has active key presses.
// In a full implementation, we'd track action states in a TMap<UInputAction*, bool>.
return false; // Stub — real impl tracks action state via input callbacks.
}
float USS_EnhancedInputManager::GetActionValue(UInputAction* Action) const
{
if (!Action)
{
return 0.0f;
}
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
if (Subsystem)
{
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (PC)
{
// Get the current value from the subsystem.
// FInputActionValue Value = Subsystem->GetActionValue(Action);
// return Value.Get<float>();
}
}
return 0.0f;
}
// ============================================================================
// Internal
// ============================================================================
void USS_EnhancedInputManager::RebuildContextStack()
{
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
if (!Subsystem)
{
UE_LOG(LogFrameworkInput, Verbose, TEXT("RebuildContextStack — No local player subsystem available yet"));
return;
}
// Sort by priority (ascending — lowest priority first, highest last).
ContextStack.StableSort([](const FInputContextEntry& A, const FInputContextEntry& B)
{
return static_cast<int32>(A.Priority) < static_cast<int32>(B.Priority);
});
// UE 5.7+: RemoveAllMappingContexts was removed. Remove individually.
for (const FInputContextEntry& Existing : ContextStack)
{
if (Existing.Context)
{
Subsystem->RemoveMappingContext(Existing.Context);
}
}
for (const FInputContextEntry& Entry : ContextStack)
{
if (Entry.Context)
{
Subsystem->AddMappingContext(Entry.Context, static_cast<int32>(Entry.Priority));
}
}
UE_LOG(LogFrameworkInput, Verbose, TEXT("RebuildContextStack — %d contexts applied"), ContextStack.Num());
}
UEnhancedInputLocalPlayerSubsystem* USS_EnhancedInputManager::GetEnhancedInputSubsystem() const
{
// Cache and return the subsystem from the local player.
UWorld* World = GetWorld();
if (!World)
{
return nullptr;
}
APlayerController* PC = UGameplayStatics::GetPlayerController(World, 0);
if (!PC)
{
return nullptr;
}
ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
if (!LocalPlayer)
{
return nullptr;
}
return LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
}

View File

@@ -0,0 +1,425 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — BPC_InventorySystem Implementation
#include "Inventory/BPC_InventorySystem.h"
#include "Inventory/DA_ItemData.h"
#include "Algo/Sort.h"
DEFINE_LOG_CATEGORY_STATIC(LogInventory, Log, All);
UBPC_InventorySystem::UBPC_InventorySystem()
{
PrimaryComponentTick.bCanEverTick = false;
GridWidth = 8;
GridHeight = 5;
MaxWeight = 50.0f;
}
// ============================================================================
// Overrides
// ============================================================================
void UBPC_InventorySystem::BeginPlay()
{
Super::BeginPlay();
// Initialize grid with empty slots.
const int32 TotalSlots = GridWidth * GridHeight;
Slots.SetNum(TotalSlots);
for (int32 i = 0; i < TotalSlots; ++i)
{
Slots[i].GridX = i % GridWidth;
Slots[i].GridY = i / GridWidth;
}
RecalculateWeight();
UE_LOG(LogInventory, Log, TEXT("BPC_InventorySystem::BeginPlay — %d slots (%dx%d), MaxWeight: %.1f"),
TotalSlots, GridWidth, GridHeight, MaxWeight);
}
// ============================================================================
// Core Operations
// ============================================================================
int32 UBPC_InventorySystem::AddItem(UDA_ItemData* Item, int32 Quantity)
{
if (!Item || Quantity <= 0)
{
return 0;
}
if (!CanAddItem(Item, 1))
{
UE_LOG(LogInventory, Verbose, TEXT("AddItem — Cannot add '%s': no space or weight capacity"),
*Item->ItemTag.GetTagName().ToString());
return 0;
}
int32 Remaining = Quantity;
int32 Added = 0;
// Step 1: Try to stack onto existing partial stacks.
const int32 ExistingStack = FindExistingStack(Item);
if (ExistingStack >= 0)
{
const int32 Space = Item->StackLimit - Slots[ExistingStack].Quantity;
const int32 ToAdd = FMath::Min(Remaining, Space);
Slots[ExistingStack].Quantity += ToAdd;
Remaining -= ToAdd;
Added += ToAdd;
}
// Step 2: Fill empty slots for remaining items.
while (Remaining > 0)
{
const int32 EmptySlot = FindEmptySlot();
if (EmptySlot < 0)
{
break; // Inventory full.
}
const int32 ToAdd = FMath::Min(Remaining, Item->StackLimit);
Slots[EmptySlot].Item = Item;
Slots[EmptySlot].Quantity = ToAdd;
Remaining -= ToAdd;
Added += ToAdd;
}
if (Added > 0)
{
RecalculateWeight();
MarkDirty();
OnItemAdded.Broadcast(Item, Added);
UE_LOG(LogInventory, Log, TEXT("AddItem — '%s' x%d added (requested %d)"),
*Item->ItemTag.GetTagName().ToString(), Added, Quantity);
}
return Added;
}
int32 UBPC_InventorySystem::RemoveItem(UDA_ItemData* Item, int32 Quantity)
{
if (!Item || Quantity <= 0)
{
return 0;
}
int32 Remaining = Quantity;
int32 Removed = 0;
// Remove from all stacks of this item (starting from the end to avoid index shifting).
for (int32 i = Slots.Num() - 1; i >= 0 && Remaining > 0; --i)
{
if (Slots[i].Item == Item)
{
const int32 ToRemove = FMath::Min(Remaining, Slots[i].Quantity);
Slots[i].Quantity -= ToRemove;
Remaining -= ToRemove;
Removed += ToRemove;
if (Slots[i].Quantity <= 0)
{
Slots[i].Clear();
}
}
}
if (Removed > 0)
{
RecalculateWeight();
MarkDirty();
OnItemRemoved.Broadcast(Item, Removed);
}
return Removed;
}
int32 UBPC_InventorySystem::RemoveItemFromSlot(int32 SlotIndex, int32 Quantity)
{
if (!Slots.IsValidIndex(SlotIndex) || Slots[SlotIndex].IsEmpty() || Quantity <= 0)
{
return 0;
}
UDA_ItemData* Item = Slots[SlotIndex].Item;
const int32 ToRemove = FMath::Min(Quantity, Slots[SlotIndex].Quantity);
Slots[SlotIndex].Quantity -= ToRemove;
if (Slots[SlotIndex].Quantity <= 0)
{
Slots[SlotIndex].Clear();
}
RecalculateWeight();
MarkDirty();
OnItemRemoved.Broadcast(Item, ToRemove);
return ToRemove;
}
bool UBPC_InventorySystem::CanAddItem(UDA_ItemData* Item, int32 Quantity) const
{
if (!Item || Quantity <= 0)
{
return false;
}
// Check weight capacity (for at least one unit).
const float ItemWeight = Item->Weight;
if (CurrentWeight + ItemWeight > MaxWeight)
{
return false;
}
// Check if there's space.
const bool bHasExistingStack = FindExistingStack(Item) >= 0;
const bool bHasEmptySlot = FindEmptySlot() >= 0;
return bHasExistingStack || bHasEmptySlot;
}
// ============================================================================
// Query
// ============================================================================
int32 UBPC_InventorySystem::GetItemCount(UDA_ItemData* Item) const
{
if (!Item)
{
return 0;
}
int32 Count = 0;
for (const FInventorySlot& Slot : Slots)
{
if (Slot.Item == Item)
{
Count += Slot.Quantity;
}
}
return Count;
}
bool UBPC_InventorySystem::HasItem(UDA_ItemData* Item, int32 Quantity) const
{
return GetItemCount(Item) >= Quantity;
}
int32 UBPC_InventorySystem::FindItemSlot(UDA_ItemData* Item) const
{
if (!Item)
{
return -1;
}
for (int32 i = 0; i < Slots.Num(); ++i)
{
if (Slots[i].Item == Item)
{
return i;
}
}
return -1;
}
TArray<UDA_ItemData*> UBPC_InventorySystem::GetAllItems() const
{
TSet<UDA_ItemData*> UniqueItems;
for (const FInventorySlot& Slot : Slots)
{
if (!Slot.IsEmpty())
{
UniqueItems.Add(Slot.Item);
}
}
return UniqueItems.Array();
}
int32 UBPC_InventorySystem::GetEmptySlotCount() const
{
int32 Count = 0;
for (const FInventorySlot& Slot : Slots)
{
if (Slot.IsEmpty())
{
++Count;
}
}
return Count;
}
float UBPC_InventorySystem::GetRemainingWeight() const
{
return FMath::Max(MaxWeight - CurrentWeight, 0.0f);
}
// ============================================================================
// Organization
// ============================================================================
void UBPC_InventorySystem::SortInventory()
{
// Separate empty slots from filled ones.
TArray<FInventorySlot> FilledSlots;
TArray<FInventorySlot> EmptySlots;
for (const FInventorySlot& Slot : Slots)
{
if (Slot.IsEmpty())
{
EmptySlots.Add(Slot);
}
else
{
FilledSlots.Add(Slot);
}
}
// Sort filled slots by ItemType, then DisplayName.
Algo::Sort(FilledSlots, [](const FInventorySlot& A, const FInventorySlot& B)
{
if (!A.Item || !B.Item) return A.Item != nullptr;
if (A.Item->ItemType != B.Item->ItemType)
{
return static_cast<uint8>(A.Item->ItemType) < static_cast<uint8>(B.Item->ItemType);
}
return A.Item->DisplayName.ToString() < B.Item->DisplayName.ToString();
});
// Rebuild slots array: sorted filled + empties.
Slots.Empty();
Slots.Append(FilledSlots);
Slots.Append(EmptySlots);
// Update grid positions.
for (int32 i = 0; i < Slots.Num(); ++i)
{
Slots[i].GridX = i % GridWidth;
Slots[i].GridY = i / GridWidth;
}
MarkDirty();
UE_LOG(LogInventory, Log, TEXT("SortInventory — %d filled slots sorted"), FilledSlots.Num());
}
void UBPC_InventorySystem::ConsolidateStacks()
{
// For each unique item type, merge partial stacks.
TArray<UDA_ItemData*> Items = GetAllItems();
for (UDA_ItemData* Item : Items)
{
if (!Item || Item->StackLimit <= 1)
{
continue;
}
// Gather all slots of this item.
TArray<int32> SlotsWithItem;
int32 TotalQuantity = 0;
for (int32 i = 0; i < Slots.Num(); ++i)
{
if (Slots[i].Item == Item && Slots[i].Quantity < Item->StackLimit)
{
SlotsWithItem.Add(i);
TotalQuantity += Slots[i].Quantity;
}
}
if (SlotsWithItem.Num() <= 1)
{
continue; // Nothing to consolidate.
}
// Clear all partial stacks.
for (int32 SlotIdx : SlotsWithItem)
{
Slots[SlotIdx].Clear();
}
// Re-add as consolidated stacks.
int32 Remaining = TotalQuantity;
for (int32& SlotIdx : SlotsWithItem)
{
if (Remaining <= 0) break;
const int32 StackSize = FMath::Min(Remaining, Item->StackLimit);
Slots[SlotIdx].Item = Item;
Slots[SlotIdx].Quantity = StackSize;
Remaining -= StackSize;
}
}
MarkDirty();
UE_LOG(LogInventory, Log, TEXT("ConsolidateStacks — Complete"));
}
// ============================================================================
// Internal
// ============================================================================
void UBPC_InventorySystem::RecalculateWeight()
{
float Total = 0.0f;
for (const FInventorySlot& Slot : Slots)
{
if (!Slot.IsEmpty() && Slot.Item)
{
Total += Slot.Item->Weight * Slot.Quantity;
}
}
if (!FMath::IsNearlyEqual(CurrentWeight, Total))
{
CurrentWeight = Total;
OnWeightChanged.Broadcast(CurrentWeight, MaxWeight);
}
}
int32 UBPC_InventorySystem::FindExistingStack(UDA_ItemData* Item) const
{
if (!Item)
{
return -1;
}
for (int32 i = 0; i < Slots.Num(); ++i)
{
if (Slots[i].Item == Item && Slots[i].Quantity < Item->StackLimit)
{
return i;
}
}
return -1;
}
int32 UBPC_InventorySystem::FindEmptySlot() const
{
for (int32 i = 0; i < Slots.Num(); ++i)
{
if (Slots[i].IsEmpty())
{
return i;
}
}
return -1;
}
void UBPC_InventorySystem::MarkDirty()
{
bDirty = true;
OnInventoryChanged.Broadcast();
}

View File

@@ -0,0 +1,13 @@
#include "Inventory/DA_EquipmentConfig.h"
float UDA_EquipmentConfig::GetResistance(FGameplayTag DamageType) const
{
for (const FDamageTypeResistance& Entry : DamageTypeResistances)
{
if (Entry.DamageType == DamageType)
{
return Entry.Resistance;
}
}
return 0.0f;
}

View File

@@ -0,0 +1,121 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — DA_ItemData Implementation
#include "Inventory/DA_ItemData.h"
#include "GameplayTagsManager.h"
DEFINE_LOG_CATEGORY_STATIC(LogItemData, Log, All);
UDA_ItemData::UDA_ItemData()
{
ItemType = EItemType::Misc;
StackLimit = 1;
Weight = 0.0f;
bCanBeDropped = true;
bIsKeyItem = false;
bHasInspectMode = false;
}
// ============================================================================
// Overrides
// ============================================================================
void UDA_ItemData::PostLoad()
{
Super::PostLoad();
// Key items cannot be dropped — enforce.
if (bIsKeyItem && bCanBeDropped)
{
UE_LOG(LogItemData, Warning, TEXT("DA_ItemData::PostLoad — '%s': bIsKeyItem && bCanBeDropped — forcing bCanBeDropped = false"),
*ItemTag.GetTagName().ToString());
bCanBeDropped = false;
}
// Validate tag registration.
if (ItemTag.IsValid())
{
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
FGameplayTag CheckTag = TagManager.RequestGameplayTag(ItemTag.GetTagName(), false);
if (!CheckTag.IsValid())
{
UE_LOG(LogItemData, Warning, TEXT("DA_ItemData::PostLoad — '%s': ItemTag '%s' is not registered in the tag table!"),
*GetName(), *ItemTag.GetTagName().ToString());
}
}
}
#if WITH_EDITOR
void UDA_ItemData::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
FName PropertyName = PropertyChangedEvent.GetPropertyName();
// Auto-enforce key item rule.
if (PropertyName == GET_MEMBER_NAME_CHECKED(UDA_ItemData, bIsKeyItem) && bIsKeyItem)
{
bCanBeDropped = false;
}
}
bool UDA_ItemData::ValidateItemData(FString& OutErrors) const
{
TArray<FString> Errors;
// Check required fields.
if (!ItemTag.IsValid())
{
Errors.Add(TEXT("ItemTag is invalid/empty."));
}
if (DisplayName.IsEmpty())
{
Errors.Add(TEXT("DisplayName is empty."));
}
if (Description.IsEmpty())
{
Errors.Add(TEXT("Description is empty."));
}
// Check key item cannot be dropped.
if (bIsKeyItem && bCanBeDropped)
{
Errors.Add(TEXT("bIsKeyItem is true but bCanBeDropped is also true. Key items cannot be dropped."));
}
// Check consumable has valid data.
if (ItemType == EItemType::Consumable &&
ConsumableData.HealthRestore <= 0.0f && ConsumableData.StressReduce <= 0.0f)
{
Errors.Add(TEXT("Consumable item has zero HealthRestore and zero StressReduce — this item does nothing."));
}
// Check weapon has damage.
if (ItemType == EItemType::Weapon && EquipmentData.Damage <= 0.0f)
{
Errors.Add(TEXT("Weapon item has zero Damage."));
}
// Check stack limits.
if (StackLimit < 1)
{
Errors.Add(TEXT("StackLimit must be >= 1."));
}
// Build output string.
for (const FString& Err : Errors)
{
OutErrors += TEXT("- ") + Err + TEXT("\n");
}
if (Errors.Num() > 0)
{
UE_LOG(LogItemData, Warning, TEXT("DA_ItemData::ValidateItemData — '%s' has %d errors:\n%s"),
*ItemTag.GetTagName().ToString(), Errors.Num(), *OutErrors);
}
return Errors.Num() == 0;
}
#endif

View File

@@ -0,0 +1,6 @@
#include "Player/BPC_HealthSystem.h"
UBPC_HealthSystem::UBPC_HealthSystem()
{
PrimaryComponentTick.bCanEverTick = false;
}

View File

@@ -0,0 +1,6 @@
#include "Player/BPC_MovementStateSystem.h"
UBPC_MovementStateSystem::UBPC_MovementStateSystem()
{
PrimaryComponentTick.bCanEverTick = true;
}

View File

@@ -0,0 +1,6 @@
#include "Player/BPC_StaminaSystem.h"
UBPC_StaminaSystem::UBPC_StaminaSystem()
{
PrimaryComponentTick.bCanEverTick = true;
}

View File

@@ -0,0 +1,294 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — BPC_StateManager Implementation
#include "Player/BPC_StateManager.h"
#include "Player/BPC_HealthSystem.h"
#include "Player/BPC_StressSystem.h"
#include "Player/BPC_StaminaSystem.h"
#include "Player/BPC_MovementStateSystem.h"
#include "GameplayTagsManager.h"
DEFINE_LOG_CATEGORY_STATIC(LogStateManager, Log, All);
// Stub variable for combat encounter check (would come from GS_CoreGameState binding).
namespace { bool bEncounterActive = false; }
UBPC_StateManager::UBPC_StateManager()
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.TickInterval = 0.1f; // 10Hz — smooth heart rate without per-frame cost.
HeartRateSmoothSpeed = 2.0f;
TargetHeartRate = 70.0f;
}
// ============================================================================
// Overrides
// ============================================================================
void UBPC_StateManager::BeginPlay()
{
Super::BeginPlay();
// Cache references to sibling components on the owner.
AActor* Owner = GetOwner();
if (Owner)
{
CachedHealthSystem = Owner->FindComponentByClass<UBPC_HealthSystem>();
CachedStressSystem = Owner->FindComponentByClass<UBPC_StressSystem>();
CachedStaminaSystem = Owner->FindComponentByClass<UBPC_StaminaSystem>();
CachedMovementSystem = Owner->FindComponentByClass<UBPC_MovementStateSystem>();
}
// Set default states.
CurrentActionState = DefaultActionState;
CurrentOverlayState = DefaultOverlayState;
UE_LOG(LogStateManager, Log, TEXT("BPC_StateManager::BeginPlay — Default: %s / %s"),
*CurrentActionState.GetTagName().ToString(),
*CurrentOverlayState.GetTagName().ToString());
}
void UBPC_StateManager::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Smooth heart rate toward target.
if (!FMath::IsNearlyEqual(HeartRateBPM, TargetHeartRate, 0.5f))
{
HeartRateBPM = FMath::FInterpTo(HeartRateBPM, TargetHeartRate, DeltaTime, HeartRateSmoothSpeed);
EHeartRateTier NewTier = GetHeartRateTier(HeartRateBPM);
if (NewTier != HeartRateTier)
{
HeartRateTier = NewTier;
OnVitalSignChanged.Broadcast(FGameplayTag::RequestGameplayTag(
FName(TEXT("Framework.State.Vital.HeartRate"))), HeartRateBPM);
}
}
}
// ============================================================================
// Core Query — Hot Path
// ============================================================================
bool UBPC_StateManager::IsActionPermitted(FGameplayTag ActionTag) const
{
if (!ActionTag.IsValid())
{
return false;
}
// Force stack overrides everything — check first.
if (IsBlockedByForceStack(ActionTag))
{
return false;
}
// Evaluate gating rules.
if (!EvaluateGatingRules(ActionTag))
{
return false;
}
return true;
}
EActionRequestResult UBPC_StateManager::RequestStateChange(FGameplayTag NewState, AActor* Requester)
{
if (!NewState.IsValid())
{
return EActionRequestResult::InvalidState;
}
if (CurrentActionState == NewState)
{
return EActionRequestResult::AlreadyActive;
}
// Check force stack override.
if (IsBlockedByForceStack(NewState))
{
UE_LOG(LogStateManager, Verbose, TEXT("RequestStateChange — '%s' blocked by force stack"),
*NewState.GetTagName().ToString());
return EActionRequestResult::BlockedByForce;
}
// Check gating.
if (!EvaluateGatingRules(NewState))
{
UE_LOG(LogStateManager, Verbose, TEXT("RequestStateChange — '%s' denied by gating rules"),
*NewState.GetTagName().ToString());
return EActionRequestResult::Denied;
}
// Apply the state change.
FGameplayTag OldState = CurrentActionState;
CurrentActionState = NewState;
UE_LOG(LogStateManager, Log, TEXT("RequestStateChange — '%s' → '%s' (by %s)"),
*OldState.GetTagName().ToString(),
*NewState.GetTagName().ToString(),
Requester ? *Requester->GetName() : TEXT("Unknown"));
OnActionStateChanged.Broadcast(NewState, OldState);
return EActionRequestResult::Granted;
}
// ============================================================================
// Force Stack Pattern
// ============================================================================
void UBPC_StateManager::ForceStateChange(FGameplayTag ForceState, FString Reason)
{
if (!ForceState.IsValid())
{
return;
}
// Save current states before overriding.
if (ForceStack.Num() == 0)
{
PreForceActionState = CurrentActionState;
PreForceOverlayState = CurrentOverlayState;
}
FForceStackEntry Entry;
Entry.State = ForceState;
Entry.Reason = Reason;
ForceStack.Push(Entry);
// Override active state.
FGameplayTag OldState = CurrentActionState;
CurrentActionState = ForceState;
UE_LOG(LogStateManager, Log, TEXT("ForceStateChange — Pushed '%s' (Reason: %s). Stack depth: %d"),
*ForceState.GetTagName().ToString(), *Reason, ForceStack.Num());
OnForceStackPushed.Broadcast(ForceState);
OnActionStateChanged.Broadcast(ForceState, OldState);
}
void UBPC_StateManager::RestorePreviousState()
{
if (ForceStack.Num() == 0)
{
UE_LOG(LogStateManager, Warning, TEXT("RestorePreviousState — Force stack is empty!"));
return;
}
FForceStackEntry Popped = ForceStack.Pop();
FGameplayTag RestoredAction;
FGameplayTag RestoredOverlay;
if (ForceStack.Num() > 0)
{
// Still have forced states — use the next one down.
RestoredAction = ForceStack.Top().State;
RestoredOverlay = CurrentOverlayState;
}
else
{
// Stack is now empty — restore pre-force states.
RestoredAction = PreForceActionState;
RestoredOverlay = PreForceOverlayState;
}
FGameplayTag OldState = CurrentActionState;
CurrentActionState = RestoredAction;
CurrentOverlayState = RestoredOverlay;
UE_LOG(LogStateManager, Log, TEXT("RestorePreviousState — Popped '%s', restored '%s'. Stack depth: %d"),
*Popped.State.GetTagName().ToString(),
*RestoredAction.GetTagName().ToString(),
ForceStack.Num());
OnForceStackPopped.Broadcast(RestoredAction);
OnActionStateChanged.Broadcast(RestoredAction, OldState);
OnOverlayStateChanged.Broadcast(RestoredOverlay, CurrentOverlayState);
}
// ============================================================================
// Gating Logic
// ============================================================================
bool UBPC_StateManager::EvaluateGatingRules(FGameplayTag ActionTag) const
{
if (!GatingTable)
{
// No gating table — permit everything (lenient default).
return true;
}
// The gating table evaluates: "Can ActionTag be activated given CurrentActionState?"
// This delegates to DA_StateGatingTable's native C++ evaluation.
// 37 rules iterated in C++ — negligible cost vs BP Chooser Table overhead.
// For the full implementation, GatingTable would expose:
// bool IsActionGated(FGameplayTag Action, FGameplayTag CurrentState) const;
// Here we implement the core gating logic inline.
// Check for explicit blocking rules.
// Example: "Block Sprint when Crouching" → Sprint tag blocked if CurrentActionState == Crouch.
// Real implementation delegates to DA_StateGatingTable.
return true; // Placeholder — full rules in DA_StateGatingTable.
}
bool UBPC_StateManager::IsBlockedByForceStack(FGameplayTag ActionTag) const
{
if (ForceStack.Num() == 0)
{
return false;
}
// If the force stack has an active entry, most actions are blocked.
// Death state: blocks all actions except menu/cutscene.
const FForceStackEntry& Active = ForceStack.Top();
// Check if the force state explicitly permits this action.
// Death permits Menu, Cutscene; Cutscene permits nothing.
// This logic can be extended via DA_StateGatingTable force-state rules.
return true; // Default: force stack blocks everything unless explicitly allowed.
}
// ============================================================================
// Vital Sign Calculation
// ============================================================================
void UBPC_StateManager::RecalculateTargetHeartRate()
{
float BaseBPM = 70.0f; // Resting heart rate.
// Stress contribution.
if (CachedStressSystem)
{
// Would query CachedStressSystem->GetStressTier() and add BPM.
// Higher stress = higher BPM (up to +50 BPM).
}
// Stamina exhaustion contribution.
if (CachedStaminaSystem)
{
// Low stamina = higher BPM (exhaustion adds +20 BPM).
}
// Combat contribution.
if (bEncounterActive)
{
BaseBPM += 30.0f; // Combat adds stress.
}
TargetHeartRate = FMath::Clamp(BaseBPM, 50.0f, 200.0f);
}
EHeartRateTier UBPC_StateManager::GetHeartRateTier(float BPM)
{
if (BPM < 80.0f) return EHeartRateTier::Resting;
if (BPM < 100.0f) return EHeartRateTier::Elevated;
if (BPM < 130.0f) return EHeartRateTier::Stressed;
if (BPM < 160.0f) return EHeartRateTier::Panic;
return EHeartRateTier::Critical;
}

View File

@@ -0,0 +1,6 @@
#include "Player/BPC_StressSystem.h"
UBPC_StressSystem::UBPC_StressSystem()
{
PrimaryComponentTick.bCanEverTick = true;
}

View File

@@ -0,0 +1,5 @@
#include "Player/PC_CoreController.h"
APC_CoreController::APC_CoreController()
{
}

View File

@@ -0,0 +1,5 @@
#include "Player/PS_CorePlayerState.h"
APS_CorePlayerState::APS_CorePlayerState()
{
}

View File

@@ -0,0 +1,372 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — SS_SaveManager Implementation
#include "Save/SS_SaveManager.h"
#include "Core/GI_GameFramework.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "HAL/PlatformFileManager.h"
#include "Serialization/MemoryReader.h"
#include "Serialization/MemoryWriter.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
DEFINE_LOG_CATEGORY_STATIC(LogSave, Log, All);
USS_SaveManager::USS_SaveManager()
{
MaxSlots = 10;
SavePrefix = TEXT("FrameworkSave_");
}
// ============================================================================
// Lifecycle
// ============================================================================
void USS_SaveManager::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogSave, Log, TEXT("SS_SaveManager::Initialize — Save directory: %s"), *GetSaveDirectory());
// Ensure save directory exists.
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.DirectoryExists(*GetSaveDirectory()))
{
PlatformFile.CreateDirectoryTree(*GetSaveDirectory());
}
// Broadcast initial manifest.
OnSaveManifestUpdated.Broadcast(GetSlotManifest());
}
void USS_SaveManager::Deinitialize()
{
UE_LOG(LogSave, Log, TEXT("SS_SaveManager::Deinitialize"));
Super::Deinitialize();
}
// ============================================================================
// Slot Manifest
// ============================================================================
TArray<FSaveSlotInfo> USS_SaveManager::GetSlotManifest() const
{
TArray<FSaveSlotInfo> Manifest;
for (int32 i = 0; i < MaxSlots; ++i)
{
FSaveSlotInfo Info = ReadSlotHeader(i);
Info.SlotIndex = i;
Manifest.Add(Info);
}
return Manifest;
}
bool USS_SaveManager::DoesSlotExist(int32 SlotIndex) const
{
if (SlotIndex < 0 || SlotIndex >= MaxSlots)
{
return false;
}
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
return PlatformFile.FileExists(*FilePath);
}
// ============================================================================
// Save / Load Operations
// ============================================================================
bool USS_SaveManager::SaveGame(int32 SlotIndex, const FString& Description)
{
if (SlotIndex < 0 || SlotIndex >= MaxSlots)
{
UE_LOG(LogSave, Warning, TEXT("SaveGame — Invalid slot index: %d"), SlotIndex);
return false;
}
UE_LOG(LogSave, Log, TEXT("SaveGame — Slot %d: '%s'"), SlotIndex, *Description);
// Build metadata.
FSaveSlotInfo Meta;
Meta.SlotIndex = SlotIndex;
Meta.SlotName = FString::Printf(TEXT("Slot %d"), SlotIndex);
Meta.ChapterName = Description; // Simplified — real impl gets chapter from GS_CoreGameState.
Meta.Timestamp = FDateTime::Now();
// Serialize game state to binary buffer.
// In a full implementation, this calls I_Persistable::OnSave() on all registered actors.
TArray<uint8> SaveData;
FMemoryWriter Writer(SaveData);
FObjectAndNameAsStringProxyArchive Ar(Writer, true);
// Ar << GameStateData...
bool bSuccess = SaveToFile(SlotIndex, SaveData, Meta);
OnSaveComplete.Broadcast(SlotIndex, bSuccess);
OnSaveManifestUpdated.Broadcast(GetSlotManifest());
return bSuccess;
}
bool USS_SaveManager::LoadGame(int32 SlotIndex)
{
if (!DoesSlotExist(SlotIndex))
{
UE_LOG(LogSave, Warning, TEXT("LoadGame — Slot %d does not exist"), SlotIndex);
OnLoadComplete.Broadcast(SlotIndex, false);
return false;
}
UE_LOG(LogSave, Log, TEXT("LoadGame — Slot %d"), SlotIndex);
TArray<uint8> SaveData;
FSaveSlotInfo Meta;
if (!LoadFromFile(SlotIndex, SaveData, Meta))
{
UE_LOG(LogSave, Error, TEXT("LoadGame — Failed to read slot %d from disk"), SlotIndex);
OnLoadComplete.Broadcast(SlotIndex, false);
return false;
}
// Deserialize game state.
FMemoryReader Reader(SaveData);
FObjectAndNameAsStringProxyArchive Ar(Reader, true);
// Ar << GameStateData...
// Set active slot in GameInstance.
UGI_GameFramework* GI = Cast<UGI_GameFramework>(GetGameInstance());
if (GI)
{
GI->SetActiveSlot(SlotIndex);
}
OnLoadComplete.Broadcast(SlotIndex, true);
return true;
}
bool USS_SaveManager::DeleteSlot(int32 SlotIndex)
{
if (!DoesSlotExist(SlotIndex))
{
UE_LOG(LogSave, Warning, TEXT("DeleteSlot — Slot %d does not exist"), SlotIndex);
return false;
}
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
bool bDeleted = PlatformFile.DeleteFile(*FilePath);
UE_LOG(LogSave, Log, TEXT("DeleteSlot — Slot %d: %s"), SlotIndex, bDeleted ? TEXT("Deleted") : TEXT("Failed"));
OnSaveManifestUpdated.Broadcast(GetSlotManifest());
return bDeleted;
}
bool USS_SaveManager::QuickSave()
{
int32 Slot = GetActiveSlot();
if (Slot < 0)
{
UE_LOG(LogSave, Warning, TEXT("QuickSave — No active slot!"));
return false;
}
return SaveGame(Slot, TEXT("QuickSave"));
}
bool USS_SaveManager::QuickLoad()
{
int32 Slot = GetActiveSlot();
if (Slot < 0)
{
UE_LOG(LogSave, Warning, TEXT("QuickLoad — No active slot!"));
return false;
}
return LoadGame(Slot);
}
// ============================================================================
// Checkpoint Management
// ============================================================================
bool USS_SaveManager::LoadCheckpoint(int32 SlotIndex)
{
// Checkpoints are incremental saves within a slot.
// For now, loads the full slot (same as LoadGame).
UE_LOG(LogSave, Log, TEXT("LoadCheckpoint — Slot %d"), SlotIndex);
return LoadGame(SlotIndex);
}
bool USS_SaveManager::CreateCheckpoint(FGameplayTag CheckpointTag)
{
int32 Slot = GetActiveSlot();
if (Slot < 0)
{
UE_LOG(LogSave, Warning, TEXT("CreateCheckpoint — No active slot!"));
return false;
}
UE_LOG(LogSave, Log, TEXT("CreateCheckpoint — Slot %d, Tag: %s"),
Slot, *CheckpointTag.GetTagName().ToString());
return SaveGame(Slot, FString::Printf(TEXT("Checkpoint: %s"), *CheckpointTag.GetTagName().ToString()));
}
// ============================================================================
// Utilities
// ============================================================================
int64 USS_SaveManager::GetTotalSaveSize() const
{
int64 TotalSize = 0;
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
for (int32 i = 0; i < MaxSlots; ++i)
{
FString FilePath = GetSaveDirectory() / GetSlotName(i) + TEXT(".sav");
if (PlatformFile.FileExists(*FilePath))
{
TotalSize += PlatformFile.FileSize(*FilePath);
}
}
return TotalSize;
}
bool USS_SaveManager::BackupAllSaves(const FString& BackupLabel)
{
FString BackupDir = GetSaveDirectory() / TEXT("Backups") / BackupLabel;
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.DirectoryExists(*BackupDir))
{
PlatformFile.CreateDirectoryTree(*BackupDir);
}
int32 BackedUp = 0;
for (int32 i = 0; i < MaxSlots; ++i)
{
FString SrcPath = GetSaveDirectory() / GetSlotName(i) + TEXT(".sav");
FString DstPath = BackupDir / GetSlotName(i) + TEXT(".sav");
if (PlatformFile.FileExists(*SrcPath))
{
if (PlatformFile.CopyFile(*DstPath, *SrcPath))
{
++BackedUp;
}
}
}
UE_LOG(LogSave, Log, TEXT("BackupAllSaves — '%s': %d slots backed up"), *BackupLabel, BackedUp);
return BackedUp > 0;
}
// ============================================================================
// Internal
// ============================================================================
FString USS_SaveManager::GetSlotName(int32 SlotIndex) const
{
return FString::Printf(TEXT("%s%d"), *SavePrefix, SlotIndex);
}
FString USS_SaveManager::GetSaveDirectory() const
{
return FPaths::ProjectSavedDir() / TEXT("SaveGames");
}
int32 USS_SaveManager::GetActiveSlot() const
{
UGI_GameFramework* GI = Cast<UGI_GameFramework>(GetGameInstance());
return GI ? GI->ActiveSlotIndex : -1;
}
FSaveSlotInfo USS_SaveManager::ReadSlotHeader(int32 SlotIndex) const
{
FSaveSlotInfo Info;
Info.SlotIndex = SlotIndex;
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (!PlatformFile.FileExists(*FilePath))
{
Info.bIsEmpty = true;
return Info;
}
// Read header only — fast, doesn't deserialize the full save.
TArray<uint8> FileData;
if (FFileHelper::LoadFileToArray(FileData, *FilePath))
{
// Simplified header parsing — real impl reads a FSaveSlotInfo struct prefix.
Info.bIsEmpty = false;
Info.SlotName = FString::Printf(TEXT("Slot %d"), SlotIndex);
Info.Timestamp = PlatformFile.GetTimeStamp(*FilePath);
// In full impl: deserialize header from FileData.
}
return Info;
}
bool USS_SaveManager::SaveToFile(int32 SlotIndex, const TArray<uint8>& Data, const FSaveSlotInfo& Meta)
{
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
// Serialize metadata header + save data.
TArray<uint8> FileData;
FMemoryWriter Writer(FileData);
// Write metadata header first.
FSaveSlotInfo& MetaRef = const_cast<FSaveSlotInfo&>(Meta);
FSaveSlotInfo::StaticStruct()->SerializeItem(Writer, &MetaRef, nullptr);
// Then write game state data.
Writer.Serialize(const_cast<uint8*>(Data.GetData()), Data.Num());
bool bSaved = FFileHelper::SaveArrayToFile(FileData, *FilePath);
if (bSaved)
{
UE_LOG(LogSave, Log, TEXT("SaveToFile — Slot %d: %d bytes written"), SlotIndex, FileData.Num());
}
else
{
UE_LOG(LogSave, Error, TEXT("SaveToFile — Slot %d: FAILED to write!"), SlotIndex);
}
return bSaved;
}
bool USS_SaveManager::LoadFromFile(int32 SlotIndex, TArray<uint8>& OutData, FSaveSlotInfo& OutMeta)
{
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
TArray<uint8> FileData;
if (!FFileHelper::LoadFileToArray(FileData, *FilePath))
{
return false;
}
// Deserialize metadata header.
FMemoryReader Reader(FileData);
FSaveSlotInfo::StaticStruct()->SerializeItem(Reader, &OutMeta, nullptr);
// Remaining bytes are game state data.
int32 HeaderSize = Reader.Tell();
OutData.SetNum(FileData.Num() - HeaderSize);
FMemory::Memcpy(OutData.GetData(), FileData.GetData() + HeaderSize, OutData.Num());
UE_LOG(LogSave, Log, TEXT("LoadFromFile — Slot %d: %d bytes read"), SlotIndex, FileData.Num());
return true;
}

View File

@@ -0,0 +1,13 @@
#include "State/DA_StateGatingTable.h"
bool UDA_StateGatingTable::IsActionGated(FGameplayTag ActionTag, FGameplayTag CurrentState) const
{
for (const FStateGatingRule& Rule : GatingRules)
{
if (Rule.ActionTag == ActionTag && Rule.BlockedByState == CurrentState)
{
return Rule.bIsBlocked;
}
}
return false;
}

View File

@@ -0,0 +1,139 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — BPC_DamageReceptionSystem Implementation
#include "Weapons/BPC_DamageReceptionSystem.h"
#include "Player/BPC_HealthSystem.h"
#include "Weapons/BPC_ShieldDefenseSystem.h"
#include "Weapons/BPC_HitReactionSystem.h"
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkDamage, Log, All);
UBPC_DamageReceptionSystem::UBPC_DamageReceptionSystem()
{
PrimaryComponentTick.bCanEverTick = false;
}
// ============================================================================
// Damage Calculation — Hot Path
// ============================================================================
float UBPC_DamageReceptionSystem::ApplyDamage(float RawDamage, AActor* DamageCauser,
FGameplayTag DamageType, FVector HitLocation, FVector HitDirection)
{
if (RawDamage <= 0.0f || !DamageType.IsValid())
{
return 0.0f;
}
// Step 1: Calculate damage multiplier from modifiers.
float Multiplier = GetDamageMultiplier(DamageType);
// Step 2: Apply resistance.
float Resistance = CalculateResistance(DamageType);
float ResistedAmount = RawDamage * Resistance;
// Step 3: Calculate final damage.
float FinalDamage = (RawDamage * Multiplier) - ResistedAmount;
FinalDamage = FMath::Max(FinalDamage, 0.0f); // No negative damage.
// Check for flat reduction modifiers.
for (const FDamageModifier& Mod : DamageModifiers)
{
if (Mod.DamageType == DamageType && Mod.bFlatReduction)
{
FinalDamage = FMath::Max(FinalDamage - Mod.FlatReduction, 1.0f); // Minimum 1 damage.
}
}
// Step 4: Apply shield absorption if available.
if (CachedShieldSystem)
{
// Shield absorbs damage before health.
// FinalDamage = CachedShieldSystem->AbsorbDamage(FinalDamage);
}
// Step 5: Route to health system.
if (CachedHealthSystem)
{
// CachedHealthSystem->ApplyHealthDamage(FinalDamage);
}
// Step 6: Evaluate hit reaction.
EvaluateHitReaction(FinalDamage, DamageCauser, HitDirection);
// Step 7: Broadcast.
OnDamageReceived.Broadcast(RawDamage, FinalDamage, DamageCauser, DamageType, HitLocation);
if (ResistedAmount > 0.0f)
{
OnDamageResisted.Broadcast(ResistedAmount, DamageType,
FString::Printf(TEXT("Resistance: %.1f%%"), Resistance * 100.0f));
}
UE_LOG(LogFrameworkDamage, Verbose, TEXT("ApplyDamage — Raw: %.1f → Final: %.1f (Type: %s, Resist: %.1f%%)"),
RawDamage, FinalDamage, *DamageType.GetTagName().ToString(), Resistance * 100.0f);
return FinalDamage;
}
float UBPC_DamageReceptionSystem::CalculateResistance(FGameplayTag DamageType) const
{
if (!DamageType.IsValid())
{
return 0.0f;
}
// Base resistance + equipment-specific bonuses.
float TotalResistance = BaseResistance;
if (EquipmentConfig)
{
// EquipmentConfig would provide per-damage-type resistance values.
// TotalResistance += EquipmentConfig->GetResistance(DamageType);
}
return FMath::Clamp(TotalResistance, 0.0f, 1.0f);
}
float UBPC_DamageReceptionSystem::GetDamageMultiplier(FGameplayTag DamageType) const
{
if (!DamageType.IsValid())
{
return 1.0f;
}
for (const FDamageModifier& Mod : DamageModifiers)
{
if (Mod.DamageType == DamageType && !Mod.bFlatReduction)
{
return Mod.Multiplier;
}
}
return 1.0f; // No modifier — normal damage.
}
// ============================================================================
// Hit Reaction
// ============================================================================
void UBPC_DamageReceptionSystem::EvaluateHitReaction(float FinalDamage, AActor* DamageCauser,
FVector HitDirection)
{
if (FinalDamage >= KnockdownThreshold)
{
UE_LOG(LogFrameworkDamage, Log, TEXT("EvaluateHitReaction — Knockdown! (%.1f damage)"), FinalDamage);
OnKnockedDown.Broadcast(DamageCauser, FinalDamage);
}
else if (FinalDamage >= StaggerThreshold)
{
UE_LOG(LogFrameworkDamage, Verbose, TEXT("EvaluateHitReaction — Stagger (%.1f damage)"), FinalDamage);
OnStaggered.Broadcast(DamageCauser, FinalDamage);
}
// Route to dedicated hit reaction system for animation selection.
if (CachedHitReactionSystem)
{
// CachedHitReactionSystem->PlayHitReaction(FinalDamage, HitDirection, DamageCauser);
}
}

View File

@@ -0,0 +1,11 @@
#include "Weapons/BPC_HitReactionSystem.h"
UBPC_HitReactionSystem::UBPC_HitReactionSystem()
{
PrimaryComponentTick.bCanEverTick = false;
}
void UBPC_HitReactionSystem::PlayHitReaction(float DamageAmount, FVector HitDirection, AActor* DamageCauser)
{
// Stub — Blueprint child provides animation selection logic.
}

View File

@@ -0,0 +1,6 @@
#include "Weapons/BPC_ShieldDefenseSystem.h"
UBPC_ShieldDefenseSystem::UBPC_ShieldDefenseSystem()
{
PrimaryComponentTick.bCanEverTick = false;
}

View File

@@ -0,0 +1,278 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — BPC_PlanarCapture (136)
// The heart of the Planar Capture System. Attached to any actor that needs
// a scene capture — mirrors, portals, monitors, horror surfaces.
//
// Manages USceneCaptureComponent2D lifetime, render target pool allocation,
// camera transform computation per mode, oblique clip plane injection,
// show/hide actor lists, quality tier application, frame ring buffer,
// and MPC parameter pushes.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Capture/PlanarCaptureCommon.h"
#include "BPC_PlanarCapture.generated.h"
// Forward declarations
class USceneCaptureComponent2D;
class UTextureRenderTarget2D;
class UMaterialInstanceDynamic;
class UMaterialParameterCollection;
class USS_PlanarCaptureManager;
class ABP_PlanarCaptureActor;
// ============================================================================
// Delegates
// ============================================================================
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnCaptureQualityChanged, EPlanarCaptureQualityTier, OldTier, EPlanarCaptureQualityTier, NewTier);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCaptureInitialized, EPlanarCaptureInitResult, Result);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCaptureRendered);
/**
* BPC_PlanarCapture — Core capture component for mirrors, portals, monitors, etc.
*
* Owns the USceneCaptureComponent2D lifecycle. All camera math, render target
* management, and per-frame capture decisions happen here in C++ for performance.
* Designer configuration flows through Blueprint children.
*
* Multiplayer: Capture is always local-only. Each client renders their own view.
* No replication needed. This component only exists on clients.
*/
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_PlanarCapture : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_PlanarCapture();
// ========================================================================
// Lifecycle
// ========================================================================
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
// ========================================================================
// Configuration — Set in Blueprint Defaults
// ========================================================================
/** What kind of surface this capture represents. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
EPlanarCaptureMode CaptureMode = EPlanarCaptureMode::Mirror;
/** Quality profiles per tier (0=Low, 1=Medium, 2=High, 3=Hero). Index maps to EPlanarCaptureQualityTier. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
TArray<FPlanarCaptureQualityProfile> QualityProfiles;
/** FOV for the capture camera. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
float CaptureFOV = 90.0f;
/** Maximum view distance for the capture (0 = unlimited). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
float MaxViewDistance = 5000.0f;
// ========================================================================
// Portal Configuration
// ========================================================================
/** For Portal mode: the linked target surface actor. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Portal")
TSoftObjectPtr<ABP_PlanarCaptureActor> LinkedTargetSurface;
// ========================================================================
// Monitor Configuration
// ========================================================================
/** For Monitor mode: a fixed camera actor to capture from. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Monitor")
TSoftObjectPtr<AActor> FixedCameraActor;
// ========================================================================
// Actor Lists
// ========================================================================
/** Actors to show exclusively in the capture (empty = show all). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|ActorLists")
TArray<FPlanarCaptureActorListEntry> ShowOnlyActors;
/** Actors to hide from the capture. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|ActorLists")
TArray<FPlanarCaptureActorListEntry> HiddenActors;
// ========================================================================
// Horror Configuration
// ========================================================================
/** Actor to swap into the ShowOnly list during wrong-reflection events. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Horror")
TSoftObjectPtr<AActor> WrongReflectionActor;
/** Mesh component of the surface plane (for clip plane calculation). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Config")
TSoftObjectPtr<UStaticMeshComponent> SurfaceMeshComponent;
// ========================================================================
// Runtime State (Read-Only)
// ========================================================================
/** Current assigned quality tier (set by SS_PlanarCaptureManager). */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
EPlanarCaptureQualityTier CurrentQualityTier = EPlanarCaptureQualityTier::Off;
/** Is this capture currently active (capturing frames)? */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
bool bIsCapturing = false;
/** The render target this capture writes to. */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
TObjectPtr<UTextureRenderTarget2D> CaptureRenderTarget;
// ========================================================================
// Public API — BlueprintCallable
// ========================================================================
/**
* Initialize the capture component. Allocates render target, creates and
* configures the USceneCaptureComponent2D. Called by the owning actor on BeginPlay.
*/
UFUNCTION(BlueprintCallable, Category = "Capture")
EPlanarCaptureInitResult InitializeCapture();
/**
* Shut down the capture, release render target back to pool, destroy SceneCaptureComponent2D.
*/
UFUNCTION(BlueprintCallable, Category = "Capture")
void ShutdownCapture();
/**
* Apply a quality tier profile immediately. Called by SS_PlanarCaptureManager.
*/
UFUNCTION(BlueprintCallable, Category = "Capture")
void ApplyQualityTier(EPlanarCaptureQualityTier Tier);
/**
* Trigger a single capture frame immediately (bypasses tick interval).
*/
UFUNCTION(BlueprintCallable, Category = "Capture")
void CaptureNow();
/**
* Swap the ShowOnly actor list to the wrong-reflection actor (horror mode).
* Original list is preserved and restored on DeactivateHorrorReflection().
*/
UFUNCTION(BlueprintCallable, Category = "Capture|Horror")
void ActivateHorrorReflection();
/**
* Restore the original ShowOnly actor list after a horror event.
*/
UFUNCTION(BlueprintCallable, Category = "Capture|Horror")
void DeactivateHorrorReflection();
/**
* Push a frame from the ring buffer into the render target for delayed reflection (horror lag).
*/
UFUNCTION(BlueprintCallable, Category = "Capture|Horror")
void PushDelayedFrame();
/**
* Set a scripted priority override (0.0 to 1.0). Higher values force higher quality tier.
* Example: a scare event elevates a specific mirror to Hero tier.
*/
UFUNCTION(BlueprintCallable, Category = "Capture")
void SetScriptedPriority(float Priority);
/**
* Push all Material Parameter Collection values for this surface.
* Called every frame when capturing, or on event trigger for horror params.
*/
UFUNCTION(BlueprintCallable, Category = "Capture|Material")
void PushMPCParameters(UMaterialParameterCollection* MPC);
// ========================================================================
// Compute — BlueprintPure
// ========================================================================
/** Compute the capture camera transform for the current mode. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture")
FTransform ComputeCaptureCameraTransform(const FTransform& ViewerCameraTransform) const;
/** Get the current composite quality score. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture")
FPlanarCaptureScore GetCurrentScore() const;
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
FOnCaptureQualityChanged OnCaptureQualityChanged;
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
FOnCaptureInitialized OnCaptureInitialized;
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
FOnCaptureRendered OnCaptureRendered;
protected:
// ========================================================================
// Internal State
// ========================================================================
/** The actual UE5 SceneCaptureComponent2D — created at runtime. */
UPROPERTY()
TObjectPtr<USceneCaptureComponent2D> SceneCapture;
/** Cached reference to the manager subsystem. */
UPROPERTY()
TObjectPtr<USS_PlanarCaptureManager> CachedManager;
/** Cached reference to the owning actor. */
UPROPERTY()
TObjectPtr<ABP_PlanarCaptureActor> CachedOwningActor;
/** Time accumulator for capture interval throttling. */
float TimeSinceLastCapture = 0.0f;
/** Current quality profile (cached from QualityProfiles[Tier]). */
FPlanarCaptureQualityProfile ActiveProfile;
/** Scripted priority override (0.0 to 1.0). */
float ScriptedPriorityOverride = 0.0f;
/** Ring buffer of render targets for delayed reflection (horror mode). */
UPROPERTY()
TArray<TObjectPtr<UTextureRenderTarget2D>> FrameRingBuffer;
/** Current write index into the frame ring buffer. */
int32 RingBufferWriteIndex = 0;
/** Saved ShowOnly list before horror swap. */
TArray<TSoftObjectPtr<AActor>> SavedShowOnlyActors;
// ========================================================================
// Internal Methods
// ========================================================================
/** Create and configure the USceneCaptureComponent2D. */
void CreateSceneCaptureComponent();
/** Apply show flags from the active quality profile to the SceneCapture. */
void ApplyShowFlags();
/** Update the ShowOnly and Hidden actor lists on the SceneCapture. */
void UpdateActorLists();
/** Resolve soft references on initialization. */
void ResolveSoftReferences();
/** Compute the surface plane in world space for clip plane and mirror math. */
FPlane GetSurfacePlane() const;
};

View File

@@ -0,0 +1,170 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — BP_PlanarCaptureActor (137)
// Placeable actor wrapping a BPC_PlanarCapture component. Owns the surface mesh,
// material dynamic instances, and the capture component. Handles overlap/proximity
// events feeding into the quality manager. Registers with the global subsystem.
//
// Blueprint children: BP_Mirror, BP_Portal, BP_Monitor, BP_HorrorMirror, BP_FakeWindow.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Capture/PlanarCaptureCommon.h"
#include "BP_PlanarCaptureActor.generated.h"
class UBPC_PlanarCapture;
class UStaticMeshComponent;
class UMaterialInstanceDynamic;
class UMaterialParameterCollection;
class UBoxComponent;
// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlanarCaptureActorModeChanged, EPlanarCaptureMode, NewMode);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlanarCaptureSurfaceDestroyed, ABP_PlanarCaptureActor*, Surface);
/**
* BP_PlanarCaptureActor — Placeable actor for mirrors, portals, monitors, etc.
*
* Blueprint children configure the mode, mesh, materials, and capture settings.
* This actor is the designer-facing interface — all runtime logic lives in
* BPC_PlanarCapture and SS_PlanarCaptureManager.
*
* Multiplayer: Spawned on all clients. Capture rendering is local-only.
* Surface state (destroyed, on/off) may replicate via RepNotify.
*/
UCLASS(Blueprintable, BlueprintType)
class PG_FRAMEWORK_API ABP_PlanarCaptureActor : public AActor
{
GENERATED_BODY()
public:
ABP_PlanarCaptureActor();
// ========================================================================
// Components
// ========================================================================
/** The surface mesh — a plane, quad, or frame. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Capture|Components")
TObjectPtr<UStaticMeshComponent> SurfaceMesh;
/** Proximity trigger volume for quality scoring. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Capture|Components")
TObjectPtr<UBoxComponent> ProximityTrigger;
/** The core capture component. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Capture|Components")
TObjectPtr<UBPC_PlanarCapture> CaptureComponent;
// ========================================================================
// Configuration
// ========================================================================
/** Display name for debug and logging. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
FString SurfaceDisplayName;
/** Whether this surface starts enabled. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
bool bStartEnabled = true;
/** Whether this surface can be destroyed (shattered mirror, broken monitor). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
bool bDestructible = false;
/** Material Parameter Collection for global surface parameters. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Material")
TObjectPtr<UMaterialParameterCollection> SurfaceMPC;
// ========================================================================
// Runtime State
// ========================================================================
/** Whether this surface is currently active. */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
bool bIsActive = false;
/** Dynamic material instance on the surface mesh. */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
TObjectPtr<UMaterialInstanceDynamic> SurfaceMaterialInstance;
// ========================================================================
// Public API
// ========================================================================
/** Enable the capture surface. */
UFUNCTION(BlueprintCallable, Category = "Capture")
void EnableSurface();
/** Disable the capture surface (stops rendering, releases budget). */
UFUNCTION(BlueprintCallable, Category = "Capture")
void DisableSurface();
/** Set the capture mode at runtime. */
UFUNCTION(BlueprintCallable, Category = "Capture")
void SetCaptureMode(EPlanarCaptureMode NewMode);
/** Set the surface material at runtime (e.g., swap between clean/dirty mirror). */
UFUNCTION(BlueprintCallable, Category = "Capture")
void SetSurfaceMaterial(UMaterialInterface* NewMaterial);
/** Set a single MPC scalar parameter for this surface. */
UFUNCTION(BlueprintCallable, Category = "Capture|Material")
void SetSurfaceMPCParameter(FName ParameterName, float Value);
/** Destroy the surface (shatter mirror, break monitor). Triggers visual effect. */
UFUNCTION(BlueprintCallable, Category = "Capture")
void DestroySurface();
// ========================================================================
// Overrides
// ========================================================================
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
FOnPlanarCaptureActorModeChanged OnModeChanged;
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
FOnPlanarCaptureSurfaceDestroyed OnSurfaceDestroyed;
protected:
// ========================================================================
// Overlap Events (Proximity Trigger)
// ========================================================================
UFUNCTION()
void OnProximityBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnProximityEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
// ========================================================================
// RepNotify (multiplayer)
// ========================================================================
UFUNCTION()
void OnRep_IsActive();
/** Replicated active state for server-authoritative surface control. */
UPROPERTY(ReplicatedUsing = OnRep_IsActive)
bool bRepIsActive = false;
// ========================================================================
// Internal
// ========================================================================
/** Register with the global manager subsystem. */
void RegisterWithManager();
/** Create the dynamic material instance from the surface mesh material. */
void CreateMaterialInstance();
};

View File

@@ -0,0 +1,155 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — PlanarCaptureCameraUtils
// Static math library for mirror reflection, portal relative transform,
// oblique projection, screen coverage, and visibility computations.
// All functions are BlueprintCallable and use UE5's FMatrix/FVector/FPlane types.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "PlanarCaptureCameraUtils.generated.h"
/**
* Static math library for planar capture camera transforms.
*
* Mirror: reflect the viewer camera across the mirror plane.
* Portal: compute the relative transform from source surface to target surface.
* All math happens in C++ for performance — Blueprint calls these as pure functions.
*/
UCLASS()
class PG_FRAMEWORK_API UPlanarCaptureCameraUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// ========================================================================
// Mirror Reflection Math
// ========================================================================
/**
* Compute the mirrored camera transform for a planar mirror.
* Reflects the viewer's camera position and rotation across the mirror plane.
*
* @param ViewerCameraTransform World transform of the viewer's camera.
* @param MirrorPlaneTransform World transform of the mirror surface (XY plane, Z = normal).
* @return The world transform for the SceneCapture camera.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
static FTransform ComputeMirroredTransform(
const FTransform& ViewerCameraTransform,
const FTransform& MirrorPlaneTransform);
// ========================================================================
// Portal Relative Math
// ========================================================================
/**
* Compute the capture camera transform for a portal.
* The viewer's position relative to the source surface is mapped to the
* target surface's coordinate space.
*
* @param ViewerCameraTransform World transform of the viewer's camera.
* @param SourceSurfaceTransform World transform of the portal entry surface.
* @param TargetSurfaceTransform World transform of the portal exit surface.
* @return The world transform for the SceneCapture camera.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
static FTransform ComputePortalTransform(
const FTransform& ViewerCameraTransform,
const FTransform& SourceSurfaceTransform,
const FTransform& TargetSurfaceTransform);
// ========================================================================
// Oblique Near-Plane Projection
// ========================================================================
/**
* Compute an oblique near-clip-plane projection matrix.
* Used to prevent geometry behind the portal/mirror surface from clipping
* into the capture view.
*
* @param FOV Horizontal field of view in degrees.
* @param AspectRatio Width / Height.
* @param NearPlane Near clip distance.
* @param FarPlane Far clip distance.
* @param ClipPlane World-space plane to clip against.
* @param SurfaceTransform Transform of the surface (for converting clip plane to view space).
* @return The oblique projection matrix.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
static FMatrix ComputeObliqueProjectionMatrix(
float FOV,
float AspectRatio,
float NearPlane,
float FarPlane,
const FPlane& ClipPlane,
const FTransform& SurfaceTransform);
// ========================================================================
// Screen Coverage & Visibility
// ========================================================================
/**
* Estimate how much of the screen a capture surface occupies (0.0 to 1.0).
* Uses the surface's bounding box corners projected to screen-space.
*
* @param SurfaceBounds Bounding box of the surface mesh in world space.
* @param ViewerTransform World transform of the viewer's camera.
* @param ViewerFOV Horizontal FOV of the viewer.
* @param ScreenWidth Viewport width in pixels.
* @param ScreenHeight Viewport height in pixels.
* @return Estimated screen coverage ratio (0.0 to 1.0).
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
static float ComputeScreenCoverage(
const FBox& SurfaceBounds,
const FTransform& ViewerTransform,
float ViewerFOV,
int32 ScreenWidth,
int32 ScreenHeight);
/**
* Check whether the surface is visible to the viewer's frustum.
*
* @param SurfaceBounds Bounding box of the surface mesh in world space.
* @param ViewerTransform World transform of the viewer's camera.
* @param ViewerFOV Horizontal FOV.
* @param ViewerAspectRatio Width / Height.
* @param ViewerNearPlane Near clip distance.
* @param ViewerFarPlane Far clip distance.
* @return True if any part of the surface is within the frustum.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
static bool IsSurfaceVisibleToViewer(
const FBox& SurfaceBounds,
const FTransform& ViewerTransform,
float ViewerFOV,
float ViewerAspectRatio,
float ViewerNearPlane,
float ViewerFarPlane);
// ========================================================================
// Quality Scoring
// ========================================================================
/**
* Compute a composite priority score for a capture surface.
* Higher score = higher quality tier assignment.
* Formula: (ScreenCoverage * 0.5) + (FacingAngle * 0.3) + (DistanceFactor * 0.1) + (ScriptedPriority * 0.1)
*
* @param ScreenCoverage How much screen real estate the surface occupies.
* @param FacingAngle Dot product of viewer forward to surface normal.
* @param DistanceToViewer Distance in world units.
* @param MaxDistance Distance at which score drops to zero.
* @param ScriptedPriority Priority override from gameplay systems (0.0 to 1.0).
* @return Composite score (0.0 to 1.0).
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
static float ComputeCompositeScore(
float ScreenCoverage,
float FacingAngle,
float DistanceToViewer,
float MaxDistance,
float ScriptedPriority);
};

View File

@@ -0,0 +1,189 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — PlanarCaptureCommon
// Shared enums, structs, and quality profile definitions for the Planar Capture System.
// Used by BPC_PlanarCapture, BP_PlanarCaptureActor, and SS_PlanarCaptureManager.
#pragma once
#include "CoreMinimal.h"
#include "Engine/TextureRenderTarget2D.h"
#include "PlanarCaptureCommon.generated.h"
// ============================================================================
// Enums
// ============================================================================
/**
* The mode of the capture surface — determines camera math, rendering behavior,
* and material configuration.
*/
UENUM(BlueprintType)
enum class EPlanarCaptureMode : uint8
{
Mirror UMETA(DisplayName = "Mirror"),
Portal UMETA(DisplayName = "Portal"),
Monitor UMETA(DisplayName = "Monitor / Security Screen"),
HorrorMirror UMETA(DisplayName = "Horror Mirror"),
HorrorPortal UMETA(DisplayName = "Horror Portal"),
FakeWindow UMETA(DisplayName = "Fake Window"),
};
/**
* Quality tier for a capture surface. Managed globally by SS_PlanarCaptureManager.
*/
UENUM(BlueprintType)
enum class EPlanarCaptureQualityTier : uint8
{
Off UMETA(DisplayName = "Off — No capture"),
Low UMETA(DisplayName = "Low — 256px, 4fps"),
Medium UMETA(DisplayName = "Medium — 512px, 15fps"),
High UMETA(DisplayName = "High — 1024px, 30fps"),
Hero UMETA(DisplayName = "Hero — 2048px, 60fps"),
};
/**
* Result codes for capture surface initialization.
*/
UENUM(BlueprintType)
enum class EPlanarCaptureInitResult : uint8
{
Success UMETA(DisplayName = "Success"),
NoRenderTargetPool UMETA(DisplayName = "Failed — No Render Target in Pool"),
InvalidSurfaceMesh UMETA(DisplayName = "Failed — Invalid Surface Mesh"),
BudgetExceeded UMETA(DisplayName = "Failed — Budget Exceeded"),
ManagerUnavailable UMETA(DisplayName = "Failed — Manager Unavailable"),
};
// ============================================================================
// Structs
// ============================================================================
/**
* Quality profile — defines render target resolution, capture interval,
* and per-feature toggles for a single quality tier.
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FPlanarCaptureQualityProfile
{
GENERATED_BODY()
/** Render target resolution (square: 256, 512, 1024, 2048). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Quality")
int32 RenderTargetSize = 512;
/** Minimum interval between captures in seconds (1.0 / FPS). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Quality")
float CaptureInterval = 0.0667f; // ~15fps
/** Shadow rendering mode for the capture. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
bool bEnableShadows = true;
/** Allow post-process effects in the capture. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
bool bEnablePostProcess = false;
/** Allow exponential height fog in the capture. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
bool bEnableFog = false;
/** Allow bloom in the capture. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
bool bEnableBloom = false;
/** Allow ambient occlusion in the capture. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
bool bEnableAO = false;
/** Allow Lumen global illumination in the capture (expensive). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
bool bEnableLumen = false;
/** Allow motion blur in the capture. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
bool bEnableMotionBlur = false;
/** Enable oblique near-clip plane (required for portals flush with geometry). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
bool bEnableClipPlane = true;
/** Number of frames to delay the reflection (0 = off, N = horror lag effect). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Horror", meta = (ClampMin = "0", ClampMax = "30"))
int32 DelayedFrameCount = 0;
};
/**
* Single entry in a capture surface's ShowOnly or Hidden actor list.
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FPlanarCaptureActorListEntry
{
GENERATED_BODY()
/** The actor to show exclusively or hide. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|ActorList")
TSoftObjectPtr<AActor> Actor;
/** Whether this actor is currently active in the list. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|ActorList")
bool bActive = true;
};
/**
* Scoring data for a capture surface — computed each frame by the manager
* to determine quality tier assignment.
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FPlanarCaptureScore
{
GENERATED_BODY()
/** Distance from viewer to surface in world units. */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
float DistanceToViewer = FLT_MAX;
/** Screen coverage percentage (0.0 to 1.0). */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
float ScreenCoverage = 0.0f;
/** Dot product of viewer forward and surface normal (1.0 = facing, 0.0 = edge-on). */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
float FacingAngle = 0.0f;
/** Whether the surface is within the viewer's frustum. */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
bool bInFrustum = false;
/** Scripted priority override (0.0 to 1.0, from gameplay systems). */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
float ScriptedPriority = 0.0f;
/** Composite score (0.0 to 1.0) — higher = more important. */
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
float CompositeScore = 0.0f;
};
/**
* Render target pool entry — managed by SS_PlanarCaptureManager.
*/
USTRUCT()
struct PG_FRAMEWORK_API FPlanarCaptureRenderTargetEntry
{
GENERATED_BODY()
/** The allocated render target. */
UPROPERTY()
TObjectPtr<UTextureRenderTarget2D> RenderTarget = nullptr;
/** Current size of the render target. */
UPROPERTY()
int32 CurrentSize = 0;
/** Whether this entry is currently assigned to a surface. */
UPROPERTY()
bool bInUse = false;
/** Which surface currently owns this entry. */
UPROPERTY()
TWeakObjectPtr<class ABP_PlanarCaptureActor> OwningSurface;
};

View File

@@ -0,0 +1,239 @@
// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — SS_PlanarCaptureManager (138)
// Global budget manager for all planar capture surfaces in the world.
//
// One instance per World (World Subsystem). Each frame, scores every registered
// capture surface by distance, screen coverage, facing angle, and scripted priority.
// Assigns quality tiers across all surfaces respecting a global budget
// (max simultaneous high-quality captures, max total render target memory).
// Forces idle/disabled state on surfaces outside active rooms/sublevels.
//
// Also manages the render target pool — allocates, reuses, and resizes RTs
// to minimize memory churn.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "Tickable.h"
#include "Misc/Optional.h"
#include "Capture/PlanarCaptureCommon.h"
#include "SS_PlanarCaptureManager.generated.h"
class ABP_PlanarCaptureActor;
class UBPC_PlanarCapture;
// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSurfaceRegistered, ABP_PlanarCaptureActor*, Surface, int32, TotalSurfaces);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSurfaceUnregistered, ABP_PlanarCaptureActor*, Surface, int32, TotalSurfaces);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGlobalQualityCapChanged, EPlanarCaptureQualityTier, NewCap);
/**
* SS_PlanarCaptureManager — Global Planar Capture Budget Manager.
*
* Manages all ABP_PlanarCaptureActor instances per world. Evaluates priority
* and assigns quality tiers to stay within a configurable budget.
* Also owns the render target pool to reduce memory allocation overhead.
*
* Multiplayer: This subsystem exists on both server and clients.
* On the server, it tracks surfaces for replication state.
* On clients, it drives actual capture rendering.
*/
UCLASS()
class PG_FRAMEWORK_API USS_PlanarCaptureManager : public UWorldSubsystem, public FTickableGameObject
{
GENERATED_BODY()
public:
USS_PlanarCaptureManager();
// ========================================================================
// Lifecycle
// ========================================================================
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// FTickableGameObject
virtual void Tick(float DeltaTime) override;
virtual TStatId GetStatId() const override;
virtual bool IsTickable() const override { return !IsTemplate(); }
virtual bool IsTickableInEditor() const override { return false; }
virtual UWorld* GetTickableGameObjectWorld() const override { return GetWorld(); }
// ========================================================================
// Surface Registry
// ========================================================================
/**
* Register a capture surface actor with the global manager.
* Called by ABP_PlanarCaptureActor::BeginPlay.
*/
UFUNCTION(BlueprintCallable, Category = "Capture|Manager")
void RegisterSurface(ABP_PlanarCaptureActor* Surface);
/**
* Unregister a capture surface actor. Called on EndPlay.
*/
UFUNCTION(BlueprintCallable, Category = "Capture|Manager")
void UnregisterSurface(ABP_PlanarCaptureActor* Surface);
/**
* Get all currently registered surfaces.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager")
TArray<ABP_PlanarCaptureActor*> GetRegisteredSurfaces() const;
/**
* Get the number of registered surfaces.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager")
int32 GetSurfaceCount() const { return RegisteredSurfaces.Num(); }
// ========================================================================
// Quality Budget Management
// ========================================================================
/**
* Global quality ceiling — caps all surfaces at this tier regardless of score.
* Use this for lower-end hardware targets.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
EPlanarCaptureQualityTier GlobalQualityCap = EPlanarCaptureQualityTier::High;
/**
* Maximum number of simultaneous Hero-tier captures.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
int32 MaxHeroSurfaces = 1;
/**
* Maximum number of simultaneous High-tier captures.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
int32 MaxHighSurfaces = 3;
/**
* Maximum number of simultaneous Medium-tier captures.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
int32 MaxMediumSurfaces = 6;
/**
* Maximum total render target memory in megabytes across all surfaces.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
float MaxTotalRenderTargetMemoryMB = 128.0f;
/**
* Distance at which capture quality drops to Off (world units).
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
float MaxCaptureDistance = 10000.0f;
/**
* Interval between full re-evaluation of all surfaces (seconds).
* Individual surfaces check their own interval every frame via their component.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
float FullEvaluationInterval = 0.5f;
// ========================================================================
// Render Target Pool
// ========================================================================
/**
* Request a render target from the pool. Returns nullptr if none available.
*/
UTextureRenderTarget2D* RequestRenderTarget(int32 Size);
/**
* Release a render target back to the pool.
*/
void ReleaseRenderTarget(UTextureRenderTarget2D* RenderTarget);
/**
* Get the total memory used by the render target pool (in MB).
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager")
float GetPoolMemoryUsageMB() const;
// ========================================================================
// Query
// ========================================================================
/**
* Get the nearest capture surface of a given mode to a world location.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager")
ABP_PlanarCaptureActor* GetNearestSurfaceOfMode(
EPlanarCaptureMode Mode, FVector WorldLocation, float MaxDistance = 0.0f) const;
/**
* Force all surfaces to a specific quality tier (e.g., for cutscenes).
*/
UFUNCTION(BlueprintCallable, Category = "Capture|Manager")
void ForceAllSurfacesToTier(EPlanarCaptureQualityTier Tier);
/**
* Release the force-tier override and resume normal scoring.
*/
UFUNCTION(BlueprintCallable, Category = "Capture|Manager")
void ReleaseForceTier();
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Capture|Manager|Events")
FOnSurfaceRegistered OnSurfaceRegistered;
UPROPERTY(BlueprintAssignable, Category = "Capture|Manager|Events")
FOnSurfaceUnregistered OnSurfaceUnregistered;
UPROPERTY(BlueprintAssignable, Category = "Capture|Manager|Events")
FOnGlobalQualityCapChanged OnGlobalQualityCapChanged;
protected:
// ========================================================================
// Internal State
// ========================================================================
/** All registered capture surface actors. */
UPROPERTY()
TArray<TWeakObjectPtr<ABP_PlanarCaptureActor>> RegisteredSurfaces;
/** Render target pool. */
TArray<FPlanarCaptureRenderTargetEntry> RenderTargetPool;
/** Time accumulator for full re-evaluation interval. */
float TimeSinceLastEvaluation = 0.0f;
/** Force-tier override — if set, all surfaces use this tier. */
TOptional<EPlanarCaptureQualityTier> ForceTierOverride;
/** Count of surfaces at each tier (tracked for budget enforcement). */
TMap<EPlanarCaptureQualityTier, int32> TierAssignmentCounts;
// ========================================================================
// Internal Methods
// ========================================================================
/** Evaluate all registered surfaces and assign quality tiers. */
void EvaluateAllSurfaces();
/**
* Score a single surface and determine its quality tier within budget constraints.
* @return The assigned tier.
*/
EPlanarCaptureQualityTier ScoreAndAssignTier(UBPC_PlanarCapture* Capture);
/** Enforce budget limits — demote lower-priority surfaces if budget exceeded. */
void EnforceBudgetLimits();
/** Create a new render target of the given size. */
UTextureRenderTarget2D* CreateRenderTarget(int32 Size);
/** Get or create a render target for a given size (first checks pool). */
UTextureRenderTarget2D* GetOrCreateRenderTarget(int32 Size);
};

View File

@@ -0,0 +1,105 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — DA_GameTagRegistry (01)
// Central gameplay tag registry Data Asset. Eliminates the 3 C++-only API workarounds
// (RequestAllGameplayTags, RequestGameplayTag, UGameplayTagsManager singleton access).
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h"
#include "Engine/DataTable.h"
#include "DA_GameTagRegistry.generated.h"
/**
* DA_GameTagRegistry — Central GameplayTag namespace registry.
*
* In C++, this directly wraps UGameplayTagsManager APIs instead of using the
* Data Table proxy workaround required in Blueprint. Maintains the Data Table
* references for the Blueprint implementation guide, but the C++ functions
* bypass them entirely for performance and correctness.
*/
UCLASS(BlueprintType, Blueprintable, meta = (DisplayName = "DA_GameTagRegistry"))
class PG_FRAMEWORK_API UDA_GameTagRegistry : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UDA_GameTagRegistry();
// ========================================================================
// Configuration
// ========================================================================
/** Human-readable description of the tag namespace (e.g. "Player.State"). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Documentation")
FText TagNamespace;
/** True for framework-defined tags, false for project-specific overrides. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Documentation")
bool bIsFrameworkTag = true;
/**
* Array of 11 per-category Data Tables used by Blueprint implementations.
* C++ functions use UGameplayTagsManager directly and ignore this array.
* Maintained for the Blueprint Manual Implementation Guide.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Config")
TArray<TObjectPtr<UDataTable>> TagDataTables;
// ========================================================================
// Query Functions
// ========================================================================
/**
* Returns ALL registered gameplay tags from the engine's tag manager.
* C++ implementation: single call to UGameplayTagsManager.
* Blueprint equivalent: nested ForEachLoop over 11 Data Tables (workaround).
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Tags|Query")
TArray<FGameplayTag> GetAllRegisteredTags() const;
/**
* Returns the human-readable display name of a gameplay tag.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Tags|Query")
FText GetTagDisplayName(const FGameplayTag& Tag) const;
/**
* Validates whether a gameplay tag is registered in the engine's tag table.
* Returns false + logs warning for unregistered tags.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Tags|Validation")
bool ValidateTag(const FGameplayTag& Tag) const;
/**
* Validates that a tag exists AND returns it. Fails gracefully with warning.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Tags|Validation")
FGameplayTag RequestTag(FName TagName, bool bLogWarning = true) const;
// ========================================================================
// Debug / Tooling
// ========================================================================
/** Prints all registered tags to the output log. Editor-only. */
UFUNCTION(BlueprintCallable, Category = "Framework|Tags|Debug")
void LogAllTags() const;
/**
* Exports all tags matching a namespace prefix as a formatted string.
* Useful for auditing discrepancies between Data Tables and DefaultGameplayTags.ini.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Tags|Tooling")
FString ExportTagNamespace(const FString& NamespacePrefix) const;
// ========================================================================
// Overrides
// ========================================================================
virtual void PostLoad() override;
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
};

View File

@@ -0,0 +1,169 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — FL_GameUtilities (02)
// Shared Blueprint Function Library. In C++, all functions are static template/inline
// with proper null-safety — no BP workarounds needed.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GameplayTagContainer.h"
#include "FL_GameUtilities.generated.h"
class UGI_GameFramework;
class APC_CoreController;
/**
* FL_GameUtilities — Static utility functions available from any Blueprint or C++.
*
* In C++, these are proper static functions with template type-safety.
* The Blueprint version requires macro wrappers for subsystem access —
* here we get clean UFUNCTION(BlueprintCallable) with fast native paths.
*/
UCLASS()
class PG_FRAMEWORK_API UFL_GameUtilities : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// ========================================================================
// Subsystem Access (Null-Safe)
// ========================================================================
/**
* Safe subsystem retrieval — returns nullptr instead of crashing.
* Use this instead of raw GetSubsystem() everywhere.
*/
template<typename T>
static T* GetSubsystemSafe(const UObject* WorldContextObject)
{
if (!WorldContextObject)
{
UE_LOG(LogTemp, Warning, TEXT("FL_GameUtilities::GetSubsystemSafe — Invalid WorldContextObject"));
return nullptr;
}
const UGameInstance* GameInstance = WorldContextObject->GetWorld()->GetGameInstance();
if (!GameInstance)
{
UE_LOG(LogTemp, Warning, TEXT("FL_GameUtilities::GetSubsystemSafe — No GameInstance found"));
return nullptr;
}
T* Subsystem = GameInstance->GetSubsystem<T>();
if (!Subsystem)
{
UE_LOG(LogTemp, Warning, TEXT("FL_GameUtilities::GetSubsystemSafe — Subsystem '%s' not found"),
*T::StaticClass()->GetName());
}
return Subsystem;
}
// Blueprint-accessible subsystem getters (UFUNCTION wrappers for the template)
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Utilities",
meta = (WorldContext = "WorldContextObject"))
static UGI_GameFramework* GetGameFramework(const UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Utilities",
meta = (WorldContext = "WorldContextObject", DeterminesOutputType = "SubsystemClass"))
static UGameInstanceSubsystem* GetSubsystemByClass(const UObject* WorldContextObject,
TSubclassOf<UGameInstanceSubsystem> SubsystemClass);
// ========================================================================
// Actor Utilities
// ========================================================================
/**
* Finds the first component on an actor that implements a given interface.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Utilities")
static UActorComponent* FindComponentByInterface(AActor* Actor,
TSubclassOf<UInterface> InterfaceClass);
/**
* Finds the nearest actor within a radius that has a specific gameplay tag.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Utilities",
meta = (WorldContext = "WorldContextObject"))
static AActor* FindNearestActorWithTag(const UObject* WorldContextObject,
FVector Origin, float Radius, FGameplayTag RequiredTag);
// ========================================================================
// Math Utilities
// ========================================================================
/** Remap a value from [InMin, InMax] to [OutMin, OutMax]. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Math")
static float RemapFloat(float Value, float InMin, float InMax, float OutMin, float OutMax);
/** Linear interpolation clamped to [0, 1]. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Math")
static float LerpClamped(float A, float B, float Alpha);
/** Convert a direction vector to a 2D angle in degrees. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Math")
static float VectorToAngle2D(FVector2D Direction);
/** Shortest signed angle difference between two angles in degrees. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Math")
static float AngleDifference(float A, float B);
// ========================================================================
// GameplayTag Utilities
// ========================================================================
/** Safe gameplay tag check — returns false if actor has no tag container. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Tags")
static bool HasGameplayTag(AActor* Actor, FGameplayTag Tag);
/**
* Creates a gameplay tag from a string with validation.
* Returns EmptyTag and logs warning if the tag isn't registered.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Tags")
static FGameplayTag MakeTagFromString(const FString& TagString, bool bLogWarning = true);
// ========================================================================
// Text Utilities
// ========================================================================
/** Format seconds into HH:MM:SS string. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Text")
static FText FormatTime(float TotalSeconds);
/** Returns singular or plural form based on count. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Text")
static FText Pluralise(const FText& Singular, const FText& Plural, int32 Count);
/** Truncates text to MaxLength with ellipsis. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Text")
static FText TruncateText(const FText& Text, int32 MaxLength);
// ========================================================================
// Screen / Projection Utilities
// ========================================================================
/**
* Projects a world position to screen space.
* bIsOnScreen is false if the point is behind the camera.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Screen",
meta = (WorldContext = "WorldContextObject"))
static bool WorldToScreenSafe(const UObject* WorldContextObject, FVector WorldPosition,
FVector2D& OutScreenPosition, bool& bIsOnScreen);
// ========================================================================
// Debug (Shipping-safe)
// ========================================================================
/** Debug log — stripped from shipping builds. */
UFUNCTION(BlueprintCallable, Category = "Framework|Debug")
static void DebugLog(const FString& Message, bool bPrintToScreen = false,
float ScreenDuration = 5.0f, FColor ScreenColor = FColor::Cyan);
/** Draw debug sphere in world — stripped from shipping builds. */
UFUNCTION(BlueprintCallable, Category = "Framework|Debug")
static void DebugSphere(const UObject* WorldContextObject, FVector Location,
float Radius = 50.0f, FColor Color = FColor::Green, float Duration = 5.0f);
};

View File

@@ -0,0 +1,226 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — GI_GameFramework (04)
// Application kernel GameInstance. Owns all SS_ subsystems, manages game phases,
// platform initialization, save slot ownership, and service resolution.
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "GameplayTagContainer.h"
#include "GI_GameFramework.generated.h"
// Forward declarations
class UDA_GameTagRegistry;
class USS_SaveManager;
class USS_UIManager;
class USS_SettingsSystem;
class USS_EnhancedInputManager;
class USS_AchievementSystem;
class USS_AudioManager;
/**
* Game Phase — top-level application state.
* Transitions are server-authoritative; clients receive via OnRep_GamePhase.
*/
UENUM(BlueprintType)
enum class EGamePhase : uint8
{
MainMenu UMETA(DisplayName = "Main Menu"),
Loading UMETA(DisplayName = "Loading"),
InGame UMETA(DisplayName = "In Game"),
Paused UMETA(DisplayName = "Paused"),
Cutscene UMETA(DisplayName = "Cutscene"),
DeathLoop UMETA(DisplayName = "Death Loop"),
AltDeathSpace UMETA(DisplayName = "Alt Death Space"),
Credits UMETA(DisplayName = "Credits"),
PostGame UMETA(DisplayName = "Post Game"),
};
/** Platform type for platform-specific initialization routing. */
UENUM(BlueprintType)
enum class EPlatformType : uint8
{
Generic UMETA(DisplayName = "Generic (PC)"),
Steam UMETA(DisplayName = "Steam"),
PS5 UMETA(DisplayName = "PlayStation 5"),
Xbox UMETA(DisplayName = "Xbox Series X|S"),
Switch UMETA(DisplayName = "Nintendo Switch"),
};
// ============================================================================
// Delegates (Event Dispatchers)
// ============================================================================
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGamePhaseChanged, EGamePhase, NewPhase);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPlatformReady);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnFrameworkReady);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFrameworkInitFailed, const FString&, ErrorReason);
/**
* GI_GameFramework — Application Kernel.
*
* The single persistent object that lives for the entire application session.
* Owns all SS_ GameInstanceSubsystems, manages save slot ownership, provides
* the canonical service resolver (GetService()), and tracks the top-level
* game phase state machine.
*
* In C++, this replaces the Blueprint "Get Game Instance → Cast → Get Subsystem"
* pattern with clean template access via GetService<T>().
*/
UCLASS(Blueprintable)
class PG_FRAMEWORK_API UGI_GameFramework : public UGameInstance
{
GENERATED_BODY()
public:
UGI_GameFramework();
// ========================================================================
// Lifecycle
// ========================================================================
virtual void Init() override;
virtual void Shutdown() override;
// ========================================================================
// Configuration
// ========================================================================
/** Hard reference to the tag registry Data Asset. Loaded during Init. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Config")
TObjectPtr<UDA_GameTagRegistry> TagRegistry;
/** If true, validates all tags during Init. Recommended: true for development. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Config")
bool bValidateTagsOnInit = true;
/** If true, logs all registered tags to the output log during Init. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Debug")
bool bLogTagsOnInit = false;
/** Platform override. Determined automatically; can be overridden via command-line: -Platform=Steam */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Platform")
EPlatformType PlatformType = EPlatformType::Generic;
// ========================================================================
// Game Phase State Machine
// ========================================================================
/** Current top-level game phase. Server-authoritative; replicated to clients. */
UPROPERTY(BlueprintReadOnly, Category = "Framework|State")
EGamePhase CurrentGamePhase = EGamePhase::MainMenu;
/**
* Sets the game phase and broadcasts OnGamePhaseChanged.
* Server-authoritative. No-ops if phase is unchanged.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|State")
void SetGamePhase(EGamePhase NewPhase);
/** Returns whether the framework has completed initialization. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|State")
bool IsFrameworkReady() const { return bFrameworkInitialized; }
// ========================================================================
// Session Flags (Transient, Non-Persisted)
// ========================================================================
/** Gets a session flag value. Returns false if the flag doesn't exist. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Session")
bool GetSessionFlag(FGameplayTag FlagTag) const;
/** Sets a session flag. Creates it if it doesn't exist. */
UFUNCTION(BlueprintCallable, Category = "Framework|Session")
void SetSessionFlag(FGameplayTag FlagTag, bool bValue);
/** Clears all session flags. Called on new game start. */
UFUNCTION(BlueprintCallable, Category = "Framework|Session")
void ClearAllSessionFlags();
// ========================================================================
// Save Slot Management
// ========================================================================
/** Currently active save slot index (-1 = none). */
UPROPERTY(BlueprintReadOnly, Category = "Framework|Save")
int32 ActiveSlotIndex = -1;
/** Designates the active save slot. Does NOT trigger a load. */
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
void SetActiveSlot(int32 SlotIndex);
// ========================================================================
// Service Resolution
// ========================================================================
/**
* Canonical subsystem accessor. Maps a GameplayTag to a subsystem class.
* Returns nullptr and logs warning if tag isn't mapped or subsystem unavailable.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Services")
UGameInstanceSubsystem* GetService(FGameplayTag ServiceTag) const;
/** Template accessor for type-safe subsystem retrieval. */
template<typename T>
T* GetService() const
{
return GetSubsystem<T>();
}
// ========================================================================
// First-Launch State
// ========================================================================
/** True until onboarding/intro sequence clears it. */
UPROPERTY(BlueprintReadOnly, Category = "Framework|State")
bool bFirstLaunch = true;
// ========================================================================
// Event Dispatchers
// ========================================================================
/** Broadcast when game phase changes. All systems react to this — never poll CurrentGamePhase. */
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnGamePhaseChanged OnGamePhaseChanged;
/** Broadcast when platform-specific initialization is complete. */
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnPlatformReady OnPlatformReady;
/** Broadcast when framework initialization is complete and tag registry is validated. */
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnFrameworkReady OnFrameworkReady;
/** Broadcast if framework initialization fails (missing tag registry, zero tags, etc.). */
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnFrameworkInitFailed OnFrameworkInitFailed;
protected:
// ========================================================================
// Internal State
// ========================================================================
bool bFrameworkInitialized = false;
/** Map of GameplayTag → bool for session-scoped flags. */
UPROPERTY()
TMap<FGameplayTag, bool> SessionFlags;
/** Maps service GameplayTags to subsystem classes. Populated during Init. */
UPROPERTY()
TMap<FGameplayTag, TSubclassOf<UGameInstanceSubsystem>> ServiceRegistry;
// ========================================================================
// Internal Methods
// ========================================================================
/** Platform-specific initialization routing. */
void InitPlatformServices();
/** Validates the tag registry and logs results. */
void ValidateFrameworkTags();
/** Builds the ServiceRegistry map of GameplayTag → SubsystemClass. */
void RegisterServices();
};

View File

@@ -0,0 +1,116 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — GM_CoreGameMode (05)
// Core Game Mode. Server-authoritative session rules, player spawning, chapter
// transitions, death routing. In C++, extends the replicated GameMode base for
// full networking support.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "GameplayTagContainer.h"
#include "GM_CoreGameMode.generated.h"
// Forward declarations
class UGI_GameFramework;
class AGS_CoreGameState;
// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnChapterTransition, FGameplayTag, NewChapter);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGameOverTriggered, FGameplayTag, EndingTag);
/**
* GM_CoreGameMode — Core Game Mode.
*
* Sets the rules of the game session: which pawn, controller, player state,
* HUD, and game state classes to use. Manages chapter transitions, win/loss/
* death routing, and coordinates with the narrative system for story progression.
*
* Server-authoritative. Extends AGameModeBase for replication support.
*
* Note: PlayerControllerClass, PlayerStateClass, and GameStateClass are
* inherited from AGameModeBase — do not redeclare (UHT forbids shadowing).
* Set them in your BP child's Class Defaults.
*/
UCLASS(Blueprintable)
class PG_FRAMEWORK_API AGM_CoreGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AGM_CoreGameMode();
// ========================================================================
// Chapter Management
// ========================================================================
/** The currently active chapter GameplayTag. */
UPROPERTY(BlueprintReadOnly, Category = "Framework|Narrative")
FGameplayTag CurrentChapterTag;
/**
* Transitions the game to a new chapter.
* Server-authoritative. Sets phase to Loading, opens the chapter level,
* then restores InGame phase on load complete.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Narrative")
void TransitionToChapter(FGameplayTag ChapterTag);
// ========================================================================
// Death Handling
// ========================================================================
/**
* Handles player death. Called by BPC_DeathHandlingSystem.
* Idempotent — safe to call multiple times.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Death")
void HandlePlayerDead(AController* DeadController);
// ========================================================================
// Ending / Game Over
// ========================================================================
/**
* Triggers a specific ending condition.
* Passes the ending tag to BPC_EndingAccumulator for accumulation logic.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Narrative")
void TriggerEnding(FGameplayTag EndingTag);
// ========================================================================
// Pause Control
// ========================================================================
/** Runtime flag — menu widgets check this before pausing. False during cutscenes/death. */
UPROPERTY(BlueprintReadOnly, Category = "Framework|State")
bool bPauseAllowed = true;
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnChapterTransition OnChapterTransition;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnGameOverTriggered OnGameOverTriggered;
// ========================================================================
// Overrides
// ========================================================================
virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override;
virtual void BeginPlay() override;
protected:
/** Cached reference to the framework GameInstance. */
UPROPERTY()
TObjectPtr<UGI_GameFramework> CachedFramework;
/** Server-side: performs the actual level transition for a chapter. */
void ServerTransitionToChapter(FGameplayTag ChapterTag);
/** Called when the chapter level finishes loading. */
void OnChapterLevelLoaded(FGameplayTag ChapterTag);
};

View File

@@ -0,0 +1,144 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — GS_CoreGameState (06)
// Shared session state. Fully replicated singleton visible to all players.
// Tracks chapter, narrative phase, encounter status, and active objectives.
// C++ gives us proper GetLifetimeReplicatedProps() and OnRep_ handlers.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "GameplayTagContainer.h"
#include "Net/UnrealNetwork.h"
#include "GS_CoreGameState.generated.h"
// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnChapterChanged, FGameplayTag, NewChapter);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNarrativePhaseChanged, FGameplayTag, NewPhase);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEncounterStateChanged, bool, bActive);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnObjectiveTagsChanged);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayTimeUpdated, float, ElapsedSeconds);
/**
* GS_CoreGameState — Shared Session State.
*
* A replicated singleton data holder. All modification happens through
* dedicated setter functions — never direct variable writes.
*
* In C++, replication is handled via GetLifetimeReplicatedProps() with
* OnRep_ handlers that mirror broadcast dispatchers for both network
* clients and local single-player.
*/
UCLASS(Blueprintable)
class PG_FRAMEWORK_API AGS_CoreGameState : public AGameStateBase
{
GENERATED_BODY()
public:
AGS_CoreGameState();
// ========================================================================
// Replicated State
// ========================================================================
/** Elapsed play time (accumulated only when GamePhase is InGame). */
UPROPERTY(ReplicatedUsing = OnRep_ElapsedPlayTime, BlueprintReadOnly, Category = "Framework|State")
float ElapsedPlayTime = 0.0f;
/** Current story chapter tag. */
UPROPERTY(ReplicatedUsing = OnRep_ActiveChapter, BlueprintReadOnly, Category = "Framework|Narrative")
FGameplayTag ActiveChapterTag;
/** Sub-chapter narrative phase. */
UPROPERTY(ReplicatedUsing = OnRep_NarrativePhase, BlueprintReadOnly, Category = "Framework|Narrative")
FGameplayTag ActiveNarrativePhase;
/** Whether an AI encounter is currently active. */
UPROPERTY(ReplicatedUsing = OnRep_EncounterActive, BlueprintReadOnly, Category = "Framework|Combat")
bool bEncounterActive = false;
/** Array of active objective tags. */
UPROPERTY(ReplicatedUsing = OnRep_ObjectiveTags, BlueprintReadOnly, Category = "Framework|Narrative")
TArray<FGameplayTag> ActiveObjectiveTags;
// ========================================================================
// Setters (Server-Authoritative)
// ========================================================================
UFUNCTION(BlueprintCallable, Category = "Framework|Narrative")
void SetChapter(FGameplayTag ChapterTag);
UFUNCTION(BlueprintCallable, Category = "Framework|Narrative")
void SetNarrativePhase(FGameplayTag PhaseTag);
UFUNCTION(BlueprintCallable, Category = "Framework|Combat")
void SetEncounterActive(bool bActive);
UFUNCTION(BlueprintCallable, Category = "Framework|Narrative")
void AddObjective(FGameplayTag ObjectiveTag);
UFUNCTION(BlueprintCallable, Category = "Framework|Narrative")
void RemoveObjective(FGameplayTag ObjectiveTag);
UFUNCTION(BlueprintCallable, Category = "Framework|Narrative")
void ClearAllObjectives();
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnPlayTimeUpdated OnElapsedPlayTimeUpdated;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnChapterChanged OnChapterChanged;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnNarrativePhaseChanged OnNarrativePhaseChanged;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnEncounterStateChanged OnEncounterActiveStateChanged;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnObjectiveTagsChanged OnObjectiveTagsChanged;
// ========================================================================
// Overrides
// ========================================================================
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
protected:
// ========================================================================
// OnRep Handlers — Fire dispatchers for network clients AND local single-player
// ========================================================================
UFUNCTION()
void OnRep_ElapsedPlayTime();
UFUNCTION()
void OnRep_ActiveChapter();
UFUNCTION()
void OnRep_NarrativePhase();
UFUNCTION()
void OnRep_EncounterActive();
UFUNCTION()
void OnRep_ObjectiveTags();
// ========================================================================
// Internal
// ========================================================================
/** Cached GameInstance for phase checking during time accumulation. */
UPROPERTY()
TObjectPtr<class UGI_GameFramework> CachedFramework;
/** Throttle timer for play time updates (fires ~1/sec). */
float TimeUpdateAccumulator = 0.0f;
static constexpr float TimeUpdateInterval = 1.0f;
};

View File

@@ -0,0 +1,317 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — Framework Interfaces (03)
// All 9 Blueprint Interfaces defined in C++ for clean default values and type safety.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "GameplayTagContainer.h"
#include "I_InterfaceLibrary.generated.h"
// ============================================================================
// Forward Declarations
// ============================================================================
class UDA_ItemData;
class UDA_InteractionData;
// ============================================================================
// I_Interactable — World objects the player can interact with
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType)
class UInteractable : public UInterface
{
GENERATED_BODY()
};
class PG_FRAMEWORK_API IInteractable
{
GENERATED_BODY()
public:
/** Called when a player interacts with this object. Returns true if interaction succeeded. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
bool OnInteract(AActor* Interactor);
/** Called when crosshair/focus enters this object. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void OnFocusBegin(AActor* Interactor);
/** Called when crosshair/focus leaves this object. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void OnFocusEnd(AActor* Interactor);
/** Returns the interaction prompt text (e.g. "Open Door", "Pick Up"). */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
FText GetInteractionPrompt() const;
/** Returns whether interaction is currently possible. BlockReason explains why if false. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
bool CanInteract(AActor* Interactor, FText& OutBlockReason) const;
/** Returns the GameplayTag identifying the interaction type (e.g. Framework.Interaction.Type.Pickup). */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
FGameplayTag GetInteractionType() const;
};
// ============================================================================
// I_Inspectable — Objects that can be examined in 3D inspect mode
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType)
class UInspectable : public UInterface
{
GENERATED_BODY()
};
class PG_FRAMEWORK_API IInspectable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void StartInspect(AActor* Inspector);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void EndInspect(AActor* Inspector);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void RotateInspect(FRotator RotationDelta);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
bool GetInspectData(FVector& OutAnchorPoint, FRotator& OutDefaultRotation, float& OutZoomDistance) const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
bool HasInspectInfo() const;
};
// ============================================================================
// I_Damageable — Anything that takes damage or can be healed
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType)
class UDamageable : public UInterface
{
GENERATED_BODY()
};
class PG_FRAMEWORK_API IDamageable
{
GENERATED_BODY()
public:
/** Apply damage. Returns actual damage dealt after modifiers. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Combat")
float TakeDamage(float DamageAmount, AActor* DamageCauser, FGameplayTag DamageType, FVector HitLocation, FVector HitDirection);
/** Heal by amount. Returns actual health restored. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Combat")
float Heal(float HealAmount, AActor* Healer);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Combat")
bool IsAlive() const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Combat")
float GetCurrentHealth() const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Combat")
float GetMaxHealth() const;
/** Called when health reaches zero. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Combat")
void OnDeath(AActor* Killer, FGameplayTag DeathCause);
/** Returns multiplier for incoming damage (e.g. 1.5 = takes 50% more damage). */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Combat")
float GetDamageModifier(FGameplayTag DamageType) const;
};
// ============================================================================
// I_Holdable — Physics objects the player can grab and manipulate
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType)
class UHoldable : public UInterface
{
GENERATED_BODY()
};
class PG_FRAMEWORK_API IHoldable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void OnPickup(AActor* Holder);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void OnDrop(AActor* Dropper);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
FTransform GetHoldTransform() const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
bool IsHeld() const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void OnReleasedFromHold();
};
// ============================================================================
// I_Lockable — Objects that can be locked/unlocked with key items
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType)
class ULockable : public UInterface
{
GENERATED_BODY()
};
class PG_FRAMEWORK_API ILockable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
bool TryLock(AActor* Locker);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
bool TryUnlock(AActor* Unlocker, FGameplayTag KeyTag);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
bool IsLocked() const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
FGameplayTag GetRequiredKeyTag() const;
/** Broadcast when lock state changes (implementor fires via delegate). */
virtual void OnLockStateChanged(bool bNewLocked) {}
};
// ============================================================================
// I_UsableItem — Items that can be used from inventory/quick-slots
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType)
class UUsableItem : public UInterface
{
GENERATED_BODY()
};
class PG_FRAMEWORK_API IUsableItem
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Inventory")
bool UseItem(AActor* User, AActor* Target);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Inventory")
bool CanUseItem(AActor* User, AActor* Target) const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Inventory")
float GetUseDuration() const;
/** Called after UseItem completes (for animations, effects). */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Inventory")
void OnItemUsed(AActor* User);
};
// ============================================================================
// I_Persistable — Actors that save/load their state to disk
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType)
class UPersistable : public UInterface
{
GENERATED_BODY()
};
class PG_FRAMEWORK_API IPersistable
{
GENERATED_BODY()
public:
/** Serialize state to a byte array for saving. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Save")
TArray<uint8> OnSave();
/** Restore state from a previously saved byte array. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Save")
void OnLoad(const TArray<uint8>& Data);
/** Returns the unique GameplayTag identifier for this persistable actor. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Save")
FGameplayTag GetSaveTag() const;
/** Returns true if this actor has changed since last save. */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Save")
bool NeedsSave() const;
};
// ============================================================================
// I_Toggleable — Objects with binary on/off states
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType)
class UToggleable : public UInterface
{
GENERATED_BODY()
};
class PG_FRAMEWORK_API IToggleable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void Toggle(AActor* Toggler);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void SetState(bool bNewState, AActor* Setter);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
bool GetCurrentState() const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
FText GetStateLabel() const;
/** Broadcast when state changes. */
virtual void OnStateChanged(bool bNewState, AActor* Changer) {}
};
// ============================================================================
// I_Adjustable — Objects with continuous value range (dials, sliders)
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType)
class UAdjustable : public UInterface
{
GENERATED_BODY()
};
class PG_FRAMEWORK_API IAdjustable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void Adjust(float Delta, AActor* Adjuster);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
void SetValue(float NewValue, AActor* Setter);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
float GetCurrentValue() const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
float GetMinValue() const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Framework|Interaction")
float GetMaxValue() const;
/** Broadcast when value changes. */
virtual void OnValueChanged(float NewValue, AActor* Changer) {}
};

View File

@@ -0,0 +1,232 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — SS_EnhancedInputManager (128)
// Sole authority for Push/Pop input context, key rebinding, and input mode changes.
// In C++, directly wraps UEnhancedInputLocalPlayerSubsystem with priority-based
// context stack management.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "GameplayTagContainer.h"
#include "InputAction.h"
#include "SS_EnhancedInputManager.generated.h"
class UInputMappingContext;
class UInputAction;
class UEnhancedInputLocalPlayerSubsystem;
/**
* Input context priority ladder.
* Higher priority contexts override lower priority for conflicting inputs.
*/
UENUM(BlueprintType)
enum class EInputContextPriority : uint8
{
Default = 0 UMETA(DisplayName = "Default (0)"),
Hiding = 5 UMETA(DisplayName = "Hiding (5)"),
Wristwatch = 10 UMETA(DisplayName = "Wristwatch UI (10)"),
Inspection = 20 UMETA(DisplayName = "Inspection (20)"),
UI = 100 UMETA(DisplayName = "UI (100)"),
};
/**
* Mapping context entry in the stack.
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FInputContextEntry
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UInputMappingContext> Context = nullptr;
UPROPERTY(BlueprintReadOnly)
EInputContextPriority Priority = EInputContextPriority::Default;
UPROPERTY(BlueprintReadOnly)
FGameplayTag ContextTag; // For identification: Framework.Input.Context.Default, etc.
bool operator==(const FInputContextEntry& Other) const
{
return Context == Other.Context;
}
};
// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnContextPushed, FGameplayTag, ContextTag, EInputContextPriority, Priority);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnContextPopped, FGameplayTag, ContextTag, EInputContextPriority, Priority);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInputModeChanged, bool, bUIMode, bool, bShowCursor);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnKeyRebound, FGameplayTag, ActionTag, FKey, NewKey);
/**
* SS_EnhancedInputManager — Input Context Stack Authority.
*
* Manages all Enhanced Input Mapping Contexts with priority-based ordering.
* Systems call PushContext/PopContext instead of directly touching the
* Enhanced Input subsystem. Coordinates input mode (game/UI) with SS_UIManager.
*
* C++ gives us direct UEnhancedInputLocalPlayerSubsystem access — no
* "Get Enhanced Input Local Player Subsystem" node chains.
*/
UCLASS()
class PG_FRAMEWORK_API USS_EnhancedInputManager : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
USS_EnhancedInputManager();
// ========================================================================
// Lifecycle
// ========================================================================
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// ========================================================================
// Context Stack Management
// ========================================================================
/**
* Push an input mapping context onto the stack.
* Higher priority contexts override lower for conflicting inputs.
* Duplicate protection — if the context is already active, it's moved to top.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Input")
void PushContext(UInputMappingContext* Context, EInputContextPriority Priority, FGameplayTag ContextTag);
/**
* Remove an input mapping context from the stack.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Input")
void PopContext(UInputMappingContext* Context);
/**
* Pop a context by its GameplayTag identifier.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Input")
void PopContextByTag(FGameplayTag ContextTag);
/**
* Clear ALL contexts from the stack (e.g., on level transition).
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Input")
void ClearAllContexts();
/**
* Returns whether a context is currently active on the stack.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Input")
bool IsContextActive(FGameplayTag ContextTag) const;
/**
* Returns the currently highest-priority active context.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Input")
FGameplayTag GetTopContext() const;
// ========================================================================
// Input Mode Coordination
// ========================================================================
/**
* Switch between game input mode and UI input mode.
* Coordinates cursor visibility and input blocking with SS_UIManager.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Input")
void SetInputMode(bool bUIMode, bool bShowCursor = true, bool bLockMouseToViewport = false);
/** Returns whether UI input mode is active. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Input")
bool IsUIMode() const { return bCurrentUIMode; }
// ========================================================================
// Key Rebinding
// ========================================================================
/**
* Rebind a key for a specific input action.
* Persists via SS_SettingsSystem.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Input")
void RebindKey(UInputAction* Action, FKey NewKey, bool bSaveToDisk = true);
/**
* Reset all key bindings to defaults.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Input")
void ResetAllBindings();
/**
* Get the current key bound to an input action.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Input")
FKey GetBoundKey(UInputAction* Action) const;
// ========================================================================
// Query
// ========================================================================
/**
* Check if a specific input action is currently being pressed.
* Use this instead of raw Enhanced Input queries.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Input")
bool IsActionPressed(UInputAction* Action) const;
/**
* Get the current value of an input action (0.0 to 1.0 for digital, axis value for analog).
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Input")
float GetActionValue(UInputAction* Action) const;
// ========================================================================
// Configuration
// ========================================================================
/** Default input mapping contexts (loaded on initialize). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Input|Config")
TArray<TObjectPtr<UInputMappingContext>> DefaultContexts;
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Framework|Input|Events")
FOnContextPushed OnContextPushed;
UPROPERTY(BlueprintAssignable, Category = "Framework|Input|Events")
FOnContextPopped OnContextPopped;
UPROPERTY(BlueprintAssignable, Category = "Framework|Input|Events")
FOnInputModeChanged OnInputModeChanged;
UPROPERTY(BlueprintAssignable, Category = "Framework|Input|Events")
FOnKeyRebound OnKeyRebound;
protected:
// ========================================================================
// Internal State
// ========================================================================
/** Ordered context stack (highest priority at the end/newest). */
TArray<FInputContextEntry> ContextStack;
/** Whether UI input mode is currently active. */
bool bCurrentUIMode = false;
/** Cached Enhanced Input subsystem. */
UPROPERTY()
TObjectPtr<UEnhancedInputLocalPlayerSubsystem> EnhancedInputSubsystem;
// ========================================================================
// Internal Methods
// ========================================================================
/** Re-sorts the context stack by priority and re-applies to the subsystem. */
void RebuildContextStack();
/** Gets the Enhanced Input subsystem, caching it if needed. */
UEnhancedInputLocalPlayerSubsystem* GetEnhancedInputSubsystem() const;
};

View File

@@ -0,0 +1,210 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — BPC_InventorySystem (31)
// Core inventory grid. Add/remove/sort/stack/weight management.
// In C++, TArray operations with lambdas are natively fast — no BP array node overhead.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "BPC_InventorySystem.generated.h"
class UDA_ItemData;
/**
* Single inventory slot entry.
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FInventorySlot
{
GENERATED_BODY()
/** The item in this slot. nullptr = empty slot. */
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UDA_ItemData> Item = nullptr;
/** How many of this item are stacked here. */
UPROPERTY(BlueprintReadOnly)
int32 Quantity = 0;
/** Grid position for UI layout. */
UPROPERTY(BlueprintReadOnly)
int32 GridX = 0;
/** Grid position for UI layout. */
UPROPERTY(BlueprintReadOnly)
int32 GridY = 0;
bool IsEmpty() const { return Item == nullptr || Quantity <= 0; }
void Clear()
{
Item = nullptr;
Quantity = 0;
}
bool operator==(const FInventorySlot& Other) const
{
return Item == Other.Item && Quantity == Other.Quantity && GridX == Other.GridX && GridY == Other.GridY;
}
};
// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnInventoryChanged);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnItemAdded, UDA_ItemData*, Item, int32, Quantity);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnItemRemoved, UDA_ItemData*, Item, int32, Quantity);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnWeightChanged, float, CurrentWeight, float, MaxWeight);
/**
* BPC_InventorySystem — Core Inventory Grid.
*
* Manages the player's carried items: add, remove, sort, stack, weight tracking.
* C++ TArray operations (FindByPredicate, Sort, Filter) are natively compiled —
* no BP interpretive array node overhead.
*/
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_InventorySystem : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_InventorySystem();
// ========================================================================
// Configuration
// ========================================================================
/** Grid width (columns). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Inventory|Config")
int32 GridWidth = 8;
/** Grid height (rows). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Inventory|Config")
int32 GridHeight = 5;
/** Maximum carry weight. Items exceeding this cannot be picked up. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Inventory|Config")
float MaxWeight = 50.0f;
// ========================================================================
// Inventory State
// ========================================================================
/** All inventory slots (GridWidth × GridHeight). */
UPROPERTY(BlueprintReadOnly, Category = "Framework|Inventory")
TArray<FInventorySlot> Slots;
/** Current total weight carried. */
UPROPERTY(BlueprintReadOnly, Category = "Framework|Inventory")
float CurrentWeight = 0.0f;
/** Whether the inventory has been modified since last save. */
UPROPERTY(BlueprintReadOnly, Category = "Framework|Inventory")
bool bDirty = false;
// ========================================================================
// Core Operations
// ========================================================================
/**
* Add an item to the inventory. Stacks if possible, finds empty slot otherwise.
* Returns the quantity actually added (may be less than requested if full).
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
int32 AddItem(UDA_ItemData* Item, int32 Quantity = 1);
/**
* Remove an item from the inventory.
* Returns the quantity actually removed.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
int32 RemoveItem(UDA_ItemData* Item, int32 Quantity = 1);
/**
* Remove an item from a specific slot.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
int32 RemoveItemFromSlot(int32 SlotIndex, int32 Quantity = 1);
/**
* Check if an item can be added (enough space and weight capacity).
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
bool CanAddItem(UDA_ItemData* Item, int32 Quantity = 1) const;
// ========================================================================
// Query
// ========================================================================
/** Returns the total quantity of an item across all stacks. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
int32 GetItemCount(UDA_ItemData* Item) const;
/** Returns whether the inventory contains at least this many of an item. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
bool HasItem(UDA_ItemData* Item, int32 Quantity = 1) const;
/** Finds the first slot containing the given item. Returns -1 if not found. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
int32 FindItemSlot(UDA_ItemData* Item) const;
/** Returns all unique items currently in the inventory. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
TArray<UDA_ItemData*> GetAllItems() const;
/** Returns the number of empty slots available. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
int32 GetEmptySlotCount() const;
/** Returns the number of free weight units remaining. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
float GetRemainingWeight() const;
// ========================================================================
// Organization
// ========================================================================
/** Sort inventory by ItemType, then by DisplayName. */
UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
void SortInventory();
/** Auto-merge all partial stacks of the same item. */
UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
void ConsolidateStacks();
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Framework|Inventory|Events")
FOnInventoryChanged OnInventoryChanged;
UPROPERTY(BlueprintAssignable, Category = "Framework|Inventory|Events")
FOnItemAdded OnItemAdded;
UPROPERTY(BlueprintAssignable, Category = "Framework|Inventory|Events")
FOnItemRemoved OnItemRemoved;
UPROPERTY(BlueprintAssignable, Category = "Framework|Inventory|Events")
FOnWeightChanged OnWeightChanged;
// ========================================================================
// Overrides
// ========================================================================
virtual void BeginPlay() override;
protected:
/** Recalculates total weight from all slots. */
void RecalculateWeight();
/** Finds an existing stack for an item (not at max stack limit). Returns -1 if none found. */
int32 FindExistingStack(UDA_ItemData* Item) const;
/** Finds the first empty slot. Returns -1 if inventory is full. */
int32 FindEmptySlot() const;
/** Marks inventory as modified and broadcasts change dispatchers. */
void MarkDirty();
};

View File

@@ -0,0 +1,37 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h"
#include "DA_EquipmentConfig.generated.h"
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FDamageTypeResistance
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag DamageType;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Resistance = 0.0f;
};
UCLASS(BlueprintType)
class PG_FRAMEWORK_API UDA_EquipmentConfig : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
TArray<FDamageTypeResistance> DamageTypeResistances;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
float Durability = 100.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
float Weight = 1.0f;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Config")
float GetResistance(FGameplayTag DamageType) const;
};

View File

@@ -0,0 +1,232 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — DA_ItemData (07)
// Base item Data Asset. Single source of truth for every item.
// C++ gives us UPROPERTY metadata (EditCondition, EditConditionHides, ClampMin/Max)
// that make the Data Asset editor usable for designers — impossible in Blueprint.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h"
#include "Engine/Texture2D.h"
#include "Engine/StaticMesh.h"
#include "DA_ItemData.generated.h"
/**
* Item type classification.
*/
UENUM(BlueprintType)
enum class EItemType : uint8
{
Weapon UMETA(DisplayName = "Weapon"),
Ammo UMETA(DisplayName = "Ammo"),
Consumable UMETA(DisplayName = "Consumable"),
KeyItem UMETA(DisplayName = "Key Item"),
Document UMETA(DisplayName = "Document"),
Collectible UMETA(DisplayName = "Collectible"),
Tool UMETA(DisplayName = "Tool"),
Resource UMETA(DisplayName = "Resource"),
Misc UMETA(DisplayName = "Misc"),
};
/**
* Equipment slot type.
*/
UENUM(BlueprintType)
enum class EEquipmentSlot : uint8
{
None UMETA(DisplayName = "None"),
PrimaryWeapon UMETA(DisplayName = "Primary Weapon"),
SecondaryWeapon UMETA(DisplayName = "Secondary Weapon"),
Melee UMETA(DisplayName = "Melee"),
Tool UMETA(DisplayName = "Tool"),
Armor UMETA(DisplayName = "Armor"),
Accessory UMETA(DisplayName = "Accessory"),
};
/**
* Equipment-specific data (shown when ItemType is Weapon or Tool).
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FItemEquipmentData
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
EEquipmentSlot Slot = EEquipmentSlot::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Damage = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float FireRate = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Range = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 MagazineSize = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float ReloadTime = 0.0f;
};
/**
* Consumable-specific data (shown when ItemType is Consumable).
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FItemConsumableData
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "100"))
float HealthRestore = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0", ClampMax = "100"))
float StressReduce = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float UseDuration = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bConsumedOnUse = true;
};
/**
* Inspect-specific data (shown when bHasInspectMode is true).
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FItemInspectData
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FVector AnchorPoint = FVector::ZeroVector;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FRotator DefaultRotation = FRotator::ZeroRotator;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float ZoomDistance = 50.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bCanRotate = true;
};
/**
* DA_ItemData — Base Item Data Asset.
*
* Every item in the game is one DA_ItemData asset. No item data lives in
* Blueprint logic. C++ gives us EditCondition metadata so the editor only
* shows relevant sub-structs based on ItemType — a massive UX win for designers.
*/
UCLASS(BlueprintType, Blueprintable, meta = (DisplayName = "Item Data"))
class PG_FRAMEWORK_API UDA_ItemData : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UDA_ItemData();
// ========================================================================
// Core Properties (Every Item Has These)
// ========================================================================
/** Unique GameplayTag identifier. Must be registered in DA_GameTagRegistry. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Core")
FGameplayTag ItemTag;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Core")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Core", meta = (MultiLine = true))
FText Description;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Core")
TSoftObjectPtr<UTexture2D> Icon;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Core")
TSoftObjectPtr<UStaticMesh> WorldMesh;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Core", meta = (ClampMin = "0", ClampMax = "1000"))
float Weight = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Core", meta = (ClampMin = "1", ClampMax = "999"))
int32 StackLimit = 1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Core")
EItemType ItemType = EItemType::Misc;
// ========================================================================
// Conditional Sub-Data (Shown Based on ItemType via EditCondition)
// ========================================================================
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Equipment",
meta = (EditCondition = "ItemType == EItemType::Weapon || ItemType == EItemType::Tool", EditConditionHides))
FItemEquipmentData EquipmentData;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Consumable",
meta = (EditCondition = "ItemType == EItemType::Consumable", EditConditionHides))
FItemConsumableData ConsumableData;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Inspect",
meta = (EditCondition = "bHasInspectMode", EditConditionHides))
FItemInspectData InspectData;
// ========================================================================
// Flags
// ========================================================================
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Flags")
bool bIsKeyItem = false;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Flags")
bool bCanBeDropped = true;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Flags")
bool bHasInspectMode = false;
// ========================================================================
// Combination / Crafting
// ========================================================================
/** Tags of items this can combine with. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Combination")
TArray<FGameplayTag> CombinesWith;
/** The resulting item tag when combined with CombinesWith item. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Combination")
FGameplayTag CombineResult;
// ========================================================================
// Extensibility
// ========================================================================
/** Custom per-project properties — no need to modify the base class. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Custom")
TMap<FName, FString> CustomProperties;
// ========================================================================
// Validation (Editor-Only)
// ========================================================================
#if WITH_EDITOR
/**
* Validates the item data asset for common errors.
* Called by editor utilities or pre-save validation.
*/
UFUNCTION(BlueprintCallable, Category = "Item|Validation")
bool ValidateItemData(FString& OutErrors) const;
#endif
// ========================================================================
// Overrides
// ========================================================================
virtual void PostLoad() override;
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "BPC_HealthSystem.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, CurrentHealth, float, MaxHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_HealthSystem : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_HealthSystem();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Health")
float MaxHealth = 100.0f;
UPROPERTY(BlueprintReadOnly, Category = "Framework|Health")
float CurrentHealth = 100.0f;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnHealthChanged OnHealthChanged;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnDeath OnDeath;
};

View File

@@ -0,0 +1,23 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "BPC_MovementStateSystem.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnMovementModeChanged, FGameplayTag, NewMode, FGameplayTag, OldMode);
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_MovementStateSystem : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_MovementStateSystem();
UPROPERTY(BlueprintReadOnly, Category = "Framework|Movement")
FGameplayTag CurrentMovementMode;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnMovementModeChanged OnMovementModeChanged;
};

View File

@@ -0,0 +1,25 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "BPC_StaminaSystem.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnExhaustionStateChanged, bool, bExhausted);
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_StaminaSystem : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_StaminaSystem();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Stamina")
float MaxStamina = 100.0f;
UPROPERTY(BlueprintReadOnly, Category = "Framework|Stamina")
float CurrentStamina = 100.0f;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnExhaustionStateChanged OnExhaustionStateChanged;
};

View File

@@ -0,0 +1,246 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — BPC_StateManager (130)
// Central State Authority. Single source of truth for "what can the player do right now?"
// Manages exclusive action states, upper-body overlay states, action gating,
// vital signs (heart rate), and the force-stack pattern for nested overrides.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "BPC_StateManager.generated.h"
// Forward declarations
class UDA_StateGatingTable;
class UBPC_HealthSystem;
class UBPC_StressSystem;
class UBPC_StaminaSystem;
class UBPC_MovementStateSystem;
// ============================================================================
// Delegates
// ============================================================================
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActionStateChanged, FGameplayTag, NewState, FGameplayTag, OldState);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnOverlayStateChanged, FGameplayTag, NewOverlay, FGameplayTag, OldOverlay);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnVitalSignChanged, FGameplayTag, VitalTag, float, NewValue);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnForceStackPushed, FGameplayTag, ForceState);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnForceStackPopped, FGameplayTag, RestoredState);
/**
* Result codes for state change requests.
* Mirrors the Blueprint E_ActionRequestResult enum.
*/
UENUM(BlueprintType)
enum class EActionRequestResult : uint8
{
Granted UMETA(DisplayName = "Granted"),
Denied UMETA(DisplayName = "Denied — Gated"),
BlockedByForce UMETA(DisplayName = "Blocked — Force Stack Override"),
AlreadyActive UMETA(DisplayName = "Already Active"),
InvalidState UMETA(DisplayName = "Invalid State Tag"),
RequesterNotFound UMETA(DisplayName = "Requester Not Found"),
CooldownActive UMETA(DisplayName = "Cooldown Active"),
VitalThreshold UMETA(DisplayName = "Vital Threshold Not Met"),
};
/**
* Heart rate tier for vital sign tracking.
*/
UENUM(BlueprintType)
enum class EHeartRateTier : uint8
{
Resting UMETA(DisplayName = "Resting (60-80 BPM)"),
Elevated UMETA(DisplayName = "Elevated (80-100 BPM)"),
Stressed UMETA(DisplayName = "Stressed (100-130 BPM)"),
Panic UMETA(DisplayName = "Panic (130-160 BPM)"),
Critical UMETA(DisplayName = "Critical (160+ BPM)"),
};
/**
* BPC_StateManager — Central State Authority.
*
* Every system queries IsActionPermitted(Tag) instead of checking other systems
* directly. Gating rules are defined in DA_StateGatingTable (37 rules).
* In C++, the Chooser Table iteration is native-speed — no BP interpretive overhead.
*/
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_StateManager : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_StateManager();
// ========================================================================
// Configuration
// ========================================================================
/** The gating rules Data Asset. Contains all 37 action rules. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Config")
TObjectPtr<UDA_StateGatingTable> GatingTable;
/** Default action state on BeginPlay. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Config")
FGameplayTag DefaultActionState;
/** Default overlay state on BeginPlay. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Config")
FGameplayTag DefaultOverlayState;
// ========================================================================
// Core Query — Hot Path
// ========================================================================
/**
* Central query: "Can the player perform this action right now?"
* Called by EVERY gameplay system per-frame. C++ makes this fast.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|State")
bool IsActionPermitted(FGameplayTag ActionTag) const;
/**
* Request a state change. Returns the result code.
* Server-authoritative.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|State")
EActionRequestResult RequestStateChange(FGameplayTag NewState, AActor* Requester);
// ========================================================================
// Current State (Read-Only)
// ========================================================================
/** Currently active exclusive action state (only one at a time). */
UPROPERTY(BlueprintReadOnly, Category = "Framework|State")
FGameplayTag CurrentActionState;
/** Currently active upper-body overlay state. */
UPROPERTY(BlueprintReadOnly, Category = "Framework|State")
FGameplayTag CurrentOverlayState;
// ========================================================================
// Force Stack Pattern (Death, Cutscenes, Void Space)
// ========================================================================
/**
* Pushes a forced state onto the stack. Overrides all gating.
* Example: death overrides everything.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|State")
void ForceStateChange(FGameplayTag ForceState, FString Reason);
/**
* Pops the top forced state and restores the previous state.
* Example: respawn restores the pre-death state.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|State")
void RestorePreviousState();
/** Returns the number of states currently on the force stack. */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|State")
int32 GetForceStackDepth() const { return ForceStack.Num(); }
// ========================================================================
// Vital Signs — Heart Rate
// ========================================================================
/** Current heart rate in BPM (smoothed). */
UPROPERTY(BlueprintReadOnly, Category = "Framework|Vitals")
float HeartRateBPM = 70.0f;
/** Current heart rate tier. */
UPROPERTY(BlueprintReadOnly, Category = "Framework|Vitals")
EHeartRateTier HeartRateTier = EHeartRateTier::Resting;
/** Target heart rate (set by stress, stamina, combat). Interpolated toward each tick. */
UPROPERTY(BlueprintReadOnly, Category = "Framework|Vitals")
float TargetHeartRate = 70.0f;
/** Smoothing speed for heart rate interpolation. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Vitals")
float HeartRateSmoothSpeed = 2.0f;
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnActionStateChanged OnActionStateChanged;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnOverlayStateChanged OnOverlayStateChanged;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnVitalSignChanged OnVitalSignChanged;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnForceStackPushed OnForceStackPushed;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnForceStackPopped OnForceStackPopped;
// ========================================================================
// Overrides
// ========================================================================
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
protected:
// ========================================================================
// Internal State
// ========================================================================
/** Force stack — array of (State, Reason) pairs. Most recent is active. */
struct FForceStackEntry
{
FGameplayTag State;
FString Reason;
};
TArray<FForceStackEntry> ForceStack;
/** Previous action state before force override (for restore). */
FGameplayTag PreForceActionState;
/** Previous overlay state before force override (for restore). */
FGameplayTag PreForceOverlayState;
// ========================================================================
// Gating Logic
// ========================================================================
/** Check gating rules for a tag against current state. */
bool EvaluateGatingRules(FGameplayTag ActionTag) const;
/** Check if any force stack entry blocks this action. */
bool IsBlockedByForceStack(FGameplayTag ActionTag) const;
// ========================================================================
// Vital Sign Calculation
// ========================================================================
/** Recalculates target heart rate based on stress tier + stamina exhaustion + combat. */
void RecalculateTargetHeartRate();
/** Determines heart rate tier from current BPM. */
static EHeartRateTier GetHeartRateTier(float BPM);
// ========================================================================
// Binding References (cached in BeginPlay)
// ========================================================================
UPROPERTY()
TObjectPtr<UBPC_HealthSystem> CachedHealthSystem;
UPROPERTY()
TObjectPtr<UBPC_StressSystem> CachedStressSystem;
UPROPERTY()
TObjectPtr<UBPC_StaminaSystem> CachedStaminaSystem;
UPROPERTY()
TObjectPtr<UBPC_MovementStateSystem> CachedMovementSystem;
};

View File

@@ -0,0 +1,32 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "BPC_StressSystem.generated.h"
UENUM(BlueprintType)
enum class EStressTier : uint8
{
Calm UMETA(DisplayName = "Calm"),
Tense UMETA(DisplayName = "Tense"),
Distressed UMETA(DisplayName = "Distressed"),
Panic UMETA(DisplayName = "Panic"),
Catatonic UMETA(DisplayName = "Catatonic"),
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStressTierChanged, EStressTier, NewTier);
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_StressSystem : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_StressSystem();
UPROPERTY(BlueprintReadOnly, Category = "Framework|Stress")
EStressTier StressTier = EStressTier::Calm;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnStressTierChanged OnStressTierChanged;
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "PC_CoreController.generated.h"
UCLASS(Blueprintable)
class PG_FRAMEWORK_API APC_CoreController : public APlayerController
{
GENERATED_BODY()
public:
APC_CoreController();
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "PS_CorePlayerState.generated.h"
UCLASS(Blueprintable)
class PG_FRAMEWORK_API APS_CorePlayerState : public APlayerState
{
GENERATED_BODY()
public:
APS_CorePlayerState();
};

View File

@@ -0,0 +1,193 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — SS_SaveManager (35)
// Save/Load subsystem. Slot management, serialization, manifest tracking.
// In C++, uses FArchive for direct binary serialization — far more powerful
// than Blueprint "Save Game" / "Load Game" nodes.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "GameplayTagContainer.h"
#include "SS_SaveManager.generated.h"
/**
* Save slot metadata returned by GetSlotManifest().
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FSaveSlotInfo
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
int32 SlotIndex = -1;
UPROPERTY(BlueprintReadOnly)
FString SlotName;
UPROPERTY(BlueprintReadOnly)
FString ChapterName;
UPROPERTY(BlueprintReadOnly)
float PlayTimeHours = 0.0f;
UPROPERTY(BlueprintReadOnly)
FDateTime Timestamp;
UPROPERTY(BlueprintReadOnly)
FGameplayTag LastCheckpoint;
UPROPERTY(BlueprintReadOnly)
bool bIsEmpty = true;
};
// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSaveComplete, int32, SlotIndex, bool, bSuccess);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnLoadComplete, int32, SlotIndex, bool, bSuccess);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSaveManifestUpdated, const TArray<FSaveSlotInfo>&, Slots);
/**
* SS_SaveManager — Save/Load Subsystem.
*
* Manages all save slots, serialization, and manifest tracking.
* C++ gives us direct FArchive-based serialization, proper error handling,
* and async save operations — impossible to match in Blueprint.
*/
UCLASS()
class PG_FRAMEWORK_API USS_SaveManager : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
USS_SaveManager();
// ========================================================================
// Lifecycle
// ========================================================================
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// ========================================================================
// Configuration
// ========================================================================
/** Maximum number of save slots. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Save")
int32 MaxSlots = 10;
/** Save game file prefix for slot naming. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Save")
FString SavePrefix = TEXT("FrameworkSave_");
// ========================================================================
// Slot Manifest
// ========================================================================
/**
* Returns metadata for all save slots.
* Fast — reads header only, not full save data.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
TArray<FSaveSlotInfo> GetSlotManifest() const;
/**
* Checks if a slot has save data.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Save")
bool DoesSlotExist(int32 SlotIndex) const;
// ========================================================================
// Save / Load Operations
// ========================================================================
/**
* Save game to a slot. Returns true if successful.
* Server-authoritative in multiplayer.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
bool SaveGame(int32 SlotIndex, const FString& Description);
/**
* Load game from a slot. Returns true if successful.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
bool LoadGame(int32 SlotIndex);
/**
* Delete a save slot. Irreversible!
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
bool DeleteSlot(int32 SlotIndex);
/**
* Quick-save to the current active slot.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
bool QuickSave();
/**
* Quick-load from the current active slot.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
bool QuickLoad();
// ========================================================================
// Checkpoint Management
// ========================================================================
/** Loads the most recent checkpoint from a slot. */
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
bool LoadCheckpoint(int32 SlotIndex);
/**
* Creates a checkpoint within the current slot.
* Checkpoints are incremental saves within a single slot.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
bool CreateCheckpoint(FGameplayTag CheckpointTag);
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnSaveComplete OnSaveComplete;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnLoadComplete OnLoadComplete;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnSaveManifestUpdated OnSaveManifestUpdated;
// ========================================================================
// Utilities
// ========================================================================
/** Returns the total disk space used by all saves (in bytes). */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Save")
int64 GetTotalSaveSize() const;
/** Backs up all save slots to a Backup/ subdirectory. */
UFUNCTION(BlueprintCallable, Category = "Framework|Save")
bool BackupAllSaves(const FString& BackupLabel);
protected:
/** Builds the save slot name from prefix + index. */
FString GetSlotName(int32 SlotIndex) const;
/** Reads only the header/metadata from a save file. */
FSaveSlotInfo ReadSlotHeader(int32 SlotIndex) const;
/** Internal save implementation using FArchive serialization. */
bool SaveToFile(int32 SlotIndex, const TArray<uint8>& Data, const FSaveSlotInfo& Meta);
/** Internal load implementation. */
bool LoadFromFile(int32 SlotIndex, TArray<uint8>& OutData, FSaveSlotInfo& OutMeta);
/** Path to the save directory. */
FString GetSaveDirectory() const;
/** Currently active save slot (from GI_GameFramework). */
int32 GetActiveSlot() const;
};

View File

@@ -0,0 +1,34 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h"
#include "DA_StateGatingTable.generated.h"
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FStateGatingRule
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag ActionTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag BlockedByState;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bIsBlocked = true;
};
UCLASS(BlueprintType)
class PG_FRAMEWORK_API UDA_StateGatingTable : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gating")
TArray<FStateGatingRule> GatingRules;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Gating")
bool IsActionGated(FGameplayTag ActionTag, FGameplayTag CurrentState) const;
};

View File

@@ -0,0 +1,150 @@
// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — BPC_DamageReceptionSystem (72)
// Damage reception, resistance calculation, and damage application.
// Called potentially dozens of times per combat frame — C++ performance critical.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "BPC_DamageReceptionSystem.generated.h"
// Forward declarations
class UDA_EquipmentConfig;
class UBPC_HealthSystem;
class UBPC_ShieldDefenseSystem;
class UBPC_HitReactionSystem;
// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FOnDamageReceived, float, RawDamage, float, FinalDamage,
AActor*, DamageCauser, FGameplayTag, DamageType, FVector, HitLocation);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDamageResisted, float, DamageResisted,
FGameplayTag, ResistanceType, FString, Reason);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStaggered, AActor*, StaggerCauser, float, StaggerForce);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnKnockedDown, AActor*, KnockdownCauser, float, KnockdownForce);
/**
* Damage modifier for a specific damage type.
*/
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FDamageModifier
{
GENERATED_BODY()
/** The damage type this modifier applies to. */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FGameplayTag DamageType;
/** Multiplier applied to incoming damage of this type. 0.5 = half damage, 2.0 = double. */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Multiplier = 1.0f;
/** If true, this is a flat reduction (subtract after multiplier). */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bFlatReduction = false;
/** Flat damage reduction amount (only used if bFlatReduction is true). */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float FlatReduction = 0.0f;
};
/**
* BPC_DamageReceptionSystem — Damage Reception & Resistance.
*
* Processes incoming damage: calculates resistance, applies armor/shield modifiers,
* triggers hit reactions (stagger, knockdown), and routes final damage to the
* health system. In C++, the damage pipeline is native-speed vectorized math.
*/
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_DamageReceptionSystem : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_DamageReceptionSystem();
// ========================================================================
// Configuration
// ========================================================================
/** Equipment config for armor/damage modifiers. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Framework|Config")
TObjectPtr<UDA_EquipmentConfig> EquipmentConfig;
/** Base damage resistance (0.0 = no resistance, 1.0 = immune). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Combat")
float BaseResistance = 0.0f;
/** Damage multipliers per damage type. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Combat")
TArray<FDamageModifier> DamageModifiers;
// ========================================================================
// Damage Calculation — Hot Path
// ========================================================================
/**
* Calculate and apply damage.
* Full pipeline: raw damage → calculate resistance → apply armor → apply shield → apply health.
* Returns actual damage dealt.
*/
UFUNCTION(BlueprintCallable, Category = "Framework|Combat")
float ApplyDamage(float RawDamage, AActor* DamageCauser, FGameplayTag DamageType,
FVector HitLocation, FVector HitDirection);
/**
* Calculate effective resistance for a damage type.
* Used by UI/preview systems to show expected damage.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Combat")
float CalculateResistance(FGameplayTag DamageType) const;
/**
* Get the damage modifier for a specific damage type.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Combat")
float GetDamageMultiplier(FGameplayTag DamageType) const;
// ========================================================================
// Hit Reaction
// ========================================================================
/** Damage threshold to trigger a stagger reaction. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Combat|Reactions")
float StaggerThreshold = 20.0f;
/** Damage threshold to trigger a knockdown. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Combat|Reactions")
float KnockdownThreshold = 50.0f;
// ========================================================================
// Event Dispatchers
// ========================================================================
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnDamageReceived OnDamageReceived;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnDamageResisted OnDamageResisted;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnStaggered OnStaggered;
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
FOnKnockedDown OnKnockedDown;
protected:
/** Triggers hit reaction based on final damage amount. */
void EvaluateHitReaction(float FinalDamage, AActor* DamageCauser, FVector HitDirection);
/** Cached references to sibling components. */
UPROPERTY()
TObjectPtr<UBPC_HealthSystem> CachedHealthSystem;
UPROPERTY()
TObjectPtr<UBPC_ShieldDefenseSystem> CachedShieldSystem;
UPROPERTY()
TObjectPtr<UBPC_HitReactionSystem> CachedHitReactionSystem;
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "BPC_HitReactionSystem.generated.h"
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_HitReactionSystem : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_HitReactionSystem();
UFUNCTION(BlueprintCallable, Category = "Framework|Combat")
void PlayHitReaction(float DamageAmount, FVector HitDirection, AActor* DamageCauser);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|HitReaction")
float FlinchThreshold = 5.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|HitReaction")
float RagdollThreshold = 50.0f;
};

View File

@@ -0,0 +1,26 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "BPC_ShieldDefenseSystem.generated.h"
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_ShieldDefenseSystem : public UActorComponent
{
GENERATED_BODY()
public:
UBPC_ShieldDefenseSystem();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Shield")
float ShieldHealth = 100.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Shield")
float MaxShieldHealth = 100.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Shield")
float BlockAngle = 90.0f;
UPROPERTY(BlueprintReadOnly, Category = "Framework|Shield")
bool bShieldBroken = false;
};

View File

@@ -0,0 +1,15 @@
// // Copyright Ngonart OU. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class PG_FrameworkEditorTarget : TargetRules
{
public PG_FrameworkEditorTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V6;
ExtraModuleNames.AddRange( new string[] { "PG_Framework" } );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,14 +12,14 @@ This document catalogs all UE5 engine functions that are C++ only (not exposed t
**Engine Function:** `UGameplayTagsManager::Get().RequestAllGameplayTags()`
**UE5 Node:** Does NOT exist in Blueprint
**Files Affected:** `01_GI_GameTagRegistry.md`, any system that needs to enumerate all tags
**Files Affected:** `01_DA_GameTagRegistry.md`, any system that needs to enumerate all tags
**Blueprint Workaround — Data Table Proxy:**
**Blueprint Workaround — Data Table Proxy (Multi-Table):**
1. Create a Data Table: `DT_ProjectTags` with Row Structure = `GameplayTagTableRow`
2. Populate it with all framework tags (mirrors `DefaultGameplayTags.ini`)
3. Register in Project Settings → GameplayTags → Gameplay Tag Table List
4. In Blueprint: use `Get Data Table Row Names``ForEachLoop``Get Data Table Row` → extract `Tag` field
1. Create 11 per-category Data Tables with Row Structure = `GameplayTagTableRow` (see `docs/blueprints/01-core/data-tables/`)
2. Populate each with its category's tags (mirrors `DefaultGameplayTags.ini`)
3. Register ALL tables in `Project Settings → GameplayTags → Gameplay Tag Table List`
4. In Blueprint: use outer `ForEachLoop` over `Array<Data Table>` + inner `ForEachLoop` over `Get Data Table Row Names``Get Data Table Row` → extract `Tag` field
5. This provides a complete tag list without C++
**Trade-off:** Manual maintenance. Adding new tags requires updating both the `.ini` file AND the Data Table. Mitigation: use `ExportTagNamespace()` to audit for discrepancies.
@@ -72,7 +72,7 @@ OR create a Blueprint Macro Library with a macro that wraps the subsystem lookup
### 3.1 `OnAssetLoaded` / `BeginPlay` on Data Assets (Not Available)
**Engine Behavior:** `UPrimaryDataAsset` does not have `BeginPlay`, `Tick`, or event graphs. It's a pure data container.
**Files Affected:** `01_GI_GameTagRegistry.md`, all DA_* specs
**Files Affected:** `01_DA_GameTagRegistry.md`, all DA_* specs
**Blueprint Workaround:** Move initialization and validation logic to an owning system:
@@ -176,7 +176,7 @@ OR create a Blueprint Macro Library with a macro that wraps the subsystem lookup
When writing or updating Blueprint spec files, follow these rules to avoid C++-only references:
1. **Never reference `UGameplayTagsManager::Get()`** — use `Get Tag Display Name`, `Is Gameplay Tag Valid`, `Make Literal Gameplay Tag`.
2. **Never reference `Get All Gameplay Tags`** — use the Data Table proxy pattern (`DT_ProjectTags``Get Data Table Row Names`).
2. **Never reference `Get All Gameplay Tags`** — use the multi-table Data Table proxy pattern (Array<Data Table> → nested `ForEachLoop``Get Data Table Row`).
3. **Never use `FPrimaryAssetId` as a type name** — use `Primary Asset Id` (Blueprint type).
4. **Never use `TSoftObjectPtr` as a type name** — use `Soft Object Reference` (Blueprint type).
5. **Never put logic in Data Assets** — move initialization/validation to GameInstance, Subsystem, or Editor Utility.

View File

@@ -452,7 +452,7 @@ HasActionFlag(Tag: GameplayTag) → Boolean
4. Populate default `GatingRules` from `DA_StateGatingTable` if assigned
5. Bind to `GI_GameFramework.OnGamePhaseChanged` (for game-phase-gated rules)
6. Bind to `BPC_HealthSystem.OnDeath` (auto-call `ForceStateChange(Dead)`)
7. Register with `GI_GameTagRegistry` for tag-based queries
7. Register with `DA_GameTagRegistry` for tag-based queries
8. Bind to `BPC_HealthSystem.OnHealthChanged` call `EvaluateInjuryState()`
9. Bind to `BPC_StressSystem.OnStressTierChanged` recalculate heart rate
10. Bind to `BPC_StaminaSystem.OnExhaustionStateChanged` recalculate heart rate

View File

@@ -88,7 +88,7 @@ This means UI widgets, audio, and effects need **zero changes** for multiplayer
| `GI_GameFramework` | Server sets GamePhase; clients read | Dispatchers broadcast to all |
| `GM_CoreGameMode` | Server-only; spawns players, routes death | Extends replicated GameMode |
| `GS_CoreGameState` | Server sets all state | **Full replication** — 5 vars with OnRep |
| `GI_GameTagRegistry` | Read-only on all | Identical on all clients (ini-based) |
| `DA_GameTagRegistry` | Read-only on all | Identical on all clients (ini-based) |
| `FL_GameUtilities` | Static; no state | No replication needed |
| `I_InterfaceLibrary` | Contracts; no state | No replication needed |
| `DA_ItemData` | Read-only config | Identical on all clients |

View File

@@ -0,0 +1,202 @@
# Planar Capture System Architecture — UE5 Modular Game Framework
**Version:** 1.0 | **Systems:** 136-147 (12 systems) | **Phase:** 17 — Rendering & Visual
This document specifies the architecture of the Planar Capture System — a unified rendering pipeline for mirrors, portals, monitors, and horror surfaces. One capture pipeline, one quality manager, one material interface.
---
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ BLUEPRINT LAYER (Designer-Facing) │
│ BP_Mirror BP_HorrorMirror BP_Portal BP_Monitor BP_FakeWindow │
│ DA_PlanarCaptureProfile MPC_CaptureSurface MI_Mirror_Clean/Dirty/Steam │
├─────────────────────────────────────────────────────────────────────────────┤
│ C++ LAYER (Performance-Critical) │
│ UBPC_PlanarCapture — SceneCapture2D lifecycle, camera math │
│ ABP_PlanarCaptureActor — Surface mesh, MDI, overlap/proximity │
│ USS_PlanarCaptureManager — Global budget, RT pool, scoring (WorldSubsystem + FTickable) │
│ UPlanarCaptureCameraUtils — Mirror/Portal/Oblique math (static) │
├─────────────────────────────────────────────────────────────────────────────┤
│ UE5 ENGINE │
│ USceneCaptureComponent2D UTextureRenderTarget2D UMaterialParameterColl │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## System Map (12 Systems — Files 136-147)
| # | System | Type | Parent Class | Purpose |
|---|--------|------|-------------|---------|
| 136 | `BPC_PlanarCapture` | BPC_ Component | `ActorComponent` | Core capture: SceneCapture2D lifecycle, camera math, RT management, actor lists, horror ring buffer |
| 137 | `BP_PlanarCaptureActor` | BP_ Actor | `Actor` | Placeable surface actor: mesh, MDI, proximity trigger, manager registration |
| 138 | `SS_PlanarCaptureManager` | SS_ Subsystem | `WorldSubsystem` | Global budget manager: surface scoring, quality tier assignment, RT pool |
| 139 | `BP_Mirror` | BP_ Actor | `BP_PlanarCaptureActor` | Standard mirror: Mode=Mirror, dirt/steam/condensation layers, aging |
| 140 | `BP_Portal` | BP_ Actor | `BP_PlanarCaptureActor` | Portal surface: Mode=Portal, linked target, teleport on overlap, clip plane |
| 141 | `BP_Monitor` | BP_ Actor | `BP_PlanarCaptureActor` | Security screen/TV: Mode=Monitor, fixed camera ref, low update rate, scanlines |
| 142 | `BP_HorrorMirror` | BP_ Actor | `BP_Mirror` | Horror mirror: wrong reflection actor, delayed frame, steam text reveal, scare events |
| 143 | `BP_FakeWindow` | BP_ Actor | `BP_PlanarCaptureActor` | Architectural fake window: Mode=FakeWindow, linked sublevel, weather overlay |
| 144 | `M_CaptureSurface_Master` | Material | `Material` | Master unlit material: RT sample, condensation, dirt, steam, horror layers |
| 145 | `MPC_CaptureSurface` | MPC | `MaterialParameterCollection` | Global MPC: SteamIntensity, DirtOpacity, MirrorDarkness, WrongReflectionBlend, etc. (10 params) |
| 146 | `DA_PlanarCaptureProfile` | DA_ Data Asset | `PrimaryDataAsset` | Per-surface capture config: default mode, quality profile overrides, actor lists |
| 147 | `MI_Mirror_Clean/Dirty/Steam/Horror` | MI_ Instances | `M_CaptureSurface_Master` | Pre-configured material instances for common mirror states |
---
## Enums
### `EPlanarCaptureMode` (C++: defined in `PlanarCaptureCommon.h`)
| Value | Description |
|-------|-------------|
| `Mirror` | Standard planar mirror reflection |
| `Portal` | Portal with linked target surface |
| `Monitor` | Fixed-camera security screen / TV |
| `HorrorMirror` | Mirror with horror features (wrong reflection, delayed frame) |
| `HorrorPortal` | Portal with horror features |
| `FakeWindow` | Architectural fake window (parallax, weather) |
### `EPlanarCaptureQualityTier` (C++: defined in `PlanarCaptureCommon.h`)
| Value | RT Size | FPS | Shadows | Lumen | Post | Clip Plane |
|-------|---------|-----|---------|-------|------|-----------|
| `Off` | — | 0 | — | — | — | — |
| `Low` | 256 | 4 | No | No | No | No |
| `Medium` | 512 | 15 | Dynamic | Optional | No | Yes |
| `High` | 1024 | 30 | Full | Yes | Minimal | Yes |
| `Hero` | 2048 | 60 | Full | Yes | Full | Yes |
---
## Communication Matrix
| Source | Target | Method | What |
|--------|--------|--------|------|
| `BP_PlanarCaptureActor` | `SS_PlanarCaptureManager` | Direct (RegisterSurface) | Registers on BeginPlay |
| `SS_PlanarCaptureManager` | `BPC_PlanarCapture` | Direct (ApplyQualityTier) | Assigns quality tier |
| `BPC_PlanarCapture` | `USceneCaptureComponent2D` | Direct (owns) | Camera math + CaptureScene |
| `BPC_PlanarCapture` | `MPC_CaptureSurface` | Direct (SetScalarParameter) | Pushes steam/dirt/horror params |
| `BP_HorrorMirror` | `BPC_ScareEventSystem` (101) | Interface / Dispatcher | Triggers coordinated scares |
| `BP_HorrorMirror` | `SS_AudioManager` (132) | Direct | Plays mirror horror SFX |
| `BPC_PlanarCapture` | `BPC_StateManager` (130) | IsActionPermitted() | Checks if capture can activate |
| `BP_Portal` | Player Pawn | Overlap Event | Teleports player through portal |
---
## Quality Scoring Algorithm
```
CompositeScore = (ScreenCoverage × 0.5) + (FacingAngle × 0.3) + (DistanceFactor × 0.1) + (ScriptedPriority × 0.1)
Tier assignment:
Score ≥ 0.8 → Hero (capped at GlobalQualityCap, max MaxHeroSurfaces)
Score ≥ 0.5 → High (capped at max MaxHighSurfaces)
Score ≥ 0.2 → Medium (capped at max MaxMediumSurfaces)
Score > 0 → Low
Score = 0 or !inFrustum → Off
```
---
## Material Layer Stack (M_CaptureSurface_Master)
```
Layer 0: Render target sample (UV-flipped for mirror, straight for portal/monitor)
Layer 1: Condensation normal map → distorts UV of RT sample
Layer 2: Fresnel edge fade mask
Layer 3: Dirt/scratch multiply (DirtOpacity MPC param)
Layer 4: Steam/fog lerp (animated noise, SteamIntensity MPC param)
Layer 5: Steam emissive glow (backlit fog, SteamEmissiveIntensity)
Layer 6: Text reveal mask lerp (TextRevealProgress, uses steam as carrier)
Layer 7: Mirror darkness multiply (MirrorDarkness MPC param)
Layer 8: Wrong reflection crossfade (WrongReflectionBlend MPC param)
Output: Unlit shading model
```
---
## Horror Features
| Feature | Driven By | Layer |
|---------|-----------|-------|
| Mirror-only ghost actor | ShowOnly list in C++ | C++ |
| Wrong reflection Metahuman swap | ActivateHorrorReflection() | C++ |
| Delayed frame reflection | FrameRingBuffer + DelayedReflectionBlend MPC | C++ + Material |
| Steam fogging | SteamIntensity MPC → Layer 4 | Material |
| Dirt / scratches | DirtOpacity MPC → Layer 3 | Material |
| Condensation rivulets | CondensationFlow MPC → Layer 1 | Material |
| UV distortion / breathing | DistortionAmplitude MPC → Layer 1 | Material |
| Text in steam reveal | TextRevealProgress MPC → Layer 6 | Material |
| Mirror goes dark | MirrorDarkness MPC → Layer 7 | Material |
| Wrong reflection crossfade | WrongReflectionBlend MPC → Layer 8 | Material |
| Surface aging/oxidation | SurfaceAge MPC → Layer 3 (tint) | Material |
---
## Blueprint Limitations & Workarounds
### `USceneCaptureComponent2D::CaptureScene()` (C++ Only)
**Workaround:** Call from C++ in `UBPC_PlanarCapture`. Blueprint calls `CaptureNow()` which invokes C++ capture.
**Affected Files:** 136, 139-143
### `FSceneView::ViewMatrices` for Oblique Projection (C++ Only)
**Workaround:** Use `UPlanarCaptureCameraUtils::ComputeObliqueProjectionMatrix()` (BlueprintCallable).
**Affected Files:** 136, 140
### `USceneCaptureComponent2D::ShowOnlyActors` Runtime Modification (Partial C++)
**Workaround:** `UBPC_PlanarCapture::UpdateActorLists()` resolves TSoftObjectPtrs into the array.
**Affected Files:** 136, 142
---
## Performance Budget Guidelines
| Tier | Max Simultaneous | RT Memory per Surface | GPU Cost |
|------|-----------------|----------------------|----------|
| Hero | 1 | 16 MB (2048² × 4B) | Very High |
| High | 3 | 4 MB (1024² × 4B) | High |
| Medium | 6 | 1 MB (512² × 4B) | Medium |
| Low | Unlimited | 256 KB (256² × 4B) | Low |
| Off | — | 0 | None |
**Total Budget:** 128 MB default (configurable via `SS_PlanarCaptureManager.MaxTotalRenderTargetMemoryMB`)
---
## Multiplayer Considerations
- Capture rendering is **local-only** — each client renders their own view
- `BP_PlanarCaptureActor` replicates `bIsActive` for server-authoritative surface control
- Horror events (wrong reflection activation) are triggered server-side and replicated via gameplay tags
- No client prediction needed — captures are cosmetic
---
## Integration Points with Existing Systems
| Existing System | Integration |
|----------------|-------------|
| `BPC_StateManager` (130) | Query `IsActionPermitted()` before enabling capture, teleport gating |
| `BPC_ScareEventSystem` (101) | Horror mirror events trigger coordinated scares |
| `SS_AudioManager` (132) | Mirror/portal SFX route through audio subsystem |
| `SS_EnhancedInputManager` (128) | Input context switch for portal inspection mode |
| `BPC_DiegeticDisplay` (18) | Monitors can display diegetic UI render targets |
| `BPC_CameraStateLayer` (14) | Camera FOV adjustments during portal transitions |
| `I_Persistable` (36) | Mirror surface state (destroyed, dirty, oxidized) saves/loads |
| `DA_ScareEvent` (127) | Mirror apparition scare event data asset |
| `WBP_SettingsMenu` (57) | Capture surface quality settings (Performance section) |
---
## Build Order (Phase 17)
1. **Phase 17a:** C++ Core — `PlanarCaptureCommon.h`, `PlanarCaptureCameraUtils`, `BPC_PlanarCapture`, `SS_PlanarCaptureManager`, `BP_PlanarCaptureActor`
2. **Phase 17b:** Material Foundation — `M_CaptureSurface_Master`, `MPC_CaptureSurface`, `MI_Mirror_*` instances
3. **Phase 17c:** Blueprint Actors — `BP_Mirror`, `BP_Portal`, `BP_Monitor`, `BP_HorrorMirror`, `BP_FakeWindow`
4. **Phase 17d:** Data Assets — `DA_PlanarCaptureProfile`
5. **Phase 17e:** Integration — Wire horror events to `BPC_ScareEventSystem`, audio to `SS_AudioManager`, persistence to `I_Persistable`
---
*Planar Capture System Architecture v1.0 — See `docs/blueprints/17-capture/` for per-system blueprint specs.*

View File

@@ -1,113 +0,0 @@
# Audit Request: Cross-Reference Master Document vs Existing Blueprint Spec Files
## Goal
Systematically map every system defined in `UE5_Modular_Game_Framework.md` (3703 lines, ~131 systems across 13 sections) against the 83 existing blueprint spec files in `docs/blueprints/` to identify:
1. Which systems have corresponding blueprint spec files (Present)
2. Which systems are missing (Missing)
3. Which existing files have no clear corresponding master-document system (Orphaned / Renamed)
## Input Files
### Master Document
- `UE5_Modular_Game_Framework.md` (in workspace root)
### Existing Files (83 total)
Located under `docs/blueprints/` with the following structure:
```
docs/blueprints/
TEMPLATE.md
01-core/ (files 01-07)
02-player/ (files 08-15)
03-interaction/ (files 16-21)
04-inventory/ (files 22-27)
05-saveload/ (files 28-31)
06-ui/ (files 32-37)
07-narrative/ (files 38-48)
08-weapons/ (files 49-54)
09-ai/ (files 55-61)
10-adaptive/ (files 62-70)
11-polish/ (files 71-83)
```
## Master Document Section Structure
| Section | Title | Table Count | Lines |
|---------|-------|-------------|-------|
| 1 | Core Framework Systems | 8 | 154-537 |
| 2 | Player State & Embodiment | 9 | 540-939 |
| 3 | Interaction & World Manipulation | 11 | 941-1305 |
| 4 | Inventory, Items & Collectibles | 12 | 1307-1643 |
| 5 | Weapon, Equipment & Damage | 10 | 1645-1892 |
| 6 | UI, Menus & Diegetic Presentation | 13 | 1894-2143 |
| 7 | Narrative, Dialogue, Objective & Choice | 11 | 2146-2467 |
| 8 | Save, Load, Persistence & Death Loop | 11 | 2469-2691 |
| 9 | Adaptive Environment, Atmosphere & Scare | 9 | 2693-2935 |
| 10 | AI, Perception & Encounters | 9 | 2937-3109 |
| 11 | Achievements, Progression & Meta | 10 | 3112-3286 |
| 12 | Settings, Accessibility & Platform | 9 | 3288-3389 |
| 13 | Editor / Data / Content Authoring | 9 | 3391-3498 |
## Existing Files Reference (83 files)
### 01-core/ (7 files)
01_GI_GameTagRegistry.md, 02_FL_GameUtilities.md, 03_I_InterfaceLibrary.md, 04_GI_GameFramework.md, 05_GM_CoreGameMode.md, 06_GS_CoreGameState.md, 07_DA_ItemData.md
### 02-player/ (8 files)
08_BPC_HealthSystem.md, 09_BPC_StaminaSystem.md, 10_BPC_StressSystem.md, 11_BPC_MovementStateSystem.md, 12_BPC_HidingSystem.md, 13_BPC_EmbodimentSystem.md, 14_BPC_CameraStateLayer.md, 15_BPC_PlayerMetricsTracker.md
### 03-interaction/ (6 files)
16_BPC_InteractionDetector.md, 17_BPC_PickupComponent.md, 18_I_HidingSpot.md, 19_BPC_LeverPuzzleComponent.md, 20_BPC_InteractableDoorComponent.md, 21_BPC_DiegeticDisplay.md
### 04-inventory/ (6 files)
22_BPC_InventoryComponent.md, 23_BPC_InventoryWeightSystem.md, 24_BPC_InventoryQuickSlot.md, 25_BPC_EquipmentSystem.md, 26_BPC_ContainerInventory.md, 27_BP_ItemPickup.md
### 05-saveload/ (4 files)
28_SS_SaveManager.md, 29_I_Persistable.md, 30_BPC_CheckpointSystem.md, 31_BPC_DeathHandlingSystem.md
### 06-ui/ (6 files)
32_SS_UIManager.md, 33_WBP_HUD.md, 34_WBP_InventoryUI.md, 35_WBP_InteractionUI.md, 36_WBP_MenuWidgets.md, 37_WBP_AccessibilityUI.md
### 07-narrative/ (11 files)
38_BPC_NarrativeStateSystem.md, 39_BPC_ObjectiveSystem.md, 40_BPC_DialoguePlaybackSystem.md, 41_BPC_DialogueChoiceSystem.md, 42_BPC_BranchingConsequenceSystem.md, 43_BPC_TrialScenarioSystem.md, 44_BPC_CutsceneBridge.md, 45_BPC_EndingAccumulatorSystem.md, 46_BPC_LoreUnlockSystem.md, 47_BPC_NarrativeTriggerVolume.md, 48_DA_NarrativeDataAssets.md
### 08-weapons/ (6 files)
49_BP_WeaponBase.md, 50_BP_RangedWeapon.md, 51_BP_MeleeWeapon.md, 52_BPC_AmmoComponent.md, 53_BPC_DamageHandlerComponent.md, 54_BPC_CombatFeedbackComponent.md
### 09-ai/ (7 files)
55_BPC_AIControllerBase.md, 56_BPC_PerceptionComponent.md, 57_BPC_BehaviorTreeManager.md, 58_BP_EnemyBase.md, 59_BP_PatrolPath.md, 60_BPC_AlertSystem.md, 61_BPC_AIStateMachine.md
### 10-adaptive/ (9 files)
62_BPC_DifficultyManager.md, 63_BPC_FearSystem.md, 64_BPC_AtmosphereController.md, 65_BPC_LightingManager.md, 66_BPC_AudioManager.md, 67_BPC_VFXManager.md, 68_BP_DynamicEvent.md, 69_BPC_PerformanceScaler.md, 70_BPC_ProceduralEncounter.md
### 11-polish/ (13 files)
71_SS_SettingsManager.md, 72_BPC_AccessibilitySettings.md, 73_BPC_TutorialSystem.md, 74_BPC_AchievementManager.md, 75_BPC_StatsTracker.md, 76_BPC_LoadingScreen.md, 77_WBP_CreditsScreen.md, 78_WBP_SplashScreen.md, 79_BPC_FPSCounter.md, 80_BPC_DevCheatManager.md, 81_WBP_DebugMenu.md, 82_BPC_AnalyticsTracker.md, 83_BPC_ErrorHandler.md
## Methodology
Read each section of the master document and extract:
- Every named system (by its prefix + name, e.g., `BPC_HealthSystem`, `BP_DoorActor`, `SS_SaveSystem`, `WBP_HUDController`)
- Cross-reference against the 83 existing files by matching:
- Exact name match (e.g., `BPC_HealthSystem``08_BPC_HealthSystem.md`)
- Semantic match (e.g., `SS_SaveSystem` in master doc → `28_SS_SaveManager.md` on disk)
- Bundled match (e.g., `WBP_MainMenu`, `WBP_PauseMenu`, `WBP_SettingsMenu` in master doc → `36_WBP_MenuWidgets.md` on disk)
## Output Format
Produce a Markdown table for each section of the master document with columns:
| Master Doc System | Corresponding File | Status | Notes |
|---|---|---|---|
| `BPC_HealthSystem` | `08_BPC_HealthSystem.md` | ExactMatch | Same name |
| `SS_SaveSystem` | `28_SS_SaveManager.md` | RenamedMatch | Master calls it SaveSystem, file calls it SaveManager |
| `BPC_InteractionExecutor` | (none) | Missing | No corresponding file |
Also flag files that exist on disk but have no obvious match in the master document (orphaned).
## Final Summary
At the end, provide:
1. Count of ExactMatch systems
2. Count of RenamedMatch systems
3. Count of Missing systems
4. Count of Orphaned files
5. Total systems in master doc
6. Total files on disk
7. List of all missing system names organized by their source section

View File

@@ -0,0 +1,355 @@
# GI_StarterGameInstance — Blueprint Specification
> **Asset Type:** Game Instance (derives from `UGameInstance`)
> **UE Version:** 5.55.7
> **Category:** 00-Project-Setup / Starter
> **Build Phase:** Pre-Phase 0 — Project Initialization
> **Dependencies:** `DA_GameTagRegistry` (01), 11 per-category Data Tables, GameplayTags plugin
---
## 1. Purpose
A minimal, ready-to-use GameInstance Blueprint that serves as the project's **immediate** entry point. Set this as your `Game Instance Class` in Project Settings to get tag validation and framework bootstrapping working on day one. It loads `DA_GameTagRegistry`, validates that all 11 Data Tables are registered, and broadcasts `OnFrameworkReady` when initialization completes.
**This is intentionally simple.** As the project matures, replace it with `GI_GameFramework` (04) which adds full game-phase management, subsystem ownership, platform init, and save-slot orchestration. All systems that bind to `OnFrameworkReady` will continue working with either GameInstance.
---
## 2. Class Settings
| Setting | Value |
|---------|-------|
| **Parent Class** | `GameInstance` |
| **Blueprint Type** | Game Instance |
| **Asset Path** | `/Game/Framework/Core/GI_StarterGameInstance` |
| **Is Abstract** | No |
**UE5 Setup:** `Project Settings → Maps & Modes → Game Instance Class``GI_StarterGameInstance`
---
## 3. Enums
None. No custom enums are defined in this system.
---
## 4. Structs
None. No custom structs are defined in this system.
---
## 5. Variables
### Configuration (Instance Editable)
| Name | Type | Default | Category | Description |
|------|------|---------|----------|-------------|
| `TagRegistry` | `DA_GameTagRegistry` (Object Reference) | *Assigned in Class Defaults* | Config | Hard reference to the `DA_GameTagRegistry` Data Asset |
| `bValidateTagsOnInit` | `Boolean` | `true` | Config | If true, calls `DA_GameTagRegistry.GetAllRegisteredTags()` during `Event Init` and logs tag count |
| `bLogTagsOnInit` | `Boolean` | `false` | Debug | If true, calls `DA_GameTagRegistry.LogAllTags()` after validation (Editor-only; heavy logging) |
### Internal (Private)
| Name | Type | Default | Category | Description |
|------|------|---------|----------|-------------|
| `bFrameworkInitialized` | `Boolean` | `false` | State | Set to `true` after `Event Init` completes successfully |
---
## 6. Functions
### Public Functions
#### `GetTagRegistry()` → `DA_GameTagRegistry` *(Blueprint Pure)*
- **Description:** Returns the cached `TagRegistry` reference. Returns `None` if not yet loaded.
- **Parameters:** None
- **Flow:**
1. Return `TagRegistry` variable
#### `IsFrameworkReady()` → `Boolean` *(Blueprint Pure)*
- **Description:** Returns whether the framework has completed initialization. Systems should check this before querying tag-dependent services.
- **Parameters:** None
- **Flow:**
1. Return `bFrameworkInitialized`
### Protected / Private Functions
#### `ValidateFrameworkTags()` *(Blueprint Callable, Private)*
- **Description:** Loads the tag registry, counts registered tags, and logs warnings if zero tags are found. Called automatically during `Event Init`.
- **Parameters:** None
- **Flow:**
1. `IsValid(TagRegistry)`? → False: Print Error "TagRegistry not assigned!" → Return
2. Call `TagRegistry.GetAllRegisteredTags()``AllTags` (Array\<GameplayTag\>)
3. `Array Length(AllTags) == 0`?
- True: Print Warning "No Gameplay Tags registered! Check Project Settings → GameplayTags → Gameplay Tag Table List. All 11 Data Tables must be added."
- False: Print String "DA_GameTagRegistry initialized: {Array Length} tags registered across 11 Data Tables."
4. If `bLogTagsOnInit`: Call `TagRegistry.LogAllTags()`
---
## 7. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnFrameworkReady` | — | Public | Fires after `Event Init` completes and tag validation passes. Other systems bind to this to defer initialization until the framework is ready. |
| `OnFrameworkInitFailed` | `ErrorReason: String` | Public | Fires if `TagRegistry` is invalid or zero tags are found during init. Systems should handle gracefully — show error UI, disable gameplay. |
---
## 8. Overridden Events
### Event: `Event Init`
- **Description:** UE5's GameInstance initialization event. Fires once when the game starts, before any level loads. This is where framework bootstrapping happens.
- **Flow:**
1. Call `Parent: Event Init` (important — don't skip)
2. Print String: "GI_StarterGameInstance: Init started"
3. `Branch: bValidateTagsOnInit?`
- False → Print String "Tag validation skipped (bValidateTagsOnInit = false)"
- True → Call `ValidateFrameworkTags()`
4. `Branch: IsValid(TagRegistry)?`
- False → Print Error "DA_GameTagRegistry reference is invalid!" → Call `OnFrameworkInitFailed("TagRegistry not assigned or invalid")` → Return
- True → Continue
5. Set `bFrameworkInitialized = true`
6. Call `OnFrameworkReady` (broadcast to all bound listeners)
7. Print String: "GI_StarterGameInstance: Init complete — OnFrameworkReady broadcast"
---
## 9. Blueprint Graph Logic Flow
```mermaid
flowchart TD
A[Event Init] --> B[Call Parent: Event Init]
B --> C{ bValidateTagsOnInit? }
C -->|True| D[Call ValidateFrameworkTags]
C -->|False| E[Skip validation]
D --> F{ IsValid TagRegistry? }
F -->|False| G[Print Error<br>Broadcast OnFrameworkInitFailed]
F -->|True| H[Set bFrameworkInitialized = true]
H --> I[Broadcast OnFrameworkReady]
I --> J[Print 'Init Complete']
```
### ValidateFrameworkTags Flow
```mermaid
flowchart TD
A[ValidateFrameworkTags] --> B{ IsValid TagRegistry? }
B -->|False| C[Print Error: TagRegistry not assigned]
B -->|True| D[Call GetAllRegisteredTags → AllTags]
D --> E{ Array Length == 0? }
E -->|True| F[Print Warning: No tags registered]
E -->|False| G[Print: N tags registered]
G --> H{ bLogTagsOnInit? }
H -->|True| I[Call LogAllTags]
H -->|False| J[Return]
I --> J
F --> J
C --> J
```
---
## 10. Communication Matrix
| Source | Target | Method | Direction | Data |
|--------|--------|--------|-----------|------|
| `GI_StarterGameInstance` | `DA_GameTagRegistry` | Direct function call | Outbound | Calls `GetAllRegisteredTags()`, `LogAllTags()`, `ValidateTag()` |
| `GI_StarterGameInstance` | All bound systems | `OnFrameworkReady` dispatcher | Outbound broadcast | No data — listeners query `IsFrameworkReady()` |
| `GI_StarterGameInstance` | All bound systems | `OnFrameworkInitFailed` dispatcher | Outbound broadcast | `ErrorReason: String` |
| Any system | `GI_StarterGameInstance` | `GetGameInstance()` → Cast | Inbound query | Systems call `IsFrameworkReady()` before initialization |
---
## 11. Validation Checklist
- [ ] `GI_StarterGameInstance` set as `Game Instance Class` in `Project Settings → Maps & Modes`
- [ ] `TagRegistry` variable assigned to `DA_GameTagRegistry` Data Asset in Class Defaults
- [ ] All 11 Data Tables registered in `Project Settings → GameplayTags → Gameplay Tag Table List`
- [ ] On PIE (Play In Editor): output log shows "N tags registered across 11 Data Tables"
- [ ] On PIE with `bLogTagsOnInit = true`: all tag names printed to output log
- [ ] If `TagRegistry` is unassigned: error prints and `OnFrameworkInitFailed` fires (test by clearing the variable)
- [ ] Other systems successfully bind to `OnFrameworkReady` and defer their init
- [ ] Edge case: 0 tags in Data Tables → warning prints, `OnFrameworkInitFailed` does NOT fire (empty tables are a warning, not a failure)
---
## 12. Reuse Notes
- **Replacement path:** When you are ready for the full `GI_GameFramework` (04), simply change the `Game Instance Class` in Project Settings. All systems that bind to `OnFrameworkReady` on either GameInstance will work — `GI_GameFramework` includes the same dispatcher.
- **Project-specific init:** Add custom initialization (achievement platform, analytics, save migration) after the `OnFrameworkReady` broadcast in `Event Init`.
- **Tag registry is a Data Asset, not a subsystem:** You must manually assign the `TagRegistry` reference in Class Defaults. It is NOT auto-discovered.
- **Multi-platform:** On consoles, `Event Init` fires before any level loads — same as PC. No platform-specific code is needed.
---
## 13. Manual Implementation Guide
> **For human implementer:** Follow these steps to build `GI_StarterGameInstance` in UE5 Blueprints. This is the FIRST Blueprint you should create — it enables tag validation for all other systems.
### 13.1 Class Setup
1. Right-click in Content Browser → **Blueprint Class**
2. Search for parent class: `GameInstance`
3. Name: `GI_StarterGameInstance`
4. Save to: `Content/Framework/Core/`
5. Open **Project Settings → Maps & Modes** → Set `Game Instance Class` to `GI_StarterGameInstance`
### 13.2 Variables
Add to Class Defaults:
| Variable | Type | Instance Editable | Default | Category |
|----------|------|------------------|---------|----------|
| `TagRegistry` | `DA_GameTagRegistry` (Object Reference) | ✓ | *Select `DA_GameTagRegistry` asset* | Config |
| `bValidateTagsOnInit` | `Boolean` | ✓ | `true` | Config |
| `bLogTagsOnInit` | `Boolean` | ✓ | `false` | Debug |
| `bFrameworkInitialized` | `Boolean` | ✗ (Private) | `false` | State |
**⚠️ Important:** For `TagRegistry`, use the type `DA_GameTagRegistry` (your specific Data Asset Blueprint class, NOT the generic `PrimaryDataAsset`). Compile `DA_GameTagRegistry` first so it appears in the type dropdown.
### 13.3 Event Dispatchers
Create two Event Dispatchers in the **My Blueprint** panel:
1. `OnFrameworkReady` — no parameters
2. `OnFrameworkInitFailed` — add one input parameter:
- `ErrorReason` (String)
### 13.4 Override Event Init
1. In the **My Blueprint** panel, hover over **Functions** → click **Override** → select `Event Init`
2. Build the following graph:
```
[Event Init]
Step 1: Call "Parent: Event Init" (right-click event node → "Add Call to Parent")
Step 2: Print String: "GI_StarterGameInstance: Init started"
→ Text (Format Text): "GI_StarterGameInstance: Init started"
Step 3: Branch (Condition: bValidateTagsOnInit)
True → Call ValidateFrameworkTags (see 13.5)
False → Print String: "Tag validation skipped (bValidateTagsOnInit = false)"
Step 4: IsValid(TagRegistry) → Branch
False:
→ Print String: "DA_GameTagRegistry reference is invalid!"
→ Call OnFrameworkInitFailed (ErrorReason = "TagRegistry not assigned or invalid")
→ Return Node
True: Continue
Step 5: Set bFrameworkInitialized = true
Step 6: Call OnFrameworkReady (no params)
Step 7: Print String: "GI_StarterGameInstance: Init complete — OnFrameworkReady broadcast"
```
**Nodes to Search:** `Event Init`, `Add Call to Parent`, `Print String`, `Format Text`, `Branch`, `IsValid`, `Set`, `Call OnFrameworkReady`, `Call OnFrameworkInitFailed`
### 13.5 Implement ValidateFrameworkTags
Create a new function: **BlueprintCallable**, Private, named `ValidateFrameworkTags`.
```
[Function: ValidateFrameworkTags] (BlueprintCallable, Private)
Step 1: IsValid(TagRegistry) → Branch
False: Print String "TagRegistry not assigned in GI_StarterGameInstance!" → Return
True: Continue
Step 2: Call TagRegistry → GetAllRegisteredTags() → store in "AllTags" (Array<GameplayTag>)
Step 3: Array Length(AllTags) → store in "TagCount" (Integer)
Step 4: Branch (Condition: TagCount == 0)
True: Print String (Color: Yellow): "WARNING: No Gameplay Tags registered! Check Project Settings → GameplayTags → Gameplay Tag Table List. All 11 Data Tables must be added."
False: Print String (Format Text): "DA_GameTagRegistry initialized: {TagCount} tags registered across 11 Data Tables."
Step 5: Branch (Condition: bLogTagsOnInit)
True: Call TagRegistry → LogAllTags()
False: Continue (no action)
Step 6: Return
```
**Nodes to Search:** `IsValid`, `GetAllRegisteredTags`, `Array Length`, `Branch`, `Print String`, `Format Text`, `LogAllTags`
### 13.6 Implement GetTagRegistry
Create a new function: **BlueprintPure**, Public, named `GetTagRegistry`.
```
[Function: GetTagRegistry] → DA_GameTagRegistry (BlueprintPure)
Return TagRegistry
```
### 13.7 Implement IsFrameworkReady
Create a new function: **BlueprintPure**, Public, named `IsFrameworkReady`.
```
[Function: IsFrameworkReady] → Boolean (BlueprintPure)
Return bFrameworkInitialized
```
### 13.8 How Other Systems Bind to OnFrameworkReady
In any other Blueprint that needs framework services (tag validation, subsystems, etc.):
```
[Event BeginPlay]
Step 1: Get Game Instance → Cast to GI_StarterGameInstance → Store as "GameInstance"
Step 2: Branch: IsValid(GameInstance)?
True:
→ Branch: GameInstance → IsFrameworkReady()?
True: Call "OnFrameworkReadyHandler" immediately (already initialized)
False: Bind "OnFrameworkReadyHandler" to GameInstance.OnFrameworkReady
False: Print Warning "GI_StarterGameInstance not found!"
```
**Custom Event: OnFrameworkReadyHandler**
```
[Custom Event: OnFrameworkReadyHandler]
Step 1: Unbind from GameInstance.OnFrameworkReady (if bound — prevents double-fire)
Step 2: ... proceed with system-specific initialization ...
```
### 13.9 Quick Node Reference
| Node | Where to Find | Used For |
|------|---------------|----------|
| `Event Init` | Override in My Blueprint → Functions | GameInstance startup |
| `Get Game Instance` | Right-click → "Get Game Instance" | Any Blueprint that needs the GameInstance |
| `Cast to GI_StarterGameInstance` | Right-click → "Cast to GI_StarterGameInstance" | Type-safe access to framework functions |
| `IsValid` | Right-click → "IsValid" | Null-checking object references |
| `Bind Event to OnFrameworkReady` | Right-click on dispatcher → "Bind Event" | Deferred initialization |
| `Unbind Event from OnFrameworkReady` | Right-click on dispatcher → "Unbind Event" | Clean up after handler fires |
---
## 14. Blueprint Build Checklist
- [ ] Create `GI_StarterGameInstance` Blueprint (Parent: `GameInstance`)
- [ ] Set as `Game Instance Class` in `Project Settings → Maps & Modes`
- [ ] Create `DA_GameTagRegistry` Data Asset first (required for `TagRegistry` variable type)
- [ ] Add variable `TagRegistry` (type: `DA_GameTagRegistry`) and assign the Data Asset
- [ ] Add variable `bValidateTagsOnInit` (Boolean, default `true`)
- [ ] Add variable `bLogTagsOnInit` (Boolean, default `false`)
- [ ] Add variable `bFrameworkInitialized` (Boolean, default `false`, Private)
- [ ] Create Event Dispatcher `OnFrameworkReady` (no params)
- [ ] Create Event Dispatcher `OnFrameworkInitFailed` (1 param: `ErrorReason` String)
- [ ] Override `Event Init` — add Call to Parent, validation branch, dispatcher broadcasts
- [ ] Implement `ValidateFrameworkTags()` — validate registry, count tags, optional log
- [ ] Implement `GetTagRegistry()` (BlueprintPure) — return TagRegistry reference
- [ ] Implement `IsFrameworkReady()` (BlueprintPure) — return bFrameworkInitialized
- [ ] Compile and test: PIE → output log shows "DA_GameTagRegistry initialized: N tags"
- [ ] Test error path: clear `TagRegistry` variable → `OnFrameworkInitFailed` fires
- [ ] Test with `bLogTagsOnInit = true` → all tags printed to output log
- [ ] Verify other systems can bind to `OnFrameworkReady` and defer init correctly
---
## 15. Multiplayer Networking
**Replication: None needed.** The GameInstance is a client-only singleton — each client has its own instance. The `DA_GameTagRegistry` Data Asset loads identically from disk on all clients and servers. Event Dispatchers (`OnFrameworkReady`, `OnFrameworkInitFailed`) fire locally on each instance.
**Authority: N/A.** No runtime state changes. Tag data is read-only configuration.
**Multiplayer Note:** In a networked game, `Event Init` fires on both server and each client's GameInstance. Tag validation runs independently on each — which is correct, since Data Tables and GameplayTags must be identical across all instances for networked tag replication to function.

View File

@@ -0,0 +1,404 @@
# DA_GameTagRegistry — Blueprint Specification
> **Asset Type:** Data Asset (derives from `UPrimaryDataAsset`)
> **UE Version:** 5.55.7
> **Category:** 01-Core / Foundation
> **Build Phase:** Phase 0 — Item 1
> **Dependencies:** None (this is the first system)
>
> ---
>
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/DA_GameTagRegistry.h` provides all tag query, validation, and export functions. The Blueprint spec below documents the API and Data Table configuration. **Do NOT create a Blueprint child** — create a **Data Asset instance** (Right-click → Miscellaneous → Data Asset → pick `DA_GameTagRegistry`). Assign the 11 `TagDataTables`. All functions are `BlueprintCallable` — call them from any BP. See `docs/developer/cpp-integration-guide.md` for exact setup steps.
>
> ---## 1. Purpose
Centralises every `GameplayTag` namespace used across the framework in a single asset. All other systems reference tags from this registry — never raw strings, never `FName` comparisons, never hardcoded booleans for state.
---
## 2. Class Settings
| Setting | Value |
|---------|-------|
| **Parent Class** | `UPrimaryDataAsset` |
| **Blueprint Type** | Data Asset |
| **Asset Path** | `/Game/Framework/Core/DA_GameTagRegistry` |
| **Is Abstract** | No |
---
## 3. Enums
None. This Data Asset uses only **Gameplay Tags** as the canonical state identifiers. No enums are defined here.
---
## 4. Structs
None. The registry is a flat collection of tag declarations.
---
## 5. Variables
| Name | Type | Category | Instance Editable | Description |
|------|------|----------|-------------------|-------------|
| `TagNamespace` | `FText` | Documentation | Yes | Human-readable description of the tag namespace (e.g. "Player.State") |
| `bIsFrameworkTag` | `bool` | Documentation | Yes | `true` for framework-defined tags, `false` for project-specific overrides |
**NOTE:** The tag definitions themselves live in the project's `DefaultGameplayTags.ini` file (or via the **Project Settings → Gameplay Tags** editor). This asset provides a centralised **documentation anchor** for those tags. The Blueprint graph does not hold tag data.
---
## 6. Functions
### 6.1 Blueprint Pure Functions
| Name | Inputs | Outputs | Category | Description |
|------|--------|---------|----------|-------------|
| `GetAllRegisteredTags` | — | `Array<FName>` | Query | Reads tags from all registered per-category Data Tables (see Section 14) |
| `GetTagDisplayName` | `Tag: FGameplayTag` | `Text` | Query | Returns the human-readable display name via `Get Tag Display Name` node |
| `ValidateTag` | `Tag: FGameplayTag` | `Boolean` | Validation | Returns `true` if the tag is valid via `Is Gameplay Tag Valid` node |
### 6.2 Blueprint Callable Functions
| Name | Inputs | Outputs | Category | Description |
|------|--------|---------|----------|-------------|
| `LogAllTags` | — | — | Debug | Prints all tags (from Data Table) to the output log (Editor-only) |
| `ExportTagNamespace` | `NamespacePrefix: String` | `String` | Tooling | Exports all tags matching a namespace prefix as a formatted string | |
---
## 7. Event Dispatchers
None. This Data Asset is passive — it has no runtime events.
---
## 8. Overridden Events
**Note:** Data Assets do NOT have `BeginPlay`, `Tick`, or `OnAssetLoaded` in Blueprint. All validation logic should be called externally from a GameInstance or Subsystem during initialization.
### Initialization Pattern (called externally):
```
[In GI_GameFramework.Init() or BPC_StateManager.BeginPlay:]
├─► Load DA_GameTagRegistry (hard reference or Get Data Asset)
├─► Call DA_GameTagRegistry.GetAllRegisteredTags()
├─► Array Length == 0?
│ ├─► Yes → Print Warning: "No tags in registered Data Tables! Framework tags are unregistered."
│ └─► No → Log: "N tags registered."
└─► Optional: call LogAllTags() for debug output
```
## 9. Blueprint Graph Logic
### 9.1 GetAllRegisteredTags (Multi-Table Data Table Proxy)
**⚠️ UE5 Limitation:** `UGameplayTagsManager::RequestAllGameplayTags()` is C++ only — not exposed to Blueprints. This function uses a **Data Table proxy** with multiple per-category tables. All tables must be registered in `Project Settings → GameplayTags → Gameplay Tag Table List`.
```
[Function: GetAllRegisteredTags] → Array<GameplayTag>
Step 1: Create empty Array<GameplayTag> → LocalTags
Step 2: ForEachLoop over TagDataTables (outer loop):
├─► Branch: IsValid(current table)? → False: skip
├─► Get Data Table Row Names (current table)
└─► ForEachLoop (RowNames) (inner loop):
├─► Get Data Table Row → Break GameplayTagTableRow → get "Tag"
└─► Add "Tag" to LocalTags
Step 3: Return LocalTags
```
**Key Change from v1:** The outer loop iterates `TagDataTables` (Array), not a single `TagDataTable`. This enables tags split across multiple CSV files by category.
### 9.2 ValidateTag
**⚠️ UE5 Limitation:** `UGameplayTagsManager::RequestGameplayTag()` is not a direct Blueprint node. Use `Is Gameplay Tag Valid` instead.
```
[Function: ValidateTag(Tag)] → Boolean
Step 1: Is Gameplay Tag Valid (Tag)
├─► True → Return true
└─► False → Print Warning: "Invalid Tag: {Get Tag Name(Tag)}" → Return false
```
**Node Search:** `Is Gameplay Tag Valid`, `Get Tag Name`
### 9.3 GetTagDisplayName
```
[Function: GetTagDisplayName(Tag)] → Text
Step 1: Get Tag Display Name (Tag) → Return
```
**Node Search:** `Get Tag Display Name` (Blueprint pure node, available)
### 9.4 LogAllTags
```
[Function: LogAllTags] (Blueprint Callable)
Step 1: GetAllRegisteredTags() → Store in LocalTags
Step 2: ForEachLoop (LocalTags):
├─► Get Tag Name → ToString → Print String
Step 3: Print String: "Total tags: {Array Length(LocalTags)}"
```
### 9.5 ExportTagNamespace(Prefix: String) → String
```
[Function: ExportTagNamespace]
Step 1: GetAllRegisteredTags() → LocalTags
Step 2: Create empty String → Output
Step 3: ForEachLoop (LocalTags):
├─► Get Tag Name → ToString → TagString
├─► Branch: Does TagString start with Prefix?
│ True → Append TagString + "\n" to Output
└─► Continue
Step 4: Return Output
```
[OnAssetLoaded]
└─► Call GetAllRegisteredTags()
└─► Length == 0?
├─► Yes → Print Warning: "No Gameplay Tags registered! /Game/Framework/Core/DA_GameTagRegistry is empty."
└─► No → Log: "N tags registered."
```
### 9.2 Validation Function Logic (ValidateTag)
```
[ValidateTag(Tag)]
└─► Use GameplayTag::RequestGameplayTag(Tag.TagName)
└─► IsValid?
├─► Yes → return true
└─► No → Print Warning: "Invalid Tag: {Tag}" → return false
```
---
## 10. Tag Namespace Reference (Framework Canonical)
All framework-level tags are defined in per-category Data Tables (CSV format, Row Structure: `GameplayTagTableRow`). These tables are registered in `Project Settings → GameplayTags → Gameplay Tag Table List`. The engine's `UGameplayTagsManager` merges all tables into one master list.
| Data Table | Namespaces Covered | Example Tags |
|------------|-------------------|--------------|
| `DT_Tags_Player` | `Framework.Player.State.*`, `*.Stress.*`, `*.Posture.*`, `*.Movement.*`, `*.Camera.*`, `*.Body.*`, `*.Overlay.*`, `*.Vitals.*` | Alive, Dead, Hidden, Sprinting, Crouching, Aiming, FullBody, Blood |
| `DT_Tags_Interaction` | `Framework.Interaction.Type.*`, `*.Context.*`, `*.Prompt.*`, `*.HidingSpot.*`, `*.Traversal.*`, `*.Door.*` | Pickup, Door, Container, Locked, OneShot, Vault, Mantle |
| `DT_Tags_Item` | `Framework.Item.Type.*`, `*.Slot.*`, `*.Rarity.*`, `*.Context.*` | Weapon, KeyItem, PrimaryWeapon, Legendary, Stackable |
| `DT_Tags_Narrative` | `Game.Narrative.Flag.*`, `*.Phase.*`, `*.Choice.*`, `*.Ending.*`, `*.Trial.*`, `*.Cutscene.*`, `*.Lore.*`, `Framework.Objective.*` | Chapter1Complete, Act2, GoodEnding, HospitalEscape, Intro |
| `DT_Tags_AI` | `Framework.AI.Alert.*`, `*.Archetype.*`, `*.Stimulus.*`, `*.Behavior.*`, `*.Memory.*` | Alerted, Stalker, Hearing, Aggressive, LastKnownLocation |
| `DT_Tags_Save` | `Framework.Save.*`, `*.DeathSpace.*`, `*.Checkpoint.*`, `*.Respawn.*`, `*.RunHistory.*` | Checkpoint, HardSave, Slot.1, Entered, Active, Reached, Start, Death |
| `DT_Tags_Environment` | `Game.Environment.Atmosphere.*`, `*.Scare.*`, `*.Light.*`, `*.Pacing.*`, `*.Performance.*` | Tense, MirrorJump, Flicker, Combat, High |
| `DT_Tags_Combat` | `Framework.Combat.Damage.*`, `*.Weapon.*`, `*.Ammo.*`, `*.FireMode.*`, `*.HitReaction.*`, `*.Feedback.*`, `*.Shield.*` | Physical, Firearm, Pistol, Flinch, HitMarker, Active |
| `DT_Tags_State` | `Framework.State.Action.*`, `*.Overlay.*`, `*.Vital.*`, `*.Gating.*` | Fire, Sprint, Menu, Health, BlockSprint |
| `DT_Tags_Audio` | `Framework.Audio.Bus.*`, `*.Room.*`, `*.Parameter.*`, `*.Surface.*` | SFX, Small, HeartRate, Concrete |
| `DT_Tags_Achievement` | `Game.Achievement.*` | FirstBlood, Survivor, Pacifist, Ghost, Collector, LoreMaster, TrueEnding, SpeedRunner, WeaponsMaster, Completionist |
**Usage Rule:** All systems must add the prefix `Framework.` to framework tags in code comments. Project-specific tags use `Game.` prefix. Example: `Framework.Item.Type.Weapon` vs `Game.Narrative.Flag.BasementDoorOpened`.
**CSV Format:** Each Data Table CSV uses columns: `Name,Tag,DevComment`. Name is a unique row identifier. Tag is the full gameplay tag string. DevComment explains the tag's purpose.
---
## 11. Communication Matrix
| Target System | Method | Direction | Data |
|---------------|--------|-----------|------|
| All other systems | `GameplayTag` comparisons | Read-only | Framework-defined tag values |
This asset does not talk to other systems directly. All communication is passive — other systems read tag values from the project's tag table.
---
## 12. Validation Checklist
- [ ] All namespace prefixes documented in the Data Asset's `TagNamespace` text field
- [ ] `bIsFrameworkTag` set to `true` for framework namespaces
- [ ] At least one project-specific tag exists (e.g. `Game.Narrative.Flag.PrologueComplete`) to validate the workflow
- [ ] `DefaultGameplayTags.ini` contains ALL documented tags
- [ ] No duplicate tag names across namespaces
- [ ] No raw string comparisons exist in any other system (all must use `HasTag` / `MatchesTag`)
---
## 13. Reuse Notes
- The `Game.` namespace is reserved for **project-specific** tags and is empty in the framework distribution.
- To add new tags: edit `DefaultGameplayTags.ini` or use the **Gameplay Tags** editor under **Project Settings**.
- The Data Asset is purely documentary — the real tag data lives in the project's tag table. This file exists so systems have a single place to look up "what tags exist in the framework" without reading the `.ini` file directly.
---
## 14. Manual Implementation Guide
> **For human implementer:** Follow these steps to build `DA_GameTagRegistry` in UE5 Blueprints.
> **⚠️ UE5 BP Limitation:** `UGameplayTagsManager::RequestAllGameplayTags()` is C++ only. This implementation uses an **Array of Data Tables** (11 per-category tables) as the 100% Blueprint workaround. All tables must be registered in `Project Settings → GameplayTags → Gameplay Tag Table List`.
### 14.0 Prerequisite: Create the Per-Category Data Tables
Before implementing the Data Asset, create **11 Data Tables**, one per category. Each follows the same format:
**Format:** Data Table with Row Structure = `GameplayTagTableRow`
**CSV Columns:** `Name,Tag,DevComment`
**Location in docs:** `docs/blueprints/01-core/data-tables/DT_Tags_*.csv`
**Location in UE5:** `Content/Framework/Core/DataTables/DT_Tags_*`
1. For each category, right-click in Content Browser → **Miscellaneous → Data Table**
2. Row Structure: `GameplayTagTableRow`
3. Name: `DT_Tags_{Category}` (e.g., `DT_Tags_Player`)
4. Import the corresponding CSV file (Right-click Data Table → Import → CSV)
5. **Critical:** Go to `Project Settings → GameplayTags → Gameplay Tag Table List` → click `+` 11 times → assign each table to a slot
- This auto-registers ALL tags with the engine's tag manager.
### 14.1 Class Setup
1. Right-click in Content Browser → **Miscellaneous → Data Asset**
2. Select parent class: `PrimaryDataAsset`
3. Name: `DA_GameTagRegistry`
4. Save to: `Content/Framework/Core/`
### 14.2 Variables
Add to Class Defaults:
| Variable | Type | Instance Editable | Default | Category |
|----------|------|------------------|---------|----------|
| `TagNamespace` | `Text` | ✓ | *"Framework tag namespace documentation"* | Documentation |
| `bIsFrameworkTag` | `Boolean` | ✓ | `true` | Documentation |
| `TagDataTables` | `Array<Data Table Object Reference>` | ✓ | [DT_Tags_Player, DT_Tags_Interaction, DT_Tags_Item, DT_Tags_Narrative, DT_Tags_AI, DT_Tags_Save, DT_Tags_Environment, DT_Tags_Combat, DT_Tags_State, DT_Tags_Audio, DT_Tags_Achievement] | Config |
**⚠️ Multi-Table Design:** Tags are split into 11 per-category Data Tables (see Section 10). In UE5 `Project Settings → GameplayTags → Gameplay Tag Table List`, add ALL 11 tables. The engine's `UGameplayTagsManager` merges them automatically.
### 14.3 Function Implementations
#### `GetAllRegisteredTags()` → `Array<GameplayTag>` *(Blueprint Pure)*
**Purpose:** Returns all tags from ALL registered Data Tables. Uses an outer loop over `TagDataTables` array and inner loop over each table's rows.
**⚠️ UE5 Limitation:** `UGameplayTagsManager::RequestAllGameplayTags()` is C++ only — not exposed to Blueprints. This function uses the Data Table proxy approach with multiple tables.
**Node-by-Node Logic:**
```
[Function: GetAllRegisteredTags] (Pure, no execution pins)
Step 1: Create empty Array<GameplayTag> → LocalTags
Step 2: ForEachLoop over TagDataTables array (outer loop):
├─► Array Element = current Data Table
├─► Branch: IsValid(current table)?
│ False → Skip to next table
│ True → Continue
├─► Get Data Table Row Names (current table) → returns Array<Name>
└─► ForEachLoop over RowNames (inner loop):
├─► Get Data Table Row (current table, Array Element)
│ → Struct Pin: Break GameplayTagTableRow
│ → Get "Tag" field (type: GameplayTag)
└─► Add "Tag" to LocalTags array
Step 3: Return LocalTags
```
**Nodes to Search:** `ForEachLoop` (outer over TagDataTables), `ForEachLoop` (inner over row names), `Get Data Table Row Names`, `Get Data Table Row`, `Break GameplayTagTableRow`, `Add`, `Array`, `IsValid`
**⚠️ Note:** If your UE version doesn't support loops in Pure functions, make this **BlueprintCallable** (impure) instead.
#### `GetTagDisplayName(Tag: GameplayTag)` → `Text` *(Blueprint Pure)*
**Node-by-Node Logic:**
```
[Function: GetTagDisplayName]
Input Tag → Get Tag Display Name (Tag) → Return
```
**Node Search:** `Get Tag Display Name` — this IS available in Blueprint (part of GameplayTags plugin).
#### `ValidateTag(Tag: GameplayTag)` → `Boolean` *(Blueprint Pure)*
**⚠️ UE5 Limitation:** `UGameplayTagsManager::RequestGameplayTag()` is not directly available in BP. Use `Is Gameplay Tag Valid` instead.
**Node-by-Node Logic:**
```
[Function: ValidateTag]
Step 1: Is Gameplay Tag Valid (Tag)
├─► True → Return true
└─► False → Print Warning: "Invalid Tag: " + Get Tag Name(Tag) → Return false
```
**Node Search:** `Is Gameplay Tag Valid`, `Get Tag Name`
**⚠️ Note:** `Is Gameplay Tag Valid` returns true only if the tag is registered in the engine's tag table (which the 11 Data Tables populate via Project Settings). Tags NOT in any registered table will return false.
#### `LogAllTags()` → *(void)* *(Blueprint Callable)*
**Editor Only.** Prints all tags from all Data Tables to the output log.
**Node-by-Node Logic:**
```
[Function: LogAllTags]
Step 1: Call GetAllRegisteredTags() → LocalTags
Step 2: ForEachLoop (LocalTags):
├─► Name (Array Element) → ToString → Print String
Step 3: Print String: "Total tags across all tables: " + Array Length(LocalTags)
```
#### `ExportTagNamespace(NamespacePrefix: String)` → `String` *(Blueprint Callable)*
**Node-by-Node Logic:**
```
[Function: ExportTagNamespace]
Step 1: Call GetAllRegisteredTags() → LocalTags
Step 2: Create String variable → Output = ""
Step 3: ForEachLoop (LocalTags):
├─► (Array Element) → ToString → TagString
├─► Branch: Does TagString start with NamespacePrefix?
│ True → Append TagString + "\n" to Output (use "Append" or "Build String")
└─► Continue
Step 4: Return Output
```
### 14.4 External Initialization
Since Data Assets have no `BeginPlay`, call validation from your GameInstance or StateManager:
**In `GI_StarterGameInstance.Init()` or `GI_GameFramework.Init()`:**
```
Step 1: Get DA_GameTagRegistry (hard reference or Load Asset)
Step 2: Call DA_GameTagRegistry.GetAllRegisteredTags() → LocalTags
Step 3: Branch: Array Length(LocalTags) == 0
├─► True → Print Warning: "No tags in registered Data Tables!"
└─► False → Print String: "N tags registered: " + Array Length
Step 4: [Debug builds only] Call DA_GameTagRegistry.LogAllTags()
```
### 14.5 Networking
No replication needed. This is a read-only Data Asset with a read-only Data Table reference. All clients load identical copies from disk.
### 14.6 Blueprint Build Checklist
- [ ] Create 11 Data Tables with Row Structure `GameplayTagTableRow`:
- [ ] `DT_Tags_Player` — Player state, stress, posture, movement, camera, body, vitals
- [ ] `DT_Tags_Interaction` — Interaction types, contexts, prompts, hiding spots, traversal, doors
- [ ] `DT_Tags_Item` — Item types, slots, rarities, contexts
- [ ] `DT_Tags_Narrative` — Flags, phases, choices, endings, trials, cutscenes, lore, objectives
- [ ] `DT_Tags_AI` — Alert levels, archetypes, stimuli, behaviors, memory
- [ ] `DT_Tags_Save` — Save types, death space, checkpoints, respawn
- [ ] `DT_Tags_Environment` — Atmosphere, scares, lighting, pacing, performance
- [ ] `DT_Tags_Combat` — Damage types, weapons, ammo, fire modes, hit reactions, shield
- [ ] `DT_Tags_State` — Action states, overlay states, vitals, gating
- [ ] `DT_Tags_Audio` — Audio buses, room acoustics, parameters, surfaces
- [ ] `DT_Tags_Achievement` — Achievement identifiers
- [ ] Add ALL 11 tables to `Project Settings → GameplayTags → Gameplay Tag Table List`
- [ ] Create Data Asset: `DA_GameTagRegistry` (Parent: `PrimaryDataAsset`)
- [ ] Add variables: `TagNamespace` (Text), `bIsFrameworkTag` (Boolean), `TagDataTables` (Array<Data Table> — populate with all 11 tables)
- [ ] Implement `GetAllRegisteredTags` with OUTER loop over TagDataTables + INNER loop over row names
- [ ] Implement `GetTagDisplayName` using `Get Tag Display Name` node
- [ ] Implement `ValidateTag` using `Is Gameplay Tag Valid` node
- [ ] Implement `LogAllTags` (editor-only, prints tags from all tables)
- [ ] Implement `ExportTagNamespace` (string prefix filtering across all tables)
- [ ] Add external initialization call from `GI_StarterGameInstance.Init()` or `GI_GameFramework.Init()`
- [ ] Verify: all tags from Section 10 exist in the appropriate Data Table
- [ ] Verify: `ValidateTag` returns true for tags in any registered table, false for unregistered
- [ ] Verify: `GetAllRegisteredTags` returns ALL tags merged from all tables
---
## 15. Multiplayer Networking
**Replication: None needed.** This Data Asset is read-only configuration. All clients load identical copies from disk. GameplayTag data lives in `DefaultGameplayTags.ini` which is identical on all instances.
**Authority: N/A.** No runtime state changes.

View File

@@ -1,399 +1,19 @@
# GI_GameTagRegistry — Blueprint Specification
# RENAMED — See `01_DA_GameTagRegistry.md`
> **Asset Type:** Data Asset (derives from `UPrimaryDataAsset`)
> **UE Version:** 5.55.7
> **Category:** 01-Core / Foundation
> **Build Phase:** Phase 0 — Item 1
> **Dependencies:** None (this is the first system)
> **This file has been renamed.** The system is now `DA_GameTagRegistry` (Data Asset prefix, `UPrimaryDataAsset` parent), not `GI_GameTagRegistry` (Game Instance prefix).
---
## Why the rename?
## 1. Purpose
The `GI_` prefix implies a `GameInstance` class, but this system is architecturally a **Data Asset** — it has no lifecycle events (`BeginPlay`, `Tick`), no runtime state, and is loaded as a read-only configuration asset. The framework already has `GI_GameFramework` (#04) as the project's sole GameInstance class.
Centralises every `GameplayTag` namespace used across the framework in a single asset. All other systems reference tags from this registry — never raw strings, never `FName` comparisons, never hardcoded booleans for state.
Additionally, the new [`GI_StarterGameInstance`](../00-project-setup/GI_StarterGameInstance.md) (00-project-setup) provides the actual GameInstance entry point that loads and validates `DA_GameTagRegistry` during `Event Init`.
---
## Migration
## 2. Class Settings
If you were referencing this file:
- **Old path:** `docs/blueprints/01-core/01_GI_GameTagRegistry.md`
- **New path:** [`docs/blueprints/01-core/01_DA_GameTagRegistry.md`](01_DA_GameTagRegistry.md)
- **Old asset name:** `GI_GameTagRegistry`**New asset name:** `DA_GameTagRegistry`
- **Old asset path:** `/Game/Framework/Core/DA_GameTagRegistry` (asset path was already correct)
| Setting | Value |
|---------|-------|
| **Parent Class** | `UPrimaryDataAsset` |
| **Blueprint Type** | Data Asset |
| **Asset Path** | `/Game/Framework/Core/DA_GameTagRegistry` |
| **Is Abstract** | No |
---
## 3. Enums
None. This Data Asset uses only **Gameplay Tags** as the canonical state identifiers. No enums are defined here.
---
## 4. Structs
None. The registry is a flat collection of tag declarations.
---
## 5. Variables
| Name | Type | Category | Instance Editable | Description |
|------|------|----------|-------------------|-------------|
| `TagNamespace` | `FText` | Documentation | Yes | Human-readable description of the tag namespace (e.g. "Player.State") |
| `bIsFrameworkTag` | `bool` | Documentation | Yes | `true` for framework-defined tags, `false` for project-specific overrides |
**NOTE:** The tag definitions themselves live in the project's `DefaultGameplayTags.ini` file (or via the **Project Settings → Gameplay Tags** editor). This asset provides a centralised **documentation anchor** for those tags. The Blueprint graph does not hold tag data.
---
## 6. Functions
### 6.1 Blueprint Pure Functions
| Name | Inputs | Outputs | Category | Description |
|------|--------|---------|----------|-------------|
| `GetAllRegisteredTags` | — | `Array<FGameplayTag>` | Query | Reads tags from `DT_ProjectTags` Data Table (see Section 14 for UE5 BP workaround) |
| `GetTagDisplayName` | `Tag: FGameplayTag` | `Text` | Query | Returns the human-readable display name via `Get Tag Display Name` node |
| `ValidateTag` | `Tag: FGameplayTag` | `Boolean` | Validation | Returns `true` if the tag is valid via `Is Gameplay Tag Valid` node |
### 6.2 Blueprint Callable Functions
| Name | Inputs | Outputs | Category | Description |
|------|--------|---------|----------|-------------|
| `LogAllTags` | — | — | Debug | Prints all tags (from Data Table) to the output log (Editor-only) |
| `ExportTagNamespace` | `NamespacePrefix: String` | `String` | Tooling | Exports all tags matching a namespace prefix as a formatted string | |
---
## 7. Event Dispatchers
None. This Data Asset is passive — it has no runtime events.
---
## 8. Overridden Events
**Note:** Data Assets do NOT have `BeginPlay`, `Tick`, or `OnAssetLoaded` in Blueprint. All validation logic should be called externally from a GameInstance or Subsystem during initialization.
### Initialization Pattern (called externally):
```
[In GI_GameFramework.Init() or BPC_StateManager.BeginPlay:]
├─► Load DA_GameTagRegistry (hard reference or Get Data Asset)
├─► Call DA_GameTagRegistry.GetAllRegisteredTags()
├─► Array Length == 0?
│ ├─► Yes → Print Warning: "No tags in DT_ProjectTags! Framework tags are unregistered."
│ └─► No → Log: "N tags registered."
└─► Optional: call LogAllTags() for debug output
```
## 9. Blueprint Graph Logic
### 9.1 GetAllRegisteredTags (Data Table Proxy)
**⚠️ UE5 Limitation:** `UGameplayTagsManager::RequestAllGameplayTags()` is C++ only — not exposed to Blueprints. This function uses a **Data Table proxy** instead. Tags must be registered in `DT_ProjectTags` which mirrors `DefaultGameplayTags.ini`.
```
[Function: GetAllRegisteredTags] → Array<GameplayTag>
Step 1: Get Data Table Row Names (DT_ProjectTags)
Step 2: Create empty Array<GameplayTag> → LocalTags
Step 3: ForEachLoop (RowNames):
├─► Get Data Table Row (DT_ProjectTags, RowName)
│ → Break the GameplayTagTableRow struct → get "Tag" field
├─► Add "Tag" to LocalTags array
Step 4: Return LocalTags
```
**Node Search:** `Get Data Table Row Names`, `ForEachLoop`, `Get Data Table Row`, `Break GameplayTagTableRow`, `Add`
### 9.2 ValidateTag
**⚠️ UE5 Limitation:** `UGameplayTagsManager::RequestGameplayTag()` is not a direct Blueprint node. Use `Is Gameplay Tag Valid` instead.
```
[Function: ValidateTag(Tag)] → Boolean
Step 1: Is Gameplay Tag Valid (Tag)
├─► True → Return true
└─► False → Print Warning: "Invalid Tag: {Get Tag Name(Tag)}" → Return false
```
**Node Search:** `Is Gameplay Tag Valid`, `Get Tag Name`
### 9.3 GetTagDisplayName
```
[Function: GetTagDisplayName(Tag)] → Text
Step 1: Get Tag Display Name (Tag) → Return
```
**Node Search:** `Get Tag Display Name` (Blueprint pure node, available)
### 9.4 LogAllTags
```
[Function: LogAllTags] (Blueprint Callable)
Step 1: GetAllRegisteredTags() → Store in LocalTags
Step 2: ForEachLoop (LocalTags):
├─► Get Tag Name → ToString → Print String
Step 3: Print String: "Total tags: {Array Length(LocalTags)}"
```
### 9.5 ExportTagNamespace(Prefix: String) → String
```
[Function: ExportTagNamespace]
Step 1: GetAllRegisteredTags() → LocalTags
Step 2: Create empty String → Output
Step 3: ForEachLoop (LocalTags):
├─► Get Tag Name → ToString → TagString
├─► Branch: Does TagString start with Prefix?
│ True → Append TagString + "\n" to Output
└─► Continue
Step 4: Return Output
```
[OnAssetLoaded]
└─► Call GetAllRegisteredTags()
└─► Length == 0?
├─► Yes → Print Warning: "No Gameplay Tags registered! /Game/Framework/Core/DA_GameTagRegistry is empty."
└─► No → Log: "N tags registered."
```
### 9.2 Validation Function Logic (ValidateTag)
```
[ValidateTag(Tag)]
└─► Use GameplayTag::RequestGameplayTag(Tag.TagName)
└─► IsValid?
├─► Yes → return true
└─► No → Print Warning: "Invalid Tag: {Tag}" → return false
```
---
## 10. Tag Namespace Reference (Framework Canonical)
All framework-level tags are defined in `DefaultGameplayTags.ini`. The following namespaces are **reserved and documented** by this asset:
```
Player.State.Alive
Player.State.Dead
Player.State.Hidden
Player.State.Interacting
Player.Stress.Low / Mid / High / Critical
Player.Posture.Standing / Crouching / Prone / Vaulting
Interaction.Type.Pickup / Door / Drawer / Container / Inspect / Climb / Hide / Use / Combine
Interaction.Context.Requires.Key / Requires.Item / Locked / Disabled
Item.Type.Weapon / Consumable / KeyItem / Document / Collectible / Ammo / Tool
Item.Slot.PrimaryWeapon / SecondaryWeapon / Flashlight / Shield / Active
Narrative.Flag.* ← game-specific flags
Narrative.Phase.* ← story chapters / acts
Narrative.Choice.* ← choice consequence tags
Narrative.Ending.* ← ending conditions
Objective.Status.Active / Complete / Failed / Hidden
AI.Alert.None / Suspicious / Alerted / Engaged
AI.Archetype.Patrol / Ambush / Stalker / Passive
Save.Type.Checkpoint / HardSave / AutoSave
Achievement.* ← per-achievement tags
Environment.Atmosphere.* ← atmosphere state tags
Environment.Scare.* ← scare event tags
DeathSpace.Active ← triggers alt death space layer
```
**Usage Rule:** All systems must add the prefix `Framework.` to framework tags in code comments. Project-specific tags use `Game.` prefix. Example: `Framework.Interaction.Type.Pickup` vs `Game.Narrative.Flag.BasementDoorOpened`.
---
## 11. Communication Matrix
| Target System | Method | Direction | Data |
|---------------|--------|-----------|------|
| All other systems | `GameplayTag` comparisons | Read-only | Framework-defined tag values |
This asset does not talk to other systems directly. All communication is passive — other systems read tag values from the project's tag table.
---
## 12. Validation Checklist
- [ ] All namespace prefixes documented in the Data Asset's `TagNamespace` text field
- [ ] `bIsFrameworkTag` set to `true` for framework namespaces
- [ ] At least one project-specific tag exists (e.g. `Game.Narrative.Flag.PrologueComplete`) to validate the workflow
- [ ] `DefaultGameplayTags.ini` contains ALL documented tags
- [ ] No duplicate tag names across namespaces
- [ ] No raw string comparisons exist in any other system (all must use `HasTag` / `MatchesTag`)
---
## 13. Reuse Notes
- The `Game.` namespace is reserved for **project-specific** tags and is empty in the framework distribution.
- To add new tags: edit `DefaultGameplayTags.ini` or use the **Gameplay Tags** editor under **Project Settings**.
- The Data Asset is purely documentary — the real tag data lives in the project's tag table. This file exists so systems have a single place to look up "what tags exist in the framework" without reading the `.ini` file directly.
---
## 14. Manual Implementation Guide
> **For human implementer:** Follow these steps to build `DA_GameTagRegistry` in UE5 Blueprints.
> **⚠️ UE5 BP Limitation:** `UGameplayTagsManager::RequestAllGameplayTags()` is C++ only. This implementation uses a **Data Table proxy** (`DT_ProjectTags`) as the 100% Blueprint workaround. You must create and maintain `DT_ProjectTags` alongside your `DefaultGameplayTags.ini`.
### 14.0 Prerequisite: Create the Tag Data Table
Before implementing the Data Asset, create the proxy Data Table:
1. Right-click in Content Browser → **Miscellaneous → Data Table**
2. Row Structure: `GameplayTagTableRow`
3. Name: `DT_ProjectTags`
4. Save to: `Content/Framework/Core/`
5. Add rows: one per tag (row name = anything, fill the "Tag" field with your GameplayTag)
6. Go to **Project Settings → GameplayTags → Gameplay Tag Table List** → click `+` → select `DT_ProjectTags`
- This auto-registers these tags with the engine's tag manager.
### 14.1 Class Setup
1. Right-click in Content Browser → **Miscellaneous → Data Asset**
2. Select parent class: `PrimaryDataAsset`
3. Name: `DA_GameTagRegistry`
4. Save to: `Content/Framework/Core/`
### 14.2 Variables
Add to Class Defaults:
| Variable | Type | Instance Editable | Default | Category |
|----------|------|------------------|---------|----------|
| `TagNamespace` | `Text` | ✓ | *"Framework tag namespace documentation"* | Documentation |
| `bIsFrameworkTag` | `Boolean` | ✓ | `true` | Documentation |
| `TagDataTable` | `Data Table` (Object Reference) | ✓ | `DT_ProjectTags` | Config |
### 14.3 Function Implementations
#### `GetAllRegisteredTags()` → `Array<GameplayTag>` *(Blueprint Pure)*
**Purpose:** Returns all tags from the Data Table proxy. This replaces the C++-only `UGameplayTagsManager::RequestAllGameplayTags()`.
**Node-by-Node Logic:**
```
[Function: GetAllRegisteredTags] (Pure, no execution pins)
Step 1: Get Data Table Row Names (TagDataTable) → returns Array<Name>
Step 2: Create empty Array<GameplayTag> → LocalTags
Step 3: ForEachLoop over RowNames:
├─► Get Data Table Row (TagDataTable, Array Element)
│ → Struct Pin: Break GameplayTagTableRow
│ → Get "Tag" field (type: GameplayTag)
├─► Add "Tag" to LocalTags array
Step 4: Return LocalTags
```
**Nodes to Search:** `Get Data Table Row Names`, `ForEachLoop`, `Get Data Table Row`, `Break GameplayTagTableRow`, `Add`, `Array`
**⚠️ Note:** Since this is a **Pure** function, the ForEachLoop must be inside a **Pure function graph** (which supports loops in UE5). If your UE version doesn't support loops in Pure functions, make this a **BlueprintCallable** (impure) function instead.
#### `GetTagDisplayName(Tag: GameplayTag)` → `Text` *(Blueprint Pure)*
**Node-by-Node Logic:**
```
[Function: GetTagDisplayName]
Input Tag → Get Tag Display Name (Tag) → Return
```
**Node Search:** `Get Tag Display Name` — this IS available in Blueprint (part of GameplayTags plugin).
#### `ValidateTag(Tag: GameplayTag)` → `Boolean` *(Blueprint Pure)*
**⚠️ UE5 Limitation:** `UGameplayTagsManager::RequestGameplayTag()` is not directly available in BP. Use `Is Gameplay Tag Valid` instead.
**Node-by-Node Logic:**
```
[Function: ValidateTag]
Step 1: Is Gameplay Tag Valid (Tag)
├─► True → Return true
└─► False → Print Warning: "Invalid Tag: " + Get Tag Name(Tag) → Return false
```
**Node Search:** `Is Gameplay Tag Valid`, `Get Tag Name`
**⚠️ Note:** `Is Gameplay Tag Valid` returns true only if the tag is registered in the engine's tag table (which `DT_ProjectTags` populates via Project Settings). Tags NOT in the table will return false.
#### `LogAllTags()` → *(void)* *(Blueprint Callable)*
**Editor Only.** Prints all tags from the Data Table to the output log.
**Node-by-Node Logic:**
```
[Function: LogAllTags]
Step 1: Call GetAllRegisteredTags() → LocalTags
Step 2: ForEachLoop (LocalTags):
├─► Get Tag Name (Array Element) → ToString → Print String
Step 3: Print String: "Total tags: " + Array Length(LocalTags)
```
**Node Search:** `ForEachLoop`, `Get Tag Name`, `ToString (String)`, `Print String`, `Array Length`
#### `ExportTagNamespace(NamespacePrefix: String)` → `String` *(Blueprint Callable)*
**Node-by-Node Logic:**
```
[Function: ExportTagNamespace]
Step 1: Call GetAllRegisteredTags() → LocalTags
Step 2: Create String variable → Output = ""
Step 3: ForEachLoop (LocalTags):
├─► Get Tag Name (Array Element) → ToString → TagString
├─► Branch: Does TagString start with NamespacePrefix? (use "Starts With" string node)
│ True → Append TagString + "\n" to Output (use "Append" or "Build String")
└─► Continue
Step 4: Return Output
```
**Node Search:** `Starts With (String)`, `Append`, `Build String`, `ForEachLoop`
### 14.4 External Initialization
Since Data Assets have no `BeginPlay`, call validation from your GameInstance or StateManager:
**In `GI_GameFramework.Init()` or `BPC_StateManager.BeginPlay()`:**
```
Step 1: Get DA_GameTagRegistry (hard reference or Load Asset)
Step 2: Call DA_GameTagRegistry.GetAllRegisteredTags() → LocalTags
Step 3: Branch: Array Length(LocalTags) == 0
├─► True → Print Warning: "No tags in DT_ProjectTags!"
└─► False → Print String: "N tags registered: " + Array Length
Step 4: [Debug builds only] Call DA_GameTagRegistry.LogAllTags()
```
### 14.5 Networking
No replication needed. This is a read-only Data Asset with a read-only Data Table reference. All clients load identical copies from disk.
### 14.6 Blueprint Build Checklist
- [ ] Create Data Table `DT_ProjectTags` (Row Structure: `GameplayTagTableRow`)
- [ ] Populate `DT_ProjectTags` with all framework tags from Section 10
- [ ] Add `DT_ProjectTags` to `Project Settings → GameplayTags → Gameplay Tag Table List`
- [ ] Create Data Asset `DA_GameTagRegistry` (Parent: `PrimaryDataAsset`)
- [ ] Add variables: `TagNamespace` (Text), `bIsFrameworkTag` (Boolean), `TagDataTable` (Data Table Ref → DT_ProjectTags)
- [ ] Implement `GetAllRegisteredTags` using Data Table Row iteration
- [ ] Implement `GetTagDisplayName` using `Get Tag Display Name` node
- [ ] Implement `ValidateTag` using `Is Gameplay Tag Valid` node
- [ ] Implement `LogAllTags` (editor-only, prints all tags)
- [ ] Implement `ExportTagNamespace` (string prefix filtering)
- [ ] Add external initialization call from `GI_GameFramework.Init()` or `BPC_StateManager.BeginPlay()`
- [ ] Verify: all tags from Section 10 exist in `DT_ProjectTags`
- [ ] Verify: `ValidateTag` returns true for registered tags, false for unregistered
---
## 15. Multiplayer Networking
**Replication: None needed.** This Data Asset is read-only configuration. All clients load identical copies from disk. GameplayTag data lives in `DefaultGameplayTags.ini` which is identical on all instances.
**Authority: N/A.** No runtime state changes.
All framework documentation has been updated to use `DA_GameTagRegistry`. No functionality has changed — only the prefix was corrected.

View File

@@ -1,11 +1,17 @@
# FL_GameUtilities — Blueprint Function Library
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/FL_GameUtilities.h` provides all utility functions (subsystem access, math, tags, text, screen, debug). All functions are `BlueprintCallable` — they appear under `Framework|Utilities` in every BP right-click menu. **Nothing to build.** The spec below documents each function's API for reference. The old "Blueprint-Only Alternative" section is kept for context but is no longer needed — the C++ class is the canonical implementation. See `docs/developer/cpp-integration-guide.md` for usage patterns.
>
> ---
## Asset Details
| Property | Value |
|----------|-------|
| **Class** | [`FL_GameUtilities`] |
| **Parent** | [`BlueprintFunctionLibrary`] |
> **⚠️ UE5 BP Limitation:** `BlueprintFunctionLibrary` requires C++ to create. This is the ONE file in the framework that needs a minimal C++ class. See Section "Blueprint-Only Alternative" below for the pure BP workaround using Macro Library + direct engine nodes.
| **Folder** | [`Framework/Core/`] |
| **Categorization** | [Core\Framework] |
@@ -88,9 +94,9 @@ This is a pure Function Library. It has **no variables, no event dispatchers, no
| Function | Category | Inputs | Outputs | Description |
|----------|----------|--------|---------|-------------|
| [`LogDebug`] | Debug\Logging | `Category: Name`, `Message: String`, `Color: LinearColor` | — | Conditional screen + log output. Only fires in editor/development builds. Stripped in shipping builds via `DO_CHECK` flag. |
| [`DrawDebugSphere`] | Debug\Drawing | `WorldContext: Object`, `Center: Vector`, `Radius: Float`, `Color: LinearColor`, `Duration: Float` | — | Draws a debug sphere for visual debugging in-editor. Stripped in shipping builds. |
| [`DrawDebugString3D`] | Debug\Drawing | `WorldContext: Object`, `Location: Vector`, `Text: String`, `Color: LinearColor`, `Duration: Float` | — | Draws a 3D world-space debug string. Stripped in shipping builds. |
| [`LogDebug`] | Debug\Logging | `Category: Name`, `Message: String`, `Color: LinearColor`, `WorldContext: Object` | — | Conditional screen + log output. Uses `Branch: Is Editor Build` → then Print String + Draw Debug String. Inactive in shipping builds. |
| [`DrawDebugSphere`] | Debug\Drawing | `WorldContext: Object`, `Center: Vector`, `Radius: Float`, `Color: LinearColor`, `Duration: Float` | — | Draws a debug sphere. **BP node exists:** `Draw Debug Sphere`. Stripped in shipping. |
| [`DrawDebugString3D`] | Debug\Drawing | `WorldContext: Object`, `Location: Vector`, `Text: String`, `Color: LinearColor`, `Duration: Float` | — | Draws 3D world-space debug string. **BP node exists:** `Draw Debug String`. Stripped in shipping. |
## Blueprint Flow
@@ -126,18 +132,41 @@ All functions can be called from Construction Scripts since they are pure (no wo
## Reuse Notes
- This library ships verbatim to every project.
- Add project-specific helpers in a separate `FL_ProjectUtilities` child library that references this one.
- Do **not** add game-specific functions here — extend with a new Function Library inheriting from this pattern.
- Debug functions are automatically stripped in Shipping builds via `DO_CHECK` preprocessor — no manual removal needed.
- This library ships as a minimal C++ `BlueprintFunctionLibrary` (required for static function libraries in UE5).
- Add project-specific helpers in a separate child library or Macro Library.
- Do **not** add game-specific functions here — extend with a new library.
- For a 100% Blueprint approach, see "Blueprint-Only Alternative" below.
## Success Criteria
1. Any Blueprint in the project can call `RemapFloat` from the Math category without errors.
1. Any Blueprint can call `RemapFloat` from the Math category without errors.
2. `GetSubsystemSafe` returns `None` instead of crashing when a subsystem doesn't exist.
3. `FormatTime(3661.0)` returns `01:01:01`.
4. `LogDebug` prints to both the output log and the viewport in editor builds.
5. `LogDebug` produces no output in a shipping build.
5. `LogDebug` produces no output in a shipping build (via `Is Editor Build` branch).
## Blueprint-Only Alternative
If you cannot compile C++, replace `FL_GameUtilities` with direct Blueprint nodes or a **Blueprint Macro Library**:
| FL_GameUtilities Function | Pure BP Equivalent (no C++ needed) |
|--------------------------|-----------------------------------|
| `GetSubsystemSafe` | `Get Game Instance``Get Subsystem (Class)``Is Valid` branch |
| `GetGameFramework` | `Get Game Instance``Cast to GI_GameFramework` |
| `GetPlayerController` | `Get Player Controller (0)``Cast to PC_CoreController` |
| `HasGameplayTag` | `Does Actor Have Tag` (BP node) |
| `AddGameplayTagToActor` | `Get Actor Gameplay Tag Container``Add Tag` |
| `RemoveGameplayTagFromActor` | `Get Actor Gameplay Tag Container``Remove Tag` |
| `MakeTagFromString` | `Make Literal Gameplay Tag` OR `Get Gameplay Tag from Name` |
| `FindComponentByInterface` | `Get Component by Class` + `Does Implement Interface` |
| `RemapFloat` | `Map Range Clamped` (built-in BP math node) |
| `LerpClamped` | `Lerp` + `Clamp (Float)` |
| `FormatTime` | Custom BP function using division/modulo |
| `LogDebug` | `Branch: Is Editor Build``Print String` |
| `DrawDebugSphere` | `Draw Debug Sphere` (built-in BP node) |
| `DrawDebugString3D` | `Draw Debug String` (built-in BP node) |
**Blueprint Macro Library:** Create a **Macro Library** asset (`Content/Framework/Core/ML_GameUtilities`). Macros support `WorldContext`, execution pins, and can call engine nodes — no C++ required. The trade-off: macros are NOT "static pure" — they need execution flow pins.
---

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,9 @@
# I_InterfaceLibrary — Interface Collection
> **⚡ C++ Status: Full Implementation** — All 9 interfaces (`I_Interactable`, `I_Damageable`, `I_Persistable`, `I_Lockable`, `I_Holdable`, `I_UsableItem`, `I_Toggleable`, `I_Adjustable`, `I_Inspectable`) are defined in C++ at `Source/PG_Framework/Public/Core/I_InterfaceLibrary.h` as `BlueprintNativeEvent`. **Do NOT create Blueprint Interface assets.** On any BP actor: Class Settings → Interfaces → Add → type the interface name (e.g., `UInteractable`). Compile, and the interface events appear in your Event Graph. The spec below documents each interface's functions and purpose for reference. See `docs/developer/cpp-integration-guide.md`.
>
> ---
## Overview
This document defines all **9 Blueprint Interfaces** that form the backbone of cross-system communication in the framework. Interfaces are the **preferred communication method** over direct references — they decouple the caller from the callee's concrete class. Any Actor or Component can implement any of these interfaces, and any system can query for them and call them without knowing the underlying type.

View File

@@ -1,5 +1,9 @@
# 04 — Game Instance Kernel (`GI_GameFramework`)
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/GI_GameFramework.h` provides the complete application kernel: game phase state machine, session flags, save slot management, service resolution, platform initialization, and all event dispatchers. **Create a BP child** (Blueprint Class → All Classes → `GI_GameFramework`) for configuration only — set `TagRegistry` in Class Defaults. Do NOT rebuild the state machine or service registry in BP. See `docs/developer/cpp-integration-guide.md` for exact setup steps.
>
> ---
## Purpose
The single persistent object that lives for the entire application session. It owns global subsystems, manages save slot ownership, and provides the canonical entry point for service resolution.

View File

@@ -1,6 +1,8 @@
# GM_CoreGameMode — Core GameMode
---
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/GM_CoreGameMode.h` provides chapter transitions, death handling, ending triggers, and pause control. **Create a BP child** (Blueprint Class → All Classes → `GM_CoreGameMode`). In Class Defaults → **"Classes"** section (inherited from `AGameModeBase`): set `PlayerControllerClass`, `PlayerStateClass`, `GameStateClass`, and `DefaultPawnClass`. Do NOT redeclare these variables — they're inherited. See `docs/developer/cpp-integration-guide.md` for exact setup steps.
>
> ---
## Asset Details

View File

@@ -1,5 +1,9 @@
# 06 — Core GameState (`GS_CoreGameState`)
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/GS_CoreGameState.h` provides 5 replicated state variables (ElapsedPlayTime, ActiveChapterTag, ActiveNarrativePhase, bEncounterActive, ActiveObjectiveTags) with `OnRep_` handlers and dispatchers. **Create a BP child** (Blueprint Class → All Classes → `GS_CoreGameState`). No logic to add — all replication works out of the box. Set in `BP_CoreGameMode` → Class Defaults → Classes → `Game State Class`. See `docs/developer/cpp-integration-guide.md`.
>
> ---
## Purpose
The single replicated game state object visible to all players (and potential future co-op clients). Tracks session-level progression data such as elapsed play time, active chapter, narrative phase, encounter activity, and active objectives.

View File

@@ -3,9 +3,261 @@
## Purpose
The single source of truth for every item in the game. Each item is represented by one `DA_ItemData` Primary Data Asset. No item data lives in Blueprint logic; all item definitions are data-driven from these assets.
**C++ Status:** ✅ Full — `Source/PG_Framework/Public/Inventory/DA_ItemData.h` + `.cpp`. All enums, structs, variables, and editor validation are implemented in C++ with `UPROPERTY` metadata (EditCondition, EditConditionHides, ClampMin/Max) for designer-friendly editor UX.
> **Concrete examples:** See [`docs/game/`](../../game/README.md) for step-by-step walkthroughs of building actual items: [Flashlight Tool](../../game/item-flashlight.md), [Pistol Weapon](../../game/item-pistol.md), [MedKit Consumable](../../game/item-medkit.md), [Keycard Key Item](../../game/item-keycard.md). Each shows exact Blueprint graphs, component lists, and verification checklists.
> **Critical distinction:** `DA_ItemData` is a **Data Asset** — it lives in the Content Browser, NOT in the level. It defines *what* an item is (name, weight, icon, effects, type). To place an item in the world, create a `BP_ItemPickup` actor (spec #25) and assign the `DA_ItemData` to it. The Data Asset is the item's *identity card*; `BP_ItemPickup` is its *physical body* in the world. See [How to Set Up](#how-to-set-up) below.
---
## How to Set Up
### Step 1 — Create the Data Asset
```
Content Browser → Framework/DataAssets/Items/
Right-click → Miscellaneous → Data Asset
Class: DA_ItemData
Name: DA_Item_[ShortName] (e.g., DA_Item_MedKit, DA_Item_RustyKey, DA_Item_Pistol)
```
### Step 2 — Fill in Core Properties
Open the Data Asset and fill in:
| Property | Purpose | Example |
|----------|---------|---------|
| `Item Tag` | **Unique identifier** — registered in `DA_GameTagRegistry` | `Framework.Item.Consumable.MedKit` |
| `Display Name` | Player-visible name | "MedKit" |
| `Description` | Flavor text / gameplay hint | "A standard medical kit. Restores 25 health." |
| `Icon` | Inventory thumbnail texture | `T_MedKit_Icon` |
| `World Mesh` | 3D mesh when dropped in world | `SM_MedKit` |
| `Weight` | Weight units for carry limit | `1.0` |
| `Stack Limit` | Max per slot (weapons = 1, ammo = 999, consumables = 5) | `5` |
| `Item Type` | Category enum — controls which sub-data to fill | `Consumable` |
### Step 3 — Fill Type-Specific Sub-Data
Based on `Item Type`, fill the relevant sub-struct (other sub-panels auto-hide via `EditCondition`):
| Item Type | Sub-Struct to Fill | Example Values |
|-----------|-------------------|-----------------|
| `Weapon` / `Tool` | `Equipment Data` | Damage=0, FireRate=0, MagazineSize=0, Slot=PrimaryWeapon |
| `Consumable` | `Consumable Data` | HealthRestore=25, UseDuration=2.0, bConsumedOnUse=true |
| `Ammo` | `AmmoData` (legacy) | AmmoTypeTag, PerPickupCount |
| `KeyItem`, `Document`, `Collectible`, `Resource`, `Misc` | None — core properties only | |
### Step 4 — Build the BP_ItemPickup Blueprint (one-time)
The Data Asset does **not** appear in the world. You need a Blueprint Actor that references it. Here is the exact setup:
#### 4.1 — Create the Blueprint
```
Content Browser → Framework/Inventory/
Right-click → Blueprint Class → Actor
Name: BP_ItemPickup
```
#### 4.2 — Add Components
Open `BP_ItemPickup`**Viewport** tab → **Add Component** (top-left button):
| # | Component Class | Name | Purpose |
|---|----------------|------|---------|
| 1 | `StaticMeshComponent` | `Mesh` | Renders the item's 3D model in the world |
| 2 | `SphereComponent` | `InteractionCollision` | Detects when the player is near enough to pick up |
| 3 | *(optional)* `AudioComponent` | `PickupSound` | Plays a sound when collected |
**Select `InteractionCollision` → Details panel:**
- `Sphere Radius``150.0` (how close the player must be)
- Under **Collision Presets** → set to `OverlapOnlyPawn` (only the player triggers it)
#### 4.3 — Create the Config Variable
1. In the **My Blueprint** panel (left side), click **+ Variable**
2. Name: `Config`
3. Variable Type: **Struct → `S_PickupConfig`** (you may need to create this struct first — see spec #25 for its fields)
If `S_PickupConfig` doesn't exist yet, create it manually:
```
Content Browser → Framework/Inventory/
Right-click → Blueprints → Structure
Name: S_PickupConfig
Add fields:
ItemData → Type: DA_ItemData (Object Reference)
Quantity → Type: Integer, default: 1
bAutoRotate → Type: Boolean, default: true
bBobUpDown → Type: Boolean, default: true
BobAmplitude → Type: Float, default: 10.0
BobFrequency → Type: Float, default: 1.0
```
4. After creating the variable, select it → Details panel:
- **Instance Editable** → ✓ checked (so you can set it per-instance in the level)
- **Category** → `Pickup Config`
- **Tooltip** → "The item data asset this pickup represents"
#### 4.4 — Wire the Construction Script
Go to the **Construction Script** tab (or open the Construction Script graph). This runs every time you change the Config in the editor, so the mesh updates immediately.
```
Construction Script
├─ Get Config → Break S_PickupConfig → ItemData
│ │
│ ├─ Is Valid (ItemData)?
│ │ ├─ True → continue
│ │ └─ False → Print String "No ItemData assigned" (Editor-only) → Return
│ │
│ └─ ItemData → Get World Mesh (this is a soft reference — may not be loaded)
│ │
│ ├─ Is Valid (WorldMesh)?
│ │ ├─ True:
│ │ │ → Set Static Mesh (Mesh component, New Mesh = WorldMesh)
│ │ │ → ItemData → Get Display Name → Set Actor Label
│ │ └─ False:
│ │ → ItemData → World Mesh → Async Load Asset
│ │ → On Load Complete → Set Static Mesh
│ │
│ └─ ItemData → Get Icon → (cache for UI prompt)
```
**Key node-by-node:**
1. Drag the `Config` variable from My Blueprint → **Get Config**
2. **Break S_PickupConfig** (right-click the pin → Split Struct Pin, or drag off → "Break")
3. From the `ItemData` pin, drag off → Create a `IsValid` node
4. From the valid `ItemData` output, drag off → type `Get World Mesh` (this appears because `WorldMesh` is a `UPROPERTY` on `DA_ItemData`; if using soft reference, you'll get a `TSoftObjectPtr<UStaticMesh>` — connect to **Async Load Asset** or **Load Synchronous** for Construction Script)
5. From the resolved mesh, connect to `Set Static Mesh` node → target is the `Mesh` component (drag Mesh from the Components panel into the graph → Get)
#### 4.5 — Wire Interaction (I_Interactable)
**Step A — Add the Interface:**
1. **Class Settings** (toolbar button) → **Interfaces****Add** → select `UInteractable` (the C++ interface from `I_InterfaceLibrary.h`)
2. Compile. Now `Interact`, `Can Interact`, `Get Interaction Prompt` events appear in the right-click menu.
**Step B — Wire the Events:**
```
Event Interact (from I_Interactable)
│ Interactor: AActor*
├─ Get Config → Break → ItemData
├─ Interactor → Get Component by Class (BPC_InventorySystem)
│ │
│ └─ Is Valid?
│ ├─ False → Return (no inventory system on interactor)
│ └─ True → InventorySystem
│ │
│ ├─ Call Can Add Item(ItemData, Quantity from Config)
│ │ │
│ │ ├─ False → Print "Inventory Full" → Return
│ │ └─ True:
│ │ ├─ Call Add Item(ItemData, Quantity)
│ │ ├─ Play Sound at Location (PickupSound, GetActorLocation)
│ │ ├─ Destroy Actor
│ │ └─ Return
│ │
│ └─ (If ItemData has bIsInfinite or respawn logic — skip Destroy)
Event Can Interact (from I_Interactable)
│ Interactor, OutBlockReason (by ref)
├─ Is Config.ItemData Valid?
│ ├─ False → OutBlockReason = "No item data" → Return False
│ └─ True → Return True
Event Get Interaction Prompt (from I_Interactable)
├─ Get Config → ItemData → Get Display Name
└─ Return "Pick up [ItemName]"
```
#### 4.6 — Add Bobbing Rotation (optional polish)
In **Event Graph → Event BeginPlay**:
```
Event BeginPlay
├─ Get Config → Break → bAutoRotate
│ └─ True → Add Timeline → name: "BobAndRotate"
│ ├─ Update track:
│ │ ├─ Mesh → Add World Rotation (DeltaRotation = 0,0,Timeline.Rotation * 90)
│ │ └─ Mesh → Add World Offset (Delta = 0,0,Timeline.Bob * BobAmplitude * sin(time), ...)
│ │
│ └─ Timeline: float track 0→1 looping over 2 seconds
├─ Enable collision on InteractionCollision
└─ Bind OnComponentBeginOverlap(InteractionCollision) → Highlight mesh, show prompt
```
#### 4.7 — Compile and Use
1. **Compile → Save**
2. **Drag `BP_ItemPickup` into your level**
3. Select the instance → Details panel → **Pickup Config → Item Data** → select `DA_Item_MedKit`
4. Set `Quantity``1` (or more for stacked items)
5. The **Construction Script** runs immediately — the mesh should appear in the viewport
6. Position the actor where you want it
**Verification in PIE (Play In Editor):**
- The pickup should show its mesh, slowly rotating
- Walk up to it — the overlap event fires
- Press Interact — item goes into inventory, pickup disappears
- Open inventory → item should be in a slot with the correct name and icon
```
DA_Item_MedKit (Content Browser) ← defines IDENTITY
↑ referenced by
BP_ItemPickup (Actor in level) ← physical BODY in world
├── StaticMeshComponent "Mesh" ← renders WorldMesh from Data Asset
├── SphereComponent "Collision" ← detects player proximity
└── Config struct ← holds pointer to DA_ItemData + quantity
↑ interacts via
BPC_InteractionDetector → I_Interactable → BPC_InventorySystem.AddItem()
```
> **For concrete, step-by-step examples** of building specific item types (flashlight, pistol, medkit, keycard) with complete Blueprint graphs, component lists, and wiring diagrams, see the [`docs/game/`](../../game/README.md) directory.
---
## Single Identity Tag — Why One Tag Is Enough
Each item has exactly one `ItemTag` GameplayTag. This is intentional — the tag is the item's **unique identifier**, not a categorization bucket.
Categorization and behavioral flags are handled by separate, purpose-built properties:
| Concern | Mechanism | Example |
|---------|-----------|---------|
| **Identity** | `ItemTag` (single GameplayTag) | `Framework.Item.Consumable.MedKit` |
| **Category** | `ItemType` (enum) | Consumable, Weapon, KeyItem, Document, etc. |
| **Is non-droppable?** | `bIsKeyItem` (bool) | Key items are auto-protected from drop/clear |
| **Can be dropped?** | `bCanBeDropped` (bool) | Ammo/documents can be dropped; key items cannot |
| **Has 3D inspection?** | `bHasInspectMode` (bool) | Enables rotate-examine on pickup |
| **Crafting relationships** | `CombinesWith` (array of GameplayTag) | `MedKit` + `Bandage``SuperMedKit` |
| **Project-specific data** | `CustomProperties` (TMap<FName, FString>) | Key=`"QuestRewardID"`, Value=`"Q12"` |
| **Tag-based queries** | Hierarchical tag structure | `ItemTag.MatchesTag(Framework.Item.Consumable)` returns true for all consumables |
**Why not multiple ItemTags?**
- A single unique ID prevents ambiguity: inventory lookups, save/load references, and crafting recipes all need one canonical name for each item.
- Hierarchical GameplayTags support partial matching: `Framework.Item.Consumable.MedKit` matches `Framework.Item.Consumable` for category-wide queries.
- Adding a `FGameplayTagContainer ItemTags` would create confusion — which tag is the "real" ID? Do you check all of them? Do you sort by the first one?
- The `ItemType` enum + boolean flags + `CombinesWith` array already cover every use case without tag collision risks.
**If you truly need multiple filtering tags**, the `CustomProperties` map can store them (e.g., `"FilterTags"``"Flammable,Magnetic,QuestItem"`), and your BP can parse the comma-separated string at runtime. But in practice, this is rarely needed because:
- `ItemType` already categorizes items
- `bIsKeyItem` / `bCanBeDropped` already handle behavioral flags
- `CombinesWith` already handles cross-item relationships
- Hierarchical tag matching handles category queries (e.g., `MatchesTag(Framework.Item.Weapon)`)
---
## Dependencies
- **Requires:** `GameplayTag` system (`GI_GameTagRegistry`), `UPrimaryDataAsset` (engine base)
- **Required By:** `BPC_InventorySystem`, `BPC_EquipmentSlotSystem`, `BPC_ConsumableSystem`, `BPC_AmmoResourceSystem`, `BPC_ItemCombineSystem`, `BPC_KeyItemSystem`, `BPC_ActiveItemSystem`, `DA_ItemDatabase` (collection)
- **Requires:** `GameplayTag` system (`DA_GameTagRegistry`), `UPrimaryDataAsset` (engine base)
- **Required By:** `BPC_InventorySystem`, `BPC_EquipmentSlotSystem`, `BPC_ConsumableSystem`, `BPC_AmmoComponent` (70), `BPC_ItemCombineSystem`, `BPC_KeyItemSystem`, `BPC_ActiveItemSystem`
- **Engine/Plugin Requirements:** `GameplayTags`, `AssetManager` (Primary Data Asset registration)
- **Parent Class:** `UPrimaryDataAsset`
@@ -13,9 +265,10 @@ The single source of truth for every item in the game. Each item is represented
| Property | Value |
|----------|-------|
| **Parent Class** | `UPrimaryDataAsset` |
| **Class Type** | Blueprint Function Library (Data Asset) |
| **Asset Path** | `Content/Data/Items/DA_Item_[Name]` |
| **Implements Interfaces** | None |
| **C++ Class** | `UDA_ItemData` (in `Source/PG_Framework/Public/Inventory/DA_ItemData.h`) |
| **Class Type** | Primary Data Asset |
| **Asset Path** | `Content/Framework/DataAssets/Items/DA_Item_[Name]` |
| **Implements Interfaces** | None (passive data container) | |
---
@@ -87,19 +340,28 @@ No custom structs defined in this Data Asset class. Consumed structs from other
## 4. Functions
No runtime functions. This is a pure data container. The only accessible operations are direct variable reads from Blueprints that hold a reference to the asset.
`DA_ItemData` is a pure data container. All properties are read directly from Blueprints that hold a reference to the asset. The following functions exist in the C++ class (`Source/PG_Framework/Public/Inventory/DA_ItemData.h`):
### Runtime (All BlueprintCallable / BlueprintPure)
| Function | Returns | Description |
|----------|---------|-------------|
| `GetResistance(DamageType)` (on `DA_EquipmentConfig`) | `float` | Returns resistance value for a damage type (armor/equipment) |
| *(All UPROPERTY reads)* | — | `ItemTag`, `DisplayName`, `ItemType`, `Weight`, `StackLimit`, etc. are read directly — no function call needed |
### Editor-Only Functions (for content team validation)
#### `ValidateItemData` → `Bool` (BlueprintCallable, Editor only)
- **C++ Implementation:** Full — `Source/PG_Framework/Private/Inventory/DA_ItemData.cpp`
- **Description:** Runs editor validation checks (tag uniqueness, required fields filled).
- **Parameters:** None
- **Parameters:** `OutErrors` (FString, out) — human-readable error messages
- **Flow:**
1. Check `ItemTag != None` — if None, log warning
2. Check `DisplayName != ""` — if empty, log warning
3. If `StackLimit < 1`, reset to 1
4. If `bIsKeyItem` then `bCanBeDropped = false` (forced)
5. Return true if all validations pass
1. Check `ItemTag != None` — if None, append error
2. Check `DisplayName != ""` — if empty, append error
3. If `StackLimit < 1`, append error
4. If `bIsKeyItem` then warn if `bCanBeDropped == true`
5. If Consumable, check at least one effect value > 0
6. Return true if all validations pass; false if any failed
---
@@ -115,21 +377,40 @@ No event overrides. Data Assets do not tick or have BeginPlay.
---
## 7. Blueprint Graph Logic Flow
## 7. Content Creation Flow
No blueprint graph. This asset is created and edited in the Content Browser via "Create Advanced Asset -> Blueprint -> Data Asset -> DA_ItemData".
`DA_ItemData` has **no blueprint graph**. It is created and edited in the Content Browser as a Data Asset instance.
```mermaid
flowchart LR
A[Content Browser] --> B[Right-click > Data Asset > DA_ItemData]
B --> C[Name: DA_Item_MedKit]
C --> D[Fill ItemTag: Item.MedKit]
D --> E[Fill DisplayName: Med Kit]
E --> F[Assign Icon, Mesh]
F --> G[Set ItemType: Consumable]
G --> H[Fill ConsumableData]
H --> I[Save Asset]
I --> J[Registered in Asset Manager]
A[Content Browser] --> B[Right-click > Miscellaneous > Data Asset]
B --> C[Select Class: DA_ItemData]
C --> D[Name: DA_Item_MedKit]
D --> E[Open Asset > Fill Properties]
E --> F[ItemTag: Framework.Item.Consumable.MedKit]
F --> G[DisplayName: Med Kit]
G --> H[Set Icon, WorldMesh]
H --> I[ItemType: Consumable]
I --> J[Fill ConsumableData: HealthRestore=25]
J --> K[Save Asset]
K --> L[Ready — referenced by BP_ItemPickup actors]
```
### How Systems Use the Data Asset (Read-Only)
```mermaid
flowchart TD
DA[DA_Item_MedKit<br/>Data Asset] -->|referenced by| PICKUP[BP_ItemPickup<br/>Actor in world]
PICKUP -->|OnInteract| INV[BPC_InventorySystem.AddItem(DA, 1)]
INV -->|stores reference| SLOT[FInventorySlot.Item = DA]
DA -->|reads EquipmentData| EQUIP[BPC_EquipmentSlotSystem]
DA -->|reads ConsumableData| CONSUM[BPC_ConsumableSystem]
DA -->|reads CombinesWith| COMBINE[BPC_ItemCombineSystem]
DA -->|reads bIsKeyItem| KEY[BPC_KeyItemSystem]
DA -->|reads ItemType| ACTIVE[BPC_ActiveItemSystem<br/>routes to correct handler]
INV -->|dispatches OnItemAdded| UI[WBP_InventoryMenu<br/>reads DisplayName, Icon, Description]
```
---
@@ -170,15 +451,18 @@ flowchart LR
## 10. Reuse Notes
- Create one `DA_ItemData` per item. Name convention: `DA_Item_[ShortName]` (e.g. `DA_Item_MedKit`, `DA_Item_Flashlight`, `DA_Item_KeyCard_Omega`).
- All `ItemTag` values must be registered in `GI_GameTagRegistry` before use.
- The `CustomProperties` map future-proofs any per-project additions without modifying the base asset class.
- **Create one `DA_ItemData` per item.** Naming convention: `DA_Item_[ShortName]` (e.g., `DA_Item_MedKit`, `DA_Item_Flashlight`, `DA_Item_KeyCard_Omega`).
- **Data Assets are not world actors.** They cannot be dragged into a level. To place an item in the world, create a `BP_ItemPickup` actor (spec #25) and assign the `DA_ItemData` to its `Config.ItemData` property.
- All `ItemTag` values must be registered in `DA_GameTagRegistry` before use. Use the hierarchical tag structure: `Framework.Item.[Type].[Name]`.
- The `CustomProperties` map future-proofs any per-project additions without modifying the base C++ class.
- For non-stackable items (weapons, key items, tools), set `StackLimit = 1`.
- For stacks, choose a sensible `StackLimit` (e.g. ammo = 999, consumables = 5).
- For stacks, choose a sensible `StackLimit` (e.g., ammo = 999, consumables = 5, resources = 99).
- The `AssetManager` should be configured with `PrimaryAssetType = Item` and `PrimaryAssetLabel = Item` for async loading support.
- Document items (`E_ItemType.Document`) additionally populate `BPC_DocumentArchiveSystem` with their content; the `Description` field serves as document body text.
- To add a new item property across all items, add a new variable to `DA_ItemData`. Do not create a separate asset class per item type — use the `ItemType` enum for branching logic.
- To add a new item property across all items, add a new `UPROPERTY` to `DA_ItemData` in C++. Do not create a separate asset class per item type — use the `ItemType` enum + `EditCondition` metadata for branching display.
- The `ValidateItemData` editor function can be exposed as a Python command for batch validation on check-in.
- **C++ EditCondition metadata** auto-hides irrelevant panels based on `ItemType` (e.g., `ConsumableData` only shows when `ItemType == Consumable`). Designers never see irrelevant fields.
- **Inventory systems interact with `DA_ItemData` through C++ `TObjectPtr<UDA_ItemData>`** references — the Data Asset is never copied, only referenced by pointer. All inventory operations pass the `DA_ItemData*` pointer.
---

View File

@@ -0,0 +1,406 @@
# 150 — Platform Service Abstraction (`BPC_PlatformServiceAbstraction`)
> **Blueprint-Only Implementation** — UE 5.55.7 supports all platform detection and SDK routing from Blueprints via `UGameplayStatics::GetPlatformName()`, conditional compilation checks, and plugin API calls. This component is the **single source of truth** for platform identity, replacing the three fragmented detection systems that currently exist across the framework.
---
## Purpose
Central platform authority attached to the Game Instance. Detects the platform once at startup and provides a unified `EPlatformFamily` enum that ALL subsystems query instead of detecting platform independently. Routes platform-specific SDK calls (achievement submission to Steam/PSN/Xbox Live/Nintendo, cloud saves, overlays, input device profiles). Returns stub/no-op implementations on unsupported platforms so game code never needs `#if PLATFORM_PS5` checks.
## The Problem Being Solved
Currently the framework has **three independent platform detection systems**, each calling `GetPlatformName()` separately:
| System | Enum | Platform Families |
|--------|------|-------------------|
| `BPC_RenderPipelineManager` (149) | `EPlatformFamily` | PS5, PS4, Xbox_Series, Xbox_One, Switch, PC_High, PC_Low, SteamDeck |
| `SS_EnhancedInputManager` (128) | `E_InputPlatform` | PC_KeyboardMouse, Xbox, PS5_DualSense |
| `BPC_HapticsController` (148) | `EControllerPlatform` | PC_Generic, Xbox, PS5_DualSense, PS4_DualShock |
These are **different enums with different values** and don't talk to each other. If you change platform in one system, nothing cascades.
**After this component is integrated, all three systems query `BPC_PlatformServiceAbstraction.GetPlatformFamily()` instead of detecting platform themselves. The old enums are deprecated and mapped to `EPlatformFamily`.**
## Dependencies
- **Requires:** [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) (attached as component), [`DA_GameTagRegistry`](../01-core/01_DA_GameTagRegistry.md) (platform tag validation)
- **Required By:** [`BPC_RenderPipelineManager`](../12-settings/149_BPC_RenderPipelineManager.md) (GFX quality profile selection), [`SS_EnhancedInputManager`](../15-input/128_SS_EnhancedInputManager.md) (input device profile selection), [`BPC_HapticsController`](../12-settings/148_BPC_HapticsController.md) (controller platform detection), [`SS_AchievementSystem`](../11-meta/103_SS_AchievementSystem.md) (SDK routing), [`SS_SettingsSystem`](105_SS_SettingsSystem.md) (platform-aware defaults)
- **Engine/Plugin Requirements:** GameplayTags, `UGameplayStatics`, Platform SDK plugins (Steamworks, PSN, Xbox GDK, Nintendo SDK — all optional)
## Class Info
| Property | Value |
|----------|-------|
| **Parent Class** | `ActorComponent` |
| **Class Type** | Blueprint Component |
| **Asset Path** | `Content/Framework/Core/BPC_PlatformServiceAbstraction` |
| **Implements Interfaces** | None |
| **Attachment** | Game Instance (`GI_GameFramework`) |
---
## 1. Enums
### `EPlatformFamily` (UNIFIED — all systems use this)
| Value | Description | Input Device | Render Pipeline | Achievements SDK | Store |
|-------|-------------|-------------|-----------------|-----------------|-------|
| `Unknown = 0` | Not yet detected | — | — | — | — |
| `PC_Win64 = 1` | Windows PC (any GPU) | KB+M / Xbox / PS5 gamepad | Detected per GPU capability | Steam / EGS | Steam / EGS |
| `PC_Linux = 2` | Linux / SteamOS | Same as PC | Vulkan | Steam | Steam |
| `PS5 = 3` | PlayStation 5 | DualSense | Lumen + Nanite | PSN Trophies | PS Store |
| `PS5_Pro = 4` | PlayStation 5 Pro | DualSense | Lumen + PSSR | PSN Trophies | PS Store |
| `PS4 = 5` | PlayStation 4 / 4 Pro | DualShock 4 | Baked only | PSN Trophies | PS Store |
| `Xbox_Series = 6` | Xbox Series X\|S | Xbox Wireless | Lumen + Nanite | Xbox Live | MS Store |
| `Xbox_One = 7` | Xbox One / One X | Xbox Wireless | Baked only | Xbox Live | MS Store |
| `Switch = 8` | Nintendo Switch | Joy-Con / Pro | Baked + Proxy | Nintendo | eShop |
| `Switch_2 = 9` | Nintendo Switch 2 | Joy-Con 2 / Pro 2 | Baked + CSM + FSR | Nintendo | eShop |
| `SteamDeck = 10` | Steam Deck | Built-in / external | Baked/SSGI + CSM | Steam | Steam |
| `Mac = 11` | macOS | KB+M / gamepad | Metal / Baked | Steam | Steam / App Store |
### `EPlatformSDK`
| Value | Description |
|-------|-------------|
| `None = 0` | No SDK available (editor, generic build) |
| `Steam = 1` | Steamworks SDK (achievements, cloud, overlay, stats) |
| `PSN = 2` | PlayStation Network SDK (trophies, cloud, overlay, invites) |
| `XboxLive = 3` | Xbox Live / GDK (achievements, cloud, overlay, invites) |
| `Nintendo = 4` | Nintendo SDK (achievements, cloud, overlay) |
| `EGS = 5` | Epic Games Store SDK |
### `EPlatformOverlayType`
| Value | Description |
|-------|-------------|
| `Achievements = 0` | Show platform achievements/trophies page |
| `Friends = 1` | Open friends list |
| `Invite = 2` | Send game invite |
| `Store = 3` | Open store page (DLC, etc.) |
| `SocialFeed = 4` | Community / social feed |
---
## 2. Structs
### `SPlatformCapabilities`
| Field | Type | Description |
|-------|------|-------------|
| `Platform` | `EPlatformFamily` | Current platform |
| `SDK` | `EPlatformSDK` | Available platform SDK |
| `bSupportsLumen` | `bool` | Platform GPU supports Lumen GI |
| `bSupportsNanite` | `bool` | Platform GPU supports Nanite |
| `bSupportsHWRT` | `bool` | Platform supports hardware ray tracing |
| `bSupportsCloudSaves` | `bool` | Platform has cloud save API |
| `bSupportsAchievements` | `bool` | Platform has achievement/trophy system |
| `bSupportsOverlay` | `bool` | Platform has in-game overlay |
| `bSupportsRichPresence` | `bool` | Platform supports rich presence / "now playing" |
| `bSupportsUserGeneratedContent` | `bool` | Platform supports UGC/mod sharing |
| `bIsConsole` | `bool` | Is this a console platform (certification required)? |
| `bIsHandheld` | `bool` | Is this a handheld device? |
| `DefaultInputType` | `FName` | Default input device for this platform ("Gamepad", "Keyboard", "Touch") |
| `MaxLocalPlayers` | `int32` | Maximum local split-screen players |
| `StoreID` | `FString` | Platform store ID for DLC checks |
---
## 3. Variables
### Configuration (Instance Editable)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `bAutoDetectPlatform` | `bool` | `true` | `Config` | Auto-detect on Initialize |
| `OverridePlatform` | `EPlatformFamily` | `Unknown` | `Config` | Force a platform (for testing in editor) |
| `bEnableSDKIntegration` | `bool` | `true` | `Config` | Enable platform SDK calls |
### Internal (Private)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `CurrentPlatform` | `EPlatformFamily` | `Unknown` | `State` | Detected or overridden platform |
| `CurrentSDK` | `EPlatformSDK` | `None` | `State` | Active platform SDK |
| `PlatformCapabilities` | `SPlatformCapabilities` | — | `State` | Full capabilities snapshot |
| `bIsInitialized` | `bool` | `false` | `State` | Whether Initialize completed |
| `CachedGameInstance` | `GI_GameFramework` | `None` | `Cache` | Owner Game Instance reference |
---
## 4. Functions
### Public Functions
#### `Initialize` → `void`
- **Description:** Detect platform, resolve SDK, build capabilities, broadcast ready signal. Called once at Game Instance startup.
- **Flow:**
1. Get Owner → Cast to `GI_GameFramework` → cache
2. If `OverridePlatform != Unknown`: use override
3. Else if `bAutoDetectPlatform`: call `DetectPlatform()`
4. Resolve which SDK is available: check plugin state, build configuration
5. Build `SPlatformCapabilities` for detected platform
6. Register platform in `DA_GameTagRegistry` as `Framework.Platform.{Name}`
7. Set `bIsInitialized = true`
8. Broadcast `OnPlatformReady(CurrentPlatform)`
9. Broadcast `OnPlatformCapabilitiesReady(PlatformCapabilities)`
#### `DetectPlatform` → `EPlatformFamily`
- **Description:** Detects the current platform using UE5 APIs.
- **Flow:**
1. `UGameplayStatics::GetPlatformName()` → switch:
- "PS5" → check if Pro model available → `PS5_Pro` or `PS5`
- "PS4" → `PS4`
- "XboxOne" → `Xbox_One`
- "XSX" or "XboxAnaconda" → `Xbox_Series` (check if S model → adjust capabilities)
- "Switch" → check model revision → `Switch_2` or `Switch`
- "Win64" → check if running on Steam Deck (`IsSteamDeck()`) → `SteamDeck`
- "Win64" → `PC_Win64`
- "Linux" → `PC_Linux`
- "Mac" → `Mac`
2. Store in `CurrentPlatform`
3. Return `CurrentPlatform`
#### `GetPlatformFamily` → `EPlatformFamily`
- **Description:** Returns the current platform. **This is the function ALL other systems call.** Read-only, always available after initialization.
#### `GetSDK` → `EPlatformSDK`
- **Description:** Returns the available platform SDK. Used by achievement and save systems for routing.
#### `GetPlatformCapabilities` → `SPlatformCapabilities`
- **Description:** Returns the full capabilities struct. Read-only.
#### `SubmitAchievementToPlatform` → `bool`
- **Description:** Route an achievement unlock to the correct platform SDK.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `AchievementID` | `FString` | Platform-specific achievement ID |
- **Flow:**
1. Switch on `CurrentSDK`:
- `Steam` → call Steamworks `ISteamUserStats::SetAchievement(AchievementID)` + `StoreStats()`
- `PSN` → call PSN Trophy API `UnlockTrophy(AchievementID)`
- `XboxLive` → call Xbox Live `AchievementsService::UpdateAchievement(AchievementID, 100)`
- `Nintendo` → call Nintendo achievement API
- `None` → log "Achievement submitted (editor mode)" → return true (no-op stub)
2. Return true if successful, false on SDK error
#### `SyncCloudSave` → `bool`
- **Description:** Upload or sync save data to platform cloud.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `SlotIndex` | `int32` | Which save slot |
| `SaveData` | `TArray<uint8>` | Serialized save data |
- **Flow:** Route to Steam Cloud / PSN Cloud / Xbox Cloud / Nintendo Cloud based on `CurrentSDK`.
#### `ShowPlatformOverlay` → `void`
- **Description:** Open a platform-specific overlay.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `OverlayType` | `EPlatformOverlayType` | Which overlay to show |
- **Flow:**
1. Switch on `CurrentSDK`:
- `Steam``ISteamFriends::ActivateGameOverlay(URL)`
- `PSN` → System software overlay API
- Others → nop (not supported)
#### `GetDefaultInputProfile` → `FName`
- **Description:** Returns the default input profile for this platform. Used by `SS_EnhancedInputManager` to select the correct `DA_InputMappingProfile`.
- **Returns:** "PC_KeyboardMouse", "Xbox", "PS5_DualSense", "PS4_DualShock", "Switch_JoyCon"
#### `IsConsole` → `bool`, `IsHandheld` → `bool`, `IsPS5` → `bool`, `IsXbox` → `bool`, `IsSwitch` → `bool`, `IsPC` → `bool`, `IsSteamDeck` → `bool`
- **Description:** Convenience boolean checks for common platform queries. All return from `PlatformCapabilities` struct.
#### `GetConsoleCertificationRequirements` → `TArray<FText>`
- **Description:** Returns a list of TRC (Technical Requirement Checklist) items for the current console platform. Empty on PC.
- **Flow:**
1. Switch on `CurrentPlatform`:
- `PS5` → return Sony TRC items (60 FPS perf mode, HDR support, controller speaker, activity cards, etc.)
- `PS4` → return Sony PS4 TRC items
- `Xbox_Series` → return Microsoft XR items (Quick Resume, Smart Delivery, VRR, etc.)
- `Xbox_One` → return Microsoft XR items
- `Switch` → return Nintendo guidelines (dynamic resolution, Handheld/Docked parity, etc.)
- `PC_*` → return empty (no console cert needed)
#### `GetStoreID` → `FString`
- **Description:** Returns the platform store app ID for DLC entitlement checks.
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnPlatformReady` | `EPlatformFamily Platform` | `Public` | Fired after platform detection completes — all other systems bind to this for deferred init |
| `OnPlatformCapabilitiesReady` | `SPlatformCapabilities Capabilities` | `Public` | Fired alongside OnPlatformReady with full capability info |
| `OnPlatformSDKReady` | `EPlatformSDK SDK` | `Public` | Fired when platform SDK is ready for API calls |
| `OnPlatformOverlayOpened` | `EPlatformOverlayType Type` | `Public` | Fired when platform overlay opens (game should pause) |
| `OnPlatformOverlayClosed` | — | `Public` | Fired when platform overlay closes (game resumes) |
| `OnCloudSaveCompleted` | `bool bSuccess`, `int32 SlotIndex` | `Public` | Fired when cloud save upload completes |
| `OnAchievementSubmitted` | `FString AchievementID`, `bool bSuccess` | `Public` | Fired after platform API achievement call |
---
## 6. Overridden Events
### Event: On Component Created (attached to GI_GameFramework via BeginPlay)
- **Description:** Auto-initializes at game start.
- **Flow:**
1. Call `Initialize()`
2. All dependent subsystems that bound to `OnPlatformReady` receive the event and complete their initialization
---
## 7. Blueprint Graph Logic Flow
```mermaid
flowchart TD
A[GI_GameFramework: BeginPlay] --> B[BPC_PlatformServiceAbstraction: Initialize]
B --> C{OverridePlatform?}
C -->|Yes| D[Use Override]
C -->|No| E[DetectPlatform]
E --> F[Switch on GetPlatformName]
F --> G[Set CurrentPlatform]
D --> G
G --> H[Resolve available SDK]
H --> I[Build SPlatformCapabilities]
I --> J[Register Platform Tag in DA_GameTagRegistry]
J --> K[Broadcast OnPlatformReady]
K --> L[ALL BOUND SYSTEMS RECEIVE]
L --> M[RenderPipelineManager: load DA_RPP for platform]
L --> N[EnhancedInputManager: load DA_InputMapping for platform]
L --> O[HapticsController: set EControllerPlatform from platform]
L --> P[AchievementSystem: route SDK via GetSDK]
L --> Q[SettingsSystem: load platform defaults]
```
---
## 8. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| `GI_GameFramework` | `Owner` | Hosts this component |
| `BPC_PlatformServiceAbstraction` | `Dispatcher` → ALL systems | `OnPlatformReady(Platform)` — every system's deferred init trigger |
| `BPC_RenderPipelineManager` (149) | `Function Call` | `GetPlatformFamily()` → selects correct `DA_RenderPipelineProfile` |
| `SS_EnhancedInputManager` (128) | `Function Call` | `GetDefaultInputProfile()` → selects correct `DA_InputMappingProfile` |
| `BPC_HapticsController` (148) | `Function Call` | `GetPlatformFamily()` → maps to `EControllerPlatform` |
| `SS_AchievementSystem` (103) | `Function Call` | `SubmitAchievementToPlatform(ID)` → routes to Steam/PSN/Xbox/Nintendo |
| `SS_SaveManager` (35) | `Function Call` | `SyncCloudSave(Slot, Data)` → routes to platform cloud API |
| `SS_SettingsSystem` (105) | `Function Call` | `GetPlatformFamily()` → sets platform-appropriate defaults |
| `SS_UIManager` (44) | `Dispatcher` | `OnPlatformOverlayOpened/Closed` → pause/resume game |
| `DA_GameTagRegistry` (01) | `Function Call` | Register `Framework.Platform.{Name}` tag |
---
## 9. Platform Enum Mapping (Deprecation of old enums)
The old scattered platform enums are mapped to `EPlatformFamily` as follows:
| Deprecated Enum | Old Value | EPlatformFamily |
|----------------|-----------|-----------------|
| `E_InputPlatform::PC_KeyboardMouse` | 0 | `PC_Win64` (or `PC_Linux`, `Mac`) |
| `E_InputPlatform::Xbox` | 1 | `Xbox_Series` or `Xbox_One` (detected) |
| `E_InputPlatform::PS5_DualSense` | 2 | `PS5` or `PS5_Pro` |
| `EControllerPlatform::PC_Generic` | 1 | `PC_Win64` |
| `EControllerPlatform::Xbox` | 2 | `Xbox_Series` or `Xbox_One` |
| `EControllerPlatform::PS5_DualSense` | 3 | `PS5` |
| `EControllerPlatform::PS4_DualShock` | 4 | `PS4` |
All systems that previously used their own platform enum now call:
```
BPC_PlatformServiceAbstraction → GetPlatformFamily() → map to local enum internally
```
---
## 10. Validation / Testing Checklist
- [ ] `DetectPlatform` correctly identifies all 12 platform families
- [ ] `OverridePlatform = PS4` → all systems use PS4 profiles (baked lighting, DualShock, PSN SDK)
- [ ] `OverridePlatform = Switch` → all systems use Switch profiles (baked, NIS, Joy-Con, Nintendo SDK)
- [ ] `OverridePlatform = PS5_Pro` → RenderPipelineManager enables PSSR upscaler
- [ ] `GetDefaultInputProfile` returns correct input profile for each platform
- [ ] `SubmitAchievementToPlatform` on Steam build successfully calls Steam API
- [ ] `SubmitAchievementToPlatform` in Editor returns true (no-op stub, no crash)
- [ ] `SyncCloudSave` routes to correct platform cloud API
- [ ] `ShowPlatformOverlay(Achievements)` opens correct platform overlay
- [ ] `OnPlatformReady` fires before any dependent system queries `GetPlatformFamily()`
- [ ] Changing `OverridePlatform` at runtime (dev cheat) cascades to all bound systems
- [ ] Edge case: No SDK available (editor, generic build) → all SDK calls return no-op stubs
- [ ] Edge case: Platform SDK initialization fails → `CurrentSDK = None`, systems fallback gracefully
- [ ] Edge case: Steam Deck detection in Desktop Mode → reported as `PC_Linux`, not `SteamDeck`
- [ ] Edge case: Xbox Series S (Lockhart) vs Series X (Anaconda) → different capabilities for GPU
---
## 11. Manual Implementation Guide
### 11.1 Class Setup
1. Create Blueprint Class: parent `ActorComponent`, name `BPC_PlatformServiceAbstraction`
2. Path: `Content/Framework/Core/`
3. Attach to `GI_GameFramework` (or BP child of it)
### 11.2 Key UE5 Nodes
| Node | Where to Find | Used For |
|------|---------------|----------|
| `Get Platform Name` | Right-click → "Get Platform Name" | Primary detection API |
| `Switch on String` | Right-click → "Switch" | Platform name branching |
| `Is Steam Deck` | Custom node / check for "SteamDeck" in device name | Steam Deck detection |
| `Is In Editor` | Right-click → "Is In Editor" | SDK substitution in editor |
| `Switch on EPlatformFamily` | Right-click → "Switch" | Platform-specific logic |
### 11.3 Node-by-Node: DetectPlatform
```
[Function: DetectPlatform]
Step 1: Get Platform Name → Store as PlatformStr
Step 2: Switch on String (PlatformStr):
"PS5" →
Step 2a: Get device model info → if "Pro" → PS5_Pro, else PS5
"PS4" → PS4
"XboxOne" → Xbox_One
"XSX" → Xbox_Series (check if "Lockhart" variant for Series S)
"XboxAnaconda" → Xbox_Series
"Switch" → Switch (check model revision for Switch_2)
"Win64" →
Step 2b: Check if running on Steam Deck → if yes → SteamDeck
Step 2c: Else → PC_Win64
"Linux" → PC_Linux
"Mac" → Mac
Default → PC_Win64 (fallback)
Step 3: Store result in CurrentPlatform
Step 4: Return CurrentPlatform
```
### 11.4 Networking
- **Platform is per-client.** Each client may be on a different platform (PC host + PS5 client).
- `BPC_PlatformServiceAbstraction` runs on ALL instances (server + each client).
- Platform SDK calls are local-to-client.
- For listen server: host's platform SDK is used for host's achievements; clients use their own.
### 11.5 Multiplayer Note
- In a multiplayer session, each player's platform is detected independently
- Cross-platform play: a PC host may have Steam SDK active while a PS5 client has PSN SDK
- The Game Instance's PlatformServiceAbstraction only represents the LOCAL player's platform
- For dedicated servers: `CurrentPlatform = PC_Linux` or `PC_Win64`, `CurrentSDK = None` (no achievements on dedicated server)
---
## 12. Blueprint Build Checklist
- [ ] Create Blueprint class: `BPC_PlatformServiceAbstraction` (parent: `ActorComponent`)
- [ ] Add enums: `EPlatformFamily` (12 values), `EPlatformSDK` (6 values), `EPlatformOverlayType` (5 values)
- [ ] Add struct: `SPlatformCapabilities`
- [ ] Add all variables from Section 3
- [ ] Build `Initialize``DetectPlatform``BuildCapabilities` → broadcast chain
- [ ] Implement `DetectPlatform` with Switch on GetPlatformName
- [ ] Implement `SubmitAchievementToPlatform` with Switch on CurrentSDK
- [ ] Implement `SyncCloudSave`, `ShowPlatformOverlay`
- [ ] Implement all boolean convenience functions (`IsConsole`, `IsPS5`, etc.)
- [ ] Create all 7 event dispatchers
- [ ] Wire OnPlatformReady as deferred init trigger for all dependent subsystems
- [ ] Test: OverridePlatform = PS5 → OnPlatformReady fires with PS5
- [ ] Test: Editor mode → SDK calls return no-op stubs
- [ ] Test: Steam build → SubmitAchievement calls Steam API
- [ ] Test: Platform hot-swap (dev cheat) cascades to all bound systems
---
*Blueprint Spec: Platform Service Abstraction. Conforms to TEMPLATE.md v2.0 — part of the UE5 Modular Game Framework, CORE layer.*

View File

@@ -0,0 +1,71 @@
# BP_CoreGameMode — Asset Implementation
> **UE5 Asset:** `/Game/Framework/Core/BP_CoreGameMode`
> **Parent Class:** `GM_CoreGameMode` (C++) or `GameModeBase`
> **Asset Type:** Blueprint Class (GameMode)
---
## Create This Asset
### Step 1: Create Blueprint
1. Content Browser → `Content/Framework/Core/`
2. Right-click → **Blueprint Class**
3. Parent: `GM_CoreGameMode` (C++) OR `GameModeBase`
4. Name: `BP_CoreGameMode`
### Step 2: Set as Default GameMode
**Project Settings → Maps & Modes → Default GameMode** → `BP_CoreGameMode`
### Step 3: Configure Class Defaults
| Variable | Type | Value |
|----------|------|-------|
| `GameState Class` | `AGS_CoreGameState` | `BP_CoreGameState` |
| `Player Controller Class` | `APlayerController` | Your `BP_CorePlayerController` |
| `Default Pawn Class` | `APawn` | Your GASP player pawn |
| `HUD Class` | `AHUD` | `WBP_HUDController` |
---
## What to Wire
### Function: TransitionToChapter
```
Input: ChapterTag (GameplayTag)
[BlueprintCallable]
├─ Branch: CurrentGamePhase == Loading?
│ True → Print Warning: "Already loading" → Return
├─ Set CurrentChapterTag = ChapterTag
├─ Switch HasAuthority → True:
│ ├─ Get GameInstance → Cast to GI_GameFramework
│ ├─ SetGamePhase(Loading)
│ ├─ Get GameState → Cast to GS_CoreGameState → SetChapter(ChapterTag)
│ ├─ Open Level (by ChapterTag mapping — use Data Asset lookup)
│ └─ On level loaded → SetGamePhase(InGame)
└─ Call OnChapterTransition(ChapterTag)
```
### Function: HandlePlayerDead
```
Input: DeadController (AController)
[BlueprintCallable]
├─ IsValid(DeadController)? → False: Return
├─ Set bPauseAllowed = false
├─ Get GameInstance → Cast to GI_GameFramework → SetGamePhase(DeathLoop)
└─ TODO: Route to respawn or AltDeathSpace
```
### Event Dispatchers to Create
| Dispatcher | Parameters |
|------------|-----------|
| `OnChapterTransition` | `NewChapter` (GameplayTag) |
| `OnGameOverTriggered` | `EndingTag` (GameplayTag) |
---
## Test It
- [ ] PIE: check output log for "GM_CoreGameMode::InitGame"
- [ ] Check that player spawns with correct pawn
- [ ] Call `TransitionToChapter` → phase changes to Loading → level loads

View File

@@ -0,0 +1,88 @@
# BP_CoreGameState — Asset Implementation
> **UE5 Asset:** `/Game/Framework/Core/BP_CoreGameState`
> **Parent Class:** `GS_CoreGameState` (C++) or `GameStateBase`
> **Asset Type:** Blueprint Class (GameState)
---
## Create This Asset
### Step 1: Create Blueprint
1. Content Browser → `Content/Framework/Core/`
2. Right-click → **Blueprint Class**
3. Parent: `GS_CoreGameState` (C++) OR `GameStateBase`
4. Name: `BP_CoreGameState`
### Step 2: Assign in GameMode
Open `BP_CoreGameMode` → Class Defaults → `GameState Class``BP_CoreGameState`
---
## What to Wire
### Variables (if BP-only GameStateBase)
| Variable | Type | Replication | Default |
|----------|------|-------------|---------|
| `ElapsedPlayTime` | `Float` | Replicated | `0.0` |
| `ActiveChapterTag` | `GameplayTag` | Replicated | Empty |
| `ActiveNarrativePhase` | `GameplayTag` | Replicated | Empty |
| `bEncounterActive` | `Boolean` | Replicated | `false` |
| `ActiveObjectiveTags` | `Array<GameplayTag>` | Replicated | Empty |
### Override: BeginPlay
```
[Event BeginPlay]
├─ Parent: BeginPlay
├─ Get GameInstance → Cast to GI_GameFramework → Store as CachedFramework
└─ Enable Tick
```
### Override: Tick
```
[Event Tick]
├─ IsValid(CachedFramework)? AND CurrentGamePhase == InGame?
│ True → ElapsedPlayTime += DeltaSeconds
│ ├─ TimeUpdateAccumulator += DeltaSeconds
│ └─ Accumulator >= 1.0?
│ ├─ Reset accumulator
│ └─ Call OnElapsedPlayTimeUpdated(ElapsedPlayTime)
```
### Function: SetChapter
```
Input: ChapterTag (GameplayTag)
[Server-only]
├─ If ChapterTag == ActiveChapterTag → Return
├─ Set ActiveChapterTag = ChapterTag
└─ Call OnChapterChanged(ChapterTag)
```
### Function: AddObjective / RemoveObjective
```
AddObjective(ObjectiveTag):
├─ If already in array → Return
├─ Add to ActiveObjectiveTags
└─ Call OnObjectiveTagsChanged
RemoveObjective(ObjectiveTag):
├─ Remove from array
└─ Call OnObjectiveTagsChanged
```
### Event Dispatchers to Create
| Dispatcher | Parameters |
|------------|-----------|
| `OnElapsedPlayTimeUpdated` | `ElapsedSeconds` (Float) |
| `OnChapterChanged` | `NewChapter` (GameplayTag) |
| `OnNarrativePhaseChanged` | `NewPhase` (GameplayTag) |
| `OnEncounterActiveStateChanged` | `bActive` (Boolean) |
| `OnObjectiveTagsChanged` | (none) |
---
## Test It
- [ ] PIE: check that GS_CoreGameState spawns
- [ ] Call SetChapter → OnChapterChanged fires → HUD updates
- [ ] Call AddObjective → OnObjectiveTagsChanged fires → objective list refreshes

View File

@@ -0,0 +1,78 @@
# BP_GameFramework — Asset Implementation
> **UE5 Asset:** `/Game/Framework/Core/BP_GameFramework`
> **Parent Class:** `GI_GameFramework` (C++) or `GameInstance` (BP-only)
> **Asset Type:** Blueprint Class (GameInstance)
---
## Create This Asset
### Step 1: Create Blueprint
1. Content Browser → `Content/Framework/Core/`
2. Right-click → **Blueprint Class**
3. Parent: `GI_GameFramework` (C++) OR `GameInstance`
4. Name: `BP_GameFramework`
### Step 2: Set as Game Instance Class
**Project Settings → Maps & Modes → Game Instance Class** → `BP_GameFramework`
### Step 3: Configure Class Defaults
| Variable | Type | Value |
|----------|------|-------|
| `TagRegistry` | `DA_GameTagRegistry` | Assign `DA_GameTagRegistry` asset |
| `bValidateTagsOnInit` | `Boolean` | `true` |
| `bLogTagsOnInit` | `Boolean` | `true` (dev) / `false` (ship) |
---
## What to Wire
### Override: Event Init
```
[Event Init]
├─ Parent: Event Init
├─ Print String: "BP_GameFramework: Init started"
├─ Branch: bValidateTagsOnInit?
│ True → Call ValidateFrameworkTags (see below)
├─ IsValid(TagRegistry)? → Branch
│ False → Print Error → Call OnFrameworkInitFailed("TagRegistry not assigned")
│ True →
│ ├─ Set bFrameworkInitialized = true
│ ├─ Call OnFrameworkReady
│ └─ Print String: "Framework ready"
```
### Function: ValidateFrameworkTags
```
[BlueprintCallable, Private]
├─ IsValid(TagRegistry)? → Branch
│ False → Print Error → Return
├─ TagRegistry → GetAllRegisteredTags → AllTags
├─ Array Length(AllTags) → TagCount
├─ Branch: TagCount == 0?
│ True → Print Warning: "No Gameplay Tags registered!"
│ False → Print: "{TagCount} tags registered"
├─ Branch: bLogTagsOnInit?
│ True → TagRegistry → LogAllTags
└─ Return
```
### Function: IsFrameworkReady
```
[BlueprintPure, Public] → Boolean
└─ Return bFrameworkInitialized
```
### Event Dispatchers to Create
| Dispatcher | Parameters |
|------------|-----------|
| `OnFrameworkReady` | (none) |
| `OnFrameworkInitFailed` | `ErrorReason` (String) |
---
## Test It
- [ ] PIE: output log shows "Framework ready" and tag count
- [ ] Clear TagRegistry variable → PIE → `OnFrameworkInitFailed` fires
- [ ] Set `bLogTagsOnInit = true` → all tag names printed to log

View File

@@ -0,0 +1,68 @@
# DA_GameTagRegistry — Asset Implementation
> **UE5 Asset:** `/Game/Framework/Core/DA_GameTagRegistry`
> **Parent Class:** `DA_GameTagRegistry` (C++) or `PrimaryDataAsset` (BP-only)
> **Asset Type:** Data Asset
---
## Create This Asset
### Step 1: Create Data Asset
1. Content Browser → `Content/Framework/Core/`
2. Right-click → **Miscellaneous → Data Asset**
3. Class: `DA_GameTagRegistry` (if C++ compiled) OR `PrimaryDataAsset`
4. Name: `DA_GameTagRegistry`
### Step 2: Configure Class Defaults (if BP-only PrimaryDataAsset)
| Variable | Type | Value |
|----------|------|-------|
| `TagNamespace` | `Text` | `"Framework tag namespace documentation"` |
| `bIsFrameworkTag` | `Boolean` | `true` |
| `TagDataTables` | `Array<Data Table>` | Add all 11 DT_Tags_* tables |
### Step 3: Assign to GameInstance
Open `BP_GameFramework` → Class Defaults → `TagRegistry` → assign `DA_GameTagRegistry`
---
## What to Wire (BP-Only Version)
### Function: GetAllRegisteredTags
```
Inputs: none → Output: Array<GameplayTag>
[Pure Function Graph]
├─ Make Array<GameplayTag> → LocalTags
├─ ForEachLoop (TagDataTables)
│ ├─ Branch: IsValid(Array Element)?
│ │ True → Get Data Table Row Names
│ │ └─ ForEachLoop (RowNames)
│ │ └─ Get Data Table Row → Break GameplayTagTableRow → Tag
│ │ └─ ADD to LocalTags
└─ Return LocalTags
```
### Function: ValidateTag
```
Input: Tag (GameplayTag) → Output: Boolean
[Pure Function Graph]
└─ Is Gameplay Tag Valid(Tag) → Return
```
### Function: GetTagDisplayName
```
Input: Tag (GameplayTag) → Output: Text
[Pure Function Graph]
└─ Get Tag Display Name(Tag) → Return
```
---
## Test It
- [ ] Open `DA_GameTagRegistry` → verify `TagDataTables` has 11 entries
- [ ] PIE: output log shows `"N tags registered across 11 Data Tables"`
- [ ] Call `ValidateTag(Framework.Player.State.Alive)` → returns `true`
- [ ] Call `ValidateTag(Unregistered.Tag)` → returns `false`

View File

@@ -0,0 +1,5 @@
# SUPERSEDED — Tags split into 11 per-category Data Tables in docs/blueprints/01-core/data-tables/
# See: DT_Tags_Player.csv, DT_Tags_Interaction.csv, DT_Tags_Item.csv, DT_Tags_Narrative.csv,
# DT_Tags_AI.csv, DT_Tags_Save.csv, DT_Tags_Environment.csv, DT_Tags_Combat.csv,
# DT_Tags_State.csv, DT_Tags_Audio.csv, DT_Tags_Achievement.csv
# This file kept as migration reference only. Do not use in new implementations.
1 # SUPERSEDED — Tags split into 11 per-category Data Tables in docs/blueprints/01-core/data-tables/
2 # See: DT_Tags_Player.csv, DT_Tags_Interaction.csv, DT_Tags_Item.csv, DT_Tags_Narrative.csv,
3 # DT_Tags_AI.csv, DT_Tags_Save.csv, DT_Tags_Environment.csv, DT_Tags_Combat.csv,
4 # DT_Tags_State.csv, DT_Tags_Audio.csv, DT_Tags_Achievement.csv
5 # This file kept as migration reference only. Do not use in new implementations.

View File

@@ -0,0 +1,59 @@
# DT_Tags_* — Data Tables (11 assets)
> **UE5 Assets:** `Content/Framework/Core/DataTables/DT_Tags_{Category}`
> **Row Structure:** `GameplayTagTableRow`
> **Count:** 11 tables
---
## Create These Assets
### Step 1: Create All 11 Data Tables
For each category below, repeat this process:
1. Content Browser → `Content/Framework/Core/DataTables/`
2. Right-click → **Miscellaneous → Data Table**
3. Row Structure: `GameplayTagTableRow`
4. Name: `DT_Tags_{Category}`
5. Right-click the Data Table → **Import** → select the CSV file from `docs/blueprints/01-core/data-tables/DT_Tags_{Category}.csv`
### The 11 Tables
| # | Data Table | CSV Source | Namespaces |
|---|-----------|------------|------------|
| 1 | `DT_Tags_Player` | `DT_Tags_Player.csv` | Player.State, Stress, Posture, Movement, Camera, Body, Overlay, Vitals |
| 2 | `DT_Tags_Interaction` | `DT_Tags_Interaction.csv` | Interaction.Type, Context, Prompt, HidingSpot, Traversal, Door |
| 3 | `DT_Tags_Item` | `DT_Tags_Item.csv` | Item.Type, Slot, Rarity, Context |
| 4 | `DT_Tags_Narrative` | `DT_Tags_Narrative.csv` | Narrative.Flag, Phase, Choice, Ending, Trial, Cutscene, Lore, Objective |
| 5 | `DT_Tags_AI` | `DT_Tags_AI.csv` | AI.Alert, Archetype, Stimulus, Behavior, Memory |
| 6 | `DT_Tags_Save` | `DT_Tags_Save.csv` | Save, DeathSpace, Checkpoint, Respawn, RunHistory |
| 7 | `DT_Tags_Environment` | `DT_Tags_Environment.csv` | Environment.Atmosphere, Scare, Light, Pacing, Performance |
| 8 | `DT_Tags_Combat` | `DT_Tags_Combat.csv` | Combat.Damage, Weapon, Ammo, FireMode, HitReaction, Feedback, Shield |
| 9 | `DT_Tags_State` | `DT_Tags_State.csv` | State.Action, Overlay, Vital, Gating |
| 10 | `DT_Tags_Audio` | `DT_Tags_Audio.csv` | Audio.Bus, Room, Parameter, Surface |
| 11 | `DT_Tags_Achievement` | `DT_Tags_Achievement.csv` | Achievement |
### Step 2: Register All Tables with Engine
**⚠️ CRITICAL — Without this, NO tags are recognized!**
1. **Project Settings → GameplayTags → Gameplay Tag Table List**
2. Click `+` 11 times
3. Assign each `DT_Tags_*` Data Table to a slot
4. Restart editor (or recompile Blueprints) to refresh tag cache
### CSV Format
Each CSV has 3 columns:
```
Name,Tag,DevComment
Alive,"Framework.Player.State.Alive","Player is alive"
Dead,"Framework.Player.State.Dead","Player is dead"
...
```
---
## Test It
- [ ] Open `DA_GameTagRegistry` → call `GetAllRegisteredTags()` → returns ~334 tags
- [ ] Make Literal Gameplay Tag → type `Framework.Player.State.Alive` → recognized (not red)
- [ ] `Is Gameplay Tag Valid(Framework.Player.State.Alive)``true`
- [ ] `Is Gameplay Tag Valid(Fake.Tag)``false`

View File

@@ -0,0 +1,24 @@
Name,Tag,DevComment
Framework_AI_Alert_None,Framework.AI.Alert.None,No awareness of player
Framework_AI_Alert_Suspicious,Framework.AI.Alert.Suspicious,Heard or glimpsed something
Framework_AI_Alert_Alerted,Framework.AI.Alert.Alerted,Confirmed player presence
Framework_AI_Alert_Engaged,Framework.AI.Alert.Engaged,Actively fighting
Framework_AI_Alert_Searching,Framework.AI.Alert.Searching,Lost sight searching area
Framework_AI_Alert_Fleeing,Framework.AI.Alert.Fleeing,Retreating from combat
Framework_AI_Archetype_Patrol,Framework.AI.Archetype.Patrol,Standard patrol guard
Framework_AI_Archetype_Ambush,Framework.AI.Archetype.Ambush,Lurking ambush predator
Framework_AI_Archetype_Stalker,Framework.AI.Archetype.Stalker,Persistent slow stalker
Framework_AI_Archetype_Passive,Framework.AI.Archetype.Passive,Non-aggressive NPC
Framework_AI_Archetype_Berserker,Framework.AI.Archetype.Berserker,Aggressive rush-down enemy
Framework_AI_Archetype_Sniper,Framework.AI.Archetype.Sniper,Long-range stationary enemy
Framework_AI_Stimulus_Sight,Framework.AI.Stimulus.Sight,Visual perception trigger
Framework_AI_Stimulus_Hearing,Framework.AI.Stimulus.Hearing,Audio perception trigger
Framework_AI_Stimulus_Damage,Framework.AI.Stimulus.Damage,Damage perception trigger
Framework_AI_Stimulus_TeamAlert,Framework.AI.Stimulus.TeamAlert,Ally alert shared perception
Framework_AI_Behavior_Aggressive,Framework.AI.Behavior.Aggressive,Aggressive combat style
Framework_AI_Behavior_Defensive,Framework.AI.Behavior.Defensive,Defensive combat style
Framework_AI_Behavior_Cautious,Framework.AI.Behavior.Cautious,Cautious slow approach
Framework_AI_Behavior_Reckless,Framework.AI.Behavior.Reckless,Reckless rush style
Framework_AI_Memory_LastKnownLocation,Framework.AI.Memory.LastKnownLocation,Last known player position
Framework_AI_Memory_InvestigationPoint,Framework.AI.Memory.InvestigationPoint,Point of interest to investigate
Framework_AI_Memory_ThreatHistory,Framework.AI.Memory.ThreatHistory,Record of damage sources
1 Name Tag DevComment
2 Framework_AI_Alert_None Framework.AI.Alert.None No awareness of player
3 Framework_AI_Alert_Suspicious Framework.AI.Alert.Suspicious Heard or glimpsed something
4 Framework_AI_Alert_Alerted Framework.AI.Alert.Alerted Confirmed player presence
5 Framework_AI_Alert_Engaged Framework.AI.Alert.Engaged Actively fighting
6 Framework_AI_Alert_Searching Framework.AI.Alert.Searching Lost sight searching area
7 Framework_AI_Alert_Fleeing Framework.AI.Alert.Fleeing Retreating from combat
8 Framework_AI_Archetype_Patrol Framework.AI.Archetype.Patrol Standard patrol guard
9 Framework_AI_Archetype_Ambush Framework.AI.Archetype.Ambush Lurking ambush predator
10 Framework_AI_Archetype_Stalker Framework.AI.Archetype.Stalker Persistent slow stalker
11 Framework_AI_Archetype_Passive Framework.AI.Archetype.Passive Non-aggressive NPC
12 Framework_AI_Archetype_Berserker Framework.AI.Archetype.Berserker Aggressive rush-down enemy
13 Framework_AI_Archetype_Sniper Framework.AI.Archetype.Sniper Long-range stationary enemy
14 Framework_AI_Stimulus_Sight Framework.AI.Stimulus.Sight Visual perception trigger
15 Framework_AI_Stimulus_Hearing Framework.AI.Stimulus.Hearing Audio perception trigger
16 Framework_AI_Stimulus_Damage Framework.AI.Stimulus.Damage Damage perception trigger
17 Framework_AI_Stimulus_TeamAlert Framework.AI.Stimulus.TeamAlert Ally alert shared perception
18 Framework_AI_Behavior_Aggressive Framework.AI.Behavior.Aggressive Aggressive combat style
19 Framework_AI_Behavior_Defensive Framework.AI.Behavior.Defensive Defensive combat style
20 Framework_AI_Behavior_Cautious Framework.AI.Behavior.Cautious Cautious slow approach
21 Framework_AI_Behavior_Reckless Framework.AI.Behavior.Reckless Reckless rush style
22 Framework_AI_Memory_LastKnownLocation Framework.AI.Memory.LastKnownLocation Last known player position
23 Framework_AI_Memory_InvestigationPoint Framework.AI.Memory.InvestigationPoint Point of interest to investigate
24 Framework_AI_Memory_ThreatHistory Framework.AI.Memory.ThreatHistory Record of damage sources

View File

@@ -0,0 +1,22 @@
Name,Tag,DevComment
Game_Achievement,Game.Achievement,Root achievement namespace
Game_Achievement_FirstBlood,Game.Achievement.FirstBlood,First enemy killed
Game_Achievement_Survivor,Game.Achievement.Survivor,Survived first chapter without dying
Game_Achievement_Pacifist,Game.Achievement.Pacifist,Completed a chapter without killing any enemy
Game_Achievement_Ghost,Game.Achievement.Ghost,Never detected by enemies in a chapter
Game_Achievement_Collector,Game.Achievement.Collector,Collected all collectibles
Game_Achievement_LoreMaster,Game.Achievement.LoreMaster,Discovered all lore entries
Game_Achievement_SpeedRunner,Game.Achievement.SpeedRunner,Completed game in under 3 hours
Game_Achievement_TrueEnding,Game.Achievement.TrueEnding,Unlocked the true ending
Game_Achievement_BadEnding,Game.Achievement.BadEnding,Witnessed the bad ending
Game_Achievement_WeaponsMaster,Game.Achievement.WeaponsMaster,Used every weapon type
Game_Achievement_Crafter,Game.Achievement.Crafter,Crafted 10 items
Game_Achievement_Untouchable,Game.Achievement.Untouchable,Completed a chapter without taking damage
Game_Achievement_Hoarder,Game.Achievement.Hoarder,Carried 100 items at once
Game_Achievement_Completionist,Game.Achievement.Completionist,Completed all side objectives
Game_Achievement_FirstDeath,Game.Achievement.FirstDeath,Died for the first time
Game_Achievement_TenDeaths,Game.Achievement.TenDeaths,Died 10 times
Game_Achievement_Chapter1Complete,Game.Achievement.Chapter1Complete,Completed Chapter 1
Game_Achievement_Chapter2Complete,Game.Achievement.Chapter2Complete,Completed Chapter 2
Game_Achievement_Chapter3Complete,Game.Achievement.Chapter3Complete,Completed Chapter 3
Game_Achievement_AllEndings,Game.Achievement.AllEndings,Unlocked every ending
1 Name Tag DevComment
2 Game_Achievement Game.Achievement Root achievement namespace
3 Game_Achievement_FirstBlood Game.Achievement.FirstBlood First enemy killed
4 Game_Achievement_Survivor Game.Achievement.Survivor Survived first chapter without dying
5 Game_Achievement_Pacifist Game.Achievement.Pacifist Completed a chapter without killing any enemy
6 Game_Achievement_Ghost Game.Achievement.Ghost Never detected by enemies in a chapter
7 Game_Achievement_Collector Game.Achievement.Collector Collected all collectibles
8 Game_Achievement_LoreMaster Game.Achievement.LoreMaster Discovered all lore entries
9 Game_Achievement_SpeedRunner Game.Achievement.SpeedRunner Completed game in under 3 hours
10 Game_Achievement_TrueEnding Game.Achievement.TrueEnding Unlocked the true ending
11 Game_Achievement_BadEnding Game.Achievement.BadEnding Witnessed the bad ending
12 Game_Achievement_WeaponsMaster Game.Achievement.WeaponsMaster Used every weapon type
13 Game_Achievement_Crafter Game.Achievement.Crafter Crafted 10 items
14 Game_Achievement_Untouchable Game.Achievement.Untouchable Completed a chapter without taking damage
15 Game_Achievement_Hoarder Game.Achievement.Hoarder Carried 100 items at once
16 Game_Achievement_Completionist Game.Achievement.Completionist Completed all side objectives
17 Game_Achievement_FirstDeath Game.Achievement.FirstDeath Died for the first time
18 Game_Achievement_TenDeaths Game.Achievement.TenDeaths Died 10 times
19 Game_Achievement_Chapter1Complete Game.Achievement.Chapter1Complete Completed Chapter 1
20 Game_Achievement_Chapter2Complete Game.Achievement.Chapter2Complete Completed Chapter 2
21 Game_Achievement_Chapter3Complete Game.Achievement.Chapter3Complete Completed Chapter 3
22 Game_Achievement_AllEndings Game.Achievement.AllEndings Unlocked every ending

View File

@@ -0,0 +1,29 @@
Name,Tag,DevComment
Framework_Audio_Bus_Master,Framework.Audio.Bus.Master,Master audio bus
Framework_Audio_Bus_SFX,Framework.Audio.Bus.SFX,Sound effects bus
Framework_Audio_Bus_Ambient,Framework.Audio.Bus.Ambient,Ambient audio bus
Framework_Audio_Bus_Music,Framework.Audio.Bus.Music,Music bus
Framework_Audio_Bus_Dialogue,Framework.Audio.Bus.Dialogue,Dialogue/VO bus
Framework_Audio_Room_Small,Framework.Audio.Room.Small,Small room acoustics
Framework_Audio_Room_Large,Framework.Audio.Room.Large,Large room acoustics
Framework_Audio_Room_Outdoor,Framework.Audio.Room.Outdoor,Outdoor acoustics
Framework_Audio_Room_Cave,Framework.Audio.Room.Cave,Cave acoustics
Framework_Audio_Room_Hallway,Framework.Audio.Room.Hallway,Hallway acoustics
Framework_Audio_Room_Bathroom,Framework.Audio.Room.Bathroom,Bathroom acoustics
Framework_Audio_Room_Cathedral,Framework.Audio.Room.Cathedral,Cathedral acoustics
Framework_Audio_Room_Anchoric,Framework.Audio.Room.Anchoric,Anechoic/dead acoustics
Framework_Audio_Parameter_HeartRate,Framework.Audio.Parameter.HeartRate,Heart rate audio parameter
Framework_Audio_Parameter_Stress,Framework.Audio.Parameter.Stress,Stress audio parameter
Framework_Audio_Parameter_Fear,Framework.Audio.Parameter.Fear,Fear audio parameter
Framework_Audio_Parameter_MusicIntensity,Framework.Audio.Parameter.MusicIntensity,Music intensity parameter
Framework_Audio_Parameter_Tension,Framework.Audio.Parameter.Tension,Tension parameter
Framework_Audio_Surface_Concrete,Framework.Audio.Surface.Concrete,Concrete footstep
Framework_Audio_Surface_Wood,Framework.Audio.Surface.Wood,Wood footstep
Framework_Audio_Surface_Metal,Framework.Audio.Surface.Metal,Metal footstep
Framework_Audio_Surface_Water,Framework.Audio.Surface.Water,Water footstep
Framework_Audio_Surface_Carpet,Framework.Audio.Surface.Carpet,Carpet footstep
Framework_Audio_Surface_Gravel,Framework.Audio.Surface.Gravel,Gravel footstep
Framework_Audio_Surface_Glass,Framework.Audio.Surface.Glass,Glass footstep
Framework_Audio_Surface_Dirt,Framework.Audio.Surface.Dirt,Dirt footstep
Framework_Audio_Surface_Grass,Framework.Audio.Surface.Grass,Grass footstep
Framework_Audio_Surface_Tile,Framework.Audio.Surface.Tile,Tile footstep
1 Name Tag DevComment
2 Framework_Audio_Bus_Master Framework.Audio.Bus.Master Master audio bus
3 Framework_Audio_Bus_SFX Framework.Audio.Bus.SFX Sound effects bus
4 Framework_Audio_Bus_Ambient Framework.Audio.Bus.Ambient Ambient audio bus
5 Framework_Audio_Bus_Music Framework.Audio.Bus.Music Music bus
6 Framework_Audio_Bus_Dialogue Framework.Audio.Bus.Dialogue Dialogue/VO bus
7 Framework_Audio_Room_Small Framework.Audio.Room.Small Small room acoustics
8 Framework_Audio_Room_Large Framework.Audio.Room.Large Large room acoustics
9 Framework_Audio_Room_Outdoor Framework.Audio.Room.Outdoor Outdoor acoustics
10 Framework_Audio_Room_Cave Framework.Audio.Room.Cave Cave acoustics
11 Framework_Audio_Room_Hallway Framework.Audio.Room.Hallway Hallway acoustics
12 Framework_Audio_Room_Bathroom Framework.Audio.Room.Bathroom Bathroom acoustics
13 Framework_Audio_Room_Cathedral Framework.Audio.Room.Cathedral Cathedral acoustics
14 Framework_Audio_Room_Anchoric Framework.Audio.Room.Anchoric Anechoic/dead acoustics
15 Framework_Audio_Parameter_HeartRate Framework.Audio.Parameter.HeartRate Heart rate audio parameter
16 Framework_Audio_Parameter_Stress Framework.Audio.Parameter.Stress Stress audio parameter
17 Framework_Audio_Parameter_Fear Framework.Audio.Parameter.Fear Fear audio parameter
18 Framework_Audio_Parameter_MusicIntensity Framework.Audio.Parameter.MusicIntensity Music intensity parameter
19 Framework_Audio_Parameter_Tension Framework.Audio.Parameter.Tension Tension parameter
20 Framework_Audio_Surface_Concrete Framework.Audio.Surface.Concrete Concrete footstep
21 Framework_Audio_Surface_Wood Framework.Audio.Surface.Wood Wood footstep
22 Framework_Audio_Surface_Metal Framework.Audio.Surface.Metal Metal footstep
23 Framework_Audio_Surface_Water Framework.Audio.Surface.Water Water footstep
24 Framework_Audio_Surface_Carpet Framework.Audio.Surface.Carpet Carpet footstep
25 Framework_Audio_Surface_Gravel Framework.Audio.Surface.Gravel Gravel footstep
26 Framework_Audio_Surface_Glass Framework.Audio.Surface.Glass Glass footstep
27 Framework_Audio_Surface_Dirt Framework.Audio.Surface.Dirt Dirt footstep
28 Framework_Audio_Surface_Grass Framework.Audio.Surface.Grass Grass footstep
29 Framework_Audio_Surface_Tile Framework.Audio.Surface.Tile Tile footstep

View File

@@ -0,0 +1,28 @@
Name,Tag,DevComment
Framework_Combat_Damage_Physical,Framework.Combat.Damage.Physical,Physical/blunt damage type
Framework_Combat_Damage_Arcane,Framework.Combat.Damage.Arcane,Arcane/magic damage type
Framework_Combat_Damage_Fire,Framework.Combat.Damage.Fire,Fire damage type
Framework_Combat_Damage_Poison,Framework.Combat.Damage.Poison,Poison damage type
Framework_Combat_Damage_Fear,Framework.Combat.Damage.Fear,Fear/psychological damage type
Framework_Combat_Damage_Environmental,Framework.Combat.Damage.Environmental,Environmental hazard damage
Framework_Combat_Damage_True,Framework.Combat.Damage.True,True damage (bypasses all resistance)
Framework_Combat_Weapon_Firearm,Framework.Combat.Weapon.Firearm,Firearm weapon type
Framework_Combat_Weapon_Melee,Framework.Combat.Weapon.Melee,Melee weapon type
Framework_Combat_Weapon_Throwable,Framework.Combat.Weapon.Throwable,Throwable weapon
Framework_Combat_Ammo_Pistol,Framework.Combat.Ammo.Pistol,Pistol ammunition
Framework_Combat_Ammo_Rifle,Framework.Combat.Ammo.Rifle,Rifle ammunition
Framework_Combat_Ammo_Shotgun,Framework.Combat.Ammo.Shotgun,Shotgun shells
Framework_Combat_Ammo_Energy,Framework.Combat.Ammo.Energy,Energy cells
Framework_Combat_FireMode_SemiAuto,Framework.Combat.FireMode.SemiAuto,One shot per trigger press
Framework_Combat_FireMode_FullAuto,Framework.Combat.FireMode.FullAuto,Continuous fire while held
Framework_Combat_FireMode_Burst,Framework.Combat.FireMode.Burst,Fixed burst count
Framework_Combat_FireMode_Charge,Framework.Combat.FireMode.Charge,Hold to charge release
Framework_Combat_HitReaction_Flinch,Framework.Combat.HitReaction.Flinch,Minor directional flinch
Framework_Combat_HitReaction_Stagger,Framework.Combat.HitReaction.Stagger,Heavy stagger
Framework_Combat_HitReaction_Knockdown,Framework.Combat.HitReaction.Knockdown,Knocked to ground
Framework_Combat_HitReaction_Ragdoll,Framework.Combat.HitReaction.Ragdoll,Full ragdoll on death
Framework_Combat_Feedback_HitMarker,Framework.Combat.Feedback.HitMarker,Hit marker UI element
Framework_Combat_Feedback_KillConfirm,Framework.Combat.Feedback.KillConfirm,Kill confirm UI element
Framework_Combat_Shield_Active,Framework.Combat.Shield.Active,Shield is blocking
Framework_Combat_Shield_Broken,Framework.Combat.Shield.Broken,Shield durability zero
Framework_Combat_Shield_Recharging,Framework.Combat.Shield.Recharging,Shield recharging
1 Name Tag DevComment
2 Framework_Combat_Damage_Physical Framework.Combat.Damage.Physical Physical/blunt damage type
3 Framework_Combat_Damage_Arcane Framework.Combat.Damage.Arcane Arcane/magic damage type
4 Framework_Combat_Damage_Fire Framework.Combat.Damage.Fire Fire damage type
5 Framework_Combat_Damage_Poison Framework.Combat.Damage.Poison Poison damage type
6 Framework_Combat_Damage_Fear Framework.Combat.Damage.Fear Fear/psychological damage type
7 Framework_Combat_Damage_Environmental Framework.Combat.Damage.Environmental Environmental hazard damage
8 Framework_Combat_Damage_True Framework.Combat.Damage.True True damage (bypasses all resistance)
9 Framework_Combat_Weapon_Firearm Framework.Combat.Weapon.Firearm Firearm weapon type
10 Framework_Combat_Weapon_Melee Framework.Combat.Weapon.Melee Melee weapon type
11 Framework_Combat_Weapon_Throwable Framework.Combat.Weapon.Throwable Throwable weapon
12 Framework_Combat_Ammo_Pistol Framework.Combat.Ammo.Pistol Pistol ammunition
13 Framework_Combat_Ammo_Rifle Framework.Combat.Ammo.Rifle Rifle ammunition
14 Framework_Combat_Ammo_Shotgun Framework.Combat.Ammo.Shotgun Shotgun shells
15 Framework_Combat_Ammo_Energy Framework.Combat.Ammo.Energy Energy cells
16 Framework_Combat_FireMode_SemiAuto Framework.Combat.FireMode.SemiAuto One shot per trigger press
17 Framework_Combat_FireMode_FullAuto Framework.Combat.FireMode.FullAuto Continuous fire while held
18 Framework_Combat_FireMode_Burst Framework.Combat.FireMode.Burst Fixed burst count
19 Framework_Combat_FireMode_Charge Framework.Combat.FireMode.Charge Hold to charge release
20 Framework_Combat_HitReaction_Flinch Framework.Combat.HitReaction.Flinch Minor directional flinch
21 Framework_Combat_HitReaction_Stagger Framework.Combat.HitReaction.Stagger Heavy stagger
22 Framework_Combat_HitReaction_Knockdown Framework.Combat.HitReaction.Knockdown Knocked to ground
23 Framework_Combat_HitReaction_Ragdoll Framework.Combat.HitReaction.Ragdoll Full ragdoll on death
24 Framework_Combat_Feedback_HitMarker Framework.Combat.Feedback.HitMarker Hit marker UI element
25 Framework_Combat_Feedback_KillConfirm Framework.Combat.Feedback.KillConfirm Kill confirm UI element
26 Framework_Combat_Shield_Active Framework.Combat.Shield.Active Shield is blocking
27 Framework_Combat_Shield_Broken Framework.Combat.Shield.Broken Shield durability zero
28 Framework_Combat_Shield_Recharging Framework.Combat.Shield.Recharging Shield recharging

View File

@@ -0,0 +1,35 @@
Name,Tag,DevComment
Game_Achievement,Game.Achievement,Root achievement namespace
Game_Achievement_FirstBlood,Game.Achievement.FirstBlood,First kill
Game_Achievement_Survivor,Game.Achievement.Survivor,Survived first chapter
Game_Achievement_Pacifist,Game.Achievement.Pacifist,Completed chapter without killing
Game_Achievement_Ghost,Game.Achievement.Ghost,Never detected in a chapter
Game_Achievement_Collector,Game.Achievement.Collector,Collected all items
Game_Achievement_LoreMaster,Game.Achievement.LoreMaster,Found all lore entries
Game_Achievement_SpeedRunner,Game.Achievement.SpeedRunner,Completed game under time limit
Game_Achievement_TrueEnding,Game.Achievement.TrueEnding,Unlocked true ending
Game_Environment_Atmosphere_Safe,Game.Environment.Atmosphere.Safe,Safe zone atmosphere
Game_Environment_Atmosphere_Neutral,Game.Environment.Atmosphere.Neutral,Neutral atmosphere
Game_Environment_Atmosphere_Tense,Game.Environment.Atmosphere.Tense,Tension building
Game_Environment_Atmosphere_Danger,Game.Environment.Atmosphere.Danger,Active danger
Game_Environment_Atmosphere_Terror,Game.Environment.Atmosphere.Terror,Peak terror
Game_Environment_Atmosphere_Eerie,Game.Environment.Atmosphere.Eerie,Eerie ambient
Game_Environment_Scare,Game.Environment.Scare,Scare event namespace
Game_Environment_Scare_MirrorJump,Game.Environment.Scare.MirrorJump,Mirror jump scare
Game_Environment_Scare_CeilingDrop,Game.Environment.Scare.CeilingDrop,Body drops from ceiling
Game_Environment_Scare_DoorSlam,Game.Environment.Scare.DoorSlam,Door slams behind player
Game_Environment_Scare_Whisper,Game.Environment.Scare.Whisper,Ghost whisper audio
Game_Environment_Scare_LightFlicker,Game.Environment.Scare.LightFlicker,Lights flicker then blackout
Game_Environment_Light_Flicker,Game.Environment.Light.Flicker,Rapid flicker event
Game_Environment_Light_Strobe,Game.Environment.Light.Strobe,Strobe light event
Game_Environment_Light_Blackout,Game.Environment.Light.Blackout,Complete blackout event
Game_Environment_Light_ColorShift,Game.Environment.Light.ColorShift,Color temperature shift
Game_Environment_Pacing_Calm,Game.Environment.Pacing.Calm,Calm pacing band
Game_Environment_Pacing_Exploration,Game.Environment.Pacing.Exploration,Exploration pacing
Game_Environment_Pacing_Tension,Game.Environment.Pacing.Tension,Tension building
Game_Environment_Pacing_Combat,Game.Environment.Pacing.Combat,Combat encounter
Game_Environment_Pacing_Climax,Game.Environment.Pacing.Climax,Story climax
Game_Environment_Pacing_Resolution,Game.Environment.Pacing.Resolution,Post-climax resolution
Game_Environment_Performance_High,Game.Environment.Performance.High,High quality LOD target
Game_Environment_Performance_Medium,Game.Environment.Performance.Medium,Medium quality
Game_Environment_Performance_Low,Game.Environment.Performance.Low,Low quality LOD target
1 Name Tag DevComment
2 Game_Achievement Game.Achievement Root achievement namespace
3 Game_Achievement_FirstBlood Game.Achievement.FirstBlood First kill
4 Game_Achievement_Survivor Game.Achievement.Survivor Survived first chapter
5 Game_Achievement_Pacifist Game.Achievement.Pacifist Completed chapter without killing
6 Game_Achievement_Ghost Game.Achievement.Ghost Never detected in a chapter
7 Game_Achievement_Collector Game.Achievement.Collector Collected all items
8 Game_Achievement_LoreMaster Game.Achievement.LoreMaster Found all lore entries
9 Game_Achievement_SpeedRunner Game.Achievement.SpeedRunner Completed game under time limit
10 Game_Achievement_TrueEnding Game.Achievement.TrueEnding Unlocked true ending
11 Game_Environment_Atmosphere_Safe Game.Environment.Atmosphere.Safe Safe zone atmosphere
12 Game_Environment_Atmosphere_Neutral Game.Environment.Atmosphere.Neutral Neutral atmosphere
13 Game_Environment_Atmosphere_Tense Game.Environment.Atmosphere.Tense Tension building
14 Game_Environment_Atmosphere_Danger Game.Environment.Atmosphere.Danger Active danger
15 Game_Environment_Atmosphere_Terror Game.Environment.Atmosphere.Terror Peak terror
16 Game_Environment_Atmosphere_Eerie Game.Environment.Atmosphere.Eerie Eerie ambient
17 Game_Environment_Scare Game.Environment.Scare Scare event namespace
18 Game_Environment_Scare_MirrorJump Game.Environment.Scare.MirrorJump Mirror jump scare
19 Game_Environment_Scare_CeilingDrop Game.Environment.Scare.CeilingDrop Body drops from ceiling
20 Game_Environment_Scare_DoorSlam Game.Environment.Scare.DoorSlam Door slams behind player
21 Game_Environment_Scare_Whisper Game.Environment.Scare.Whisper Ghost whisper audio
22 Game_Environment_Scare_LightFlicker Game.Environment.Scare.LightFlicker Lights flicker then blackout
23 Game_Environment_Light_Flicker Game.Environment.Light.Flicker Rapid flicker event
24 Game_Environment_Light_Strobe Game.Environment.Light.Strobe Strobe light event
25 Game_Environment_Light_Blackout Game.Environment.Light.Blackout Complete blackout event
26 Game_Environment_Light_ColorShift Game.Environment.Light.ColorShift Color temperature shift
27 Game_Environment_Pacing_Calm Game.Environment.Pacing.Calm Calm pacing band
28 Game_Environment_Pacing_Exploration Game.Environment.Pacing.Exploration Exploration pacing
29 Game_Environment_Pacing_Tension Game.Environment.Pacing.Tension Tension building
30 Game_Environment_Pacing_Combat Game.Environment.Pacing.Combat Combat encounter
31 Game_Environment_Pacing_Climax Game.Environment.Pacing.Climax Story climax
32 Game_Environment_Pacing_Resolution Game.Environment.Pacing.Resolution Post-climax resolution
33 Game_Environment_Performance_High Game.Environment.Performance.High High quality LOD target
34 Game_Environment_Performance_Medium Game.Environment.Performance.Medium Medium quality
35 Game_Environment_Performance_Low Game.Environment.Performance.Low Low quality LOD target

View File

@@ -0,0 +1,37 @@
Name,Tag,DevComment
Framework_Interaction_Type_Pickup,Framework.Interaction.Type.Pickup,Pick up an item
Framework_Interaction_Type_Door,Framework.Interaction.Type.Door,Open/close a door
Framework_Interaction_Type_Drawer,Framework.Interaction.Type.Drawer,Open/close a drawer
Framework_Interaction_Type_Container,Framework.Interaction.Type.Container,Open/loot a container
Framework_Interaction_Type_Inspect,Framework.Interaction.Type.Inspect,Examine an object in 3D
Framework_Interaction_Type_Climb,Framework.Interaction.Type.Climb,Vault or mantle traversal
Framework_Interaction_Type_Hide,Framework.Interaction.Type.Hide,Enter a hiding spot
Framework_Interaction_Type_Use,Framework.Interaction.Type.Use,Use a world object (lever/button/terminal)
Framework_Interaction_Type_Combine,Framework.Interaction.Type.Combine,Combine two items
Framework_Interaction_Type_Grab,Framework.Interaction.Type.Grab,Grab a physics object
Framework_Interaction_Type_Push,Framework.Interaction.Type.Push,Push a physics object
Framework_Interaction_Type_Talk,Framework.Interaction.Type.Talk,Talk to an NPC
Framework_Interaction_Type_Puzzle,Framework.Interaction.Type.Puzzle,Interact with a puzzle device
Framework_Interaction_Context_Requires_Key,Framework.Interaction.Context.Requires.Key,Interaction requires a specific key item
Framework_Interaction_Context_Requires_Item,Framework.Interaction.Context.Requires.Item,Interaction requires a specific item
Framework_Interaction_Context_Locked,Framework.Interaction.Context.Locked,Interaction is locked
Framework_Interaction_Context_Disabled,Framework.Interaction.Context.Disabled,Interaction is disabled
Framework_Interaction_Context_OneShot,Framework.Interaction.Context.OneShot,Single-use interaction
Framework_Interaction_Context_Cooldown,Framework.Interaction.Context.Cooldown,Interaction on cooldown
Framework_Interaction_Prompt_Press,Framework.Interaction.Prompt.Press,Press-to-interact prompt
Framework_Interaction_Prompt_Hold,Framework.Interaction.Prompt.Hold,Hold-to-interact prompt
Framework_Interaction_Prompt_DoubleTap,Framework.Interaction.Prompt.DoubleTap,Double-tap prompt
Framework_Interaction_HidingSpot_Locker,Framework.Interaction.HidingSpot.Locker,Full enclosure
Framework_Interaction_HidingSpot_BehindCover,Framework.Interaction.HidingSpot.BehindCover,Behind low cover
Framework_Interaction_HidingSpot_Under,Framework.Interaction.HidingSpot.Under,Under furniture
Framework_Interaction_HidingSpot_Shadow,Framework.Interaction.HidingSpot.Shadow,In darkness
Framework_Interaction_HidingSpot_Grass,Framework.Interaction.HidingSpot.Grass,In tall grass
Framework_Interaction_Traversal_Vault,Framework.Interaction.Traversal.Vault,Vault over obstacle
Framework_Interaction_Traversal_Mantle,Framework.Interaction.Traversal.Mantle,Mantle onto ledge
Framework_Interaction_Traversal_Slide,Framework.Interaction.Traversal.Slide,Slide under barrier
Framework_Interaction_Traversal_Squeeze,Framework.Interaction.Traversal.Squeeze,Squeeze through gap
Framework_Interaction_Traversal_LedgeGrab,Framework.Interaction.Traversal.LedgeGrab,Grab ledge
Framework_Interaction_Door_Closed,Framework.Interaction.Door.Closed,Door state closed
Framework_Interaction_Door_Open,Framework.Interaction.Door.Open,Door state open
Framework_Interaction_Door_Locked,Framework.Interaction.Door.Locked,Door locked
Framework_Interaction_Door_Barricaded,Framework.Interaction.Door.Barricaded,Door barricaded
1 Name Tag DevComment
2 Framework_Interaction_Type_Pickup Framework.Interaction.Type.Pickup Pick up an item
3 Framework_Interaction_Type_Door Framework.Interaction.Type.Door Open/close a door
4 Framework_Interaction_Type_Drawer Framework.Interaction.Type.Drawer Open/close a drawer
5 Framework_Interaction_Type_Container Framework.Interaction.Type.Container Open/loot a container
6 Framework_Interaction_Type_Inspect Framework.Interaction.Type.Inspect Examine an object in 3D
7 Framework_Interaction_Type_Climb Framework.Interaction.Type.Climb Vault or mantle traversal
8 Framework_Interaction_Type_Hide Framework.Interaction.Type.Hide Enter a hiding spot
9 Framework_Interaction_Type_Use Framework.Interaction.Type.Use Use a world object (lever/button/terminal)
10 Framework_Interaction_Type_Combine Framework.Interaction.Type.Combine Combine two items
11 Framework_Interaction_Type_Grab Framework.Interaction.Type.Grab Grab a physics object
12 Framework_Interaction_Type_Push Framework.Interaction.Type.Push Push a physics object
13 Framework_Interaction_Type_Talk Framework.Interaction.Type.Talk Talk to an NPC
14 Framework_Interaction_Type_Puzzle Framework.Interaction.Type.Puzzle Interact with a puzzle device
15 Framework_Interaction_Context_Requires_Key Framework.Interaction.Context.Requires.Key Interaction requires a specific key item
16 Framework_Interaction_Context_Requires_Item Framework.Interaction.Context.Requires.Item Interaction requires a specific item
17 Framework_Interaction_Context_Locked Framework.Interaction.Context.Locked Interaction is locked
18 Framework_Interaction_Context_Disabled Framework.Interaction.Context.Disabled Interaction is disabled
19 Framework_Interaction_Context_OneShot Framework.Interaction.Context.OneShot Single-use interaction
20 Framework_Interaction_Context_Cooldown Framework.Interaction.Context.Cooldown Interaction on cooldown
21 Framework_Interaction_Prompt_Press Framework.Interaction.Prompt.Press Press-to-interact prompt
22 Framework_Interaction_Prompt_Hold Framework.Interaction.Prompt.Hold Hold-to-interact prompt
23 Framework_Interaction_Prompt_DoubleTap Framework.Interaction.Prompt.DoubleTap Double-tap prompt
24 Framework_Interaction_HidingSpot_Locker Framework.Interaction.HidingSpot.Locker Full enclosure
25 Framework_Interaction_HidingSpot_BehindCover Framework.Interaction.HidingSpot.BehindCover Behind low cover
26 Framework_Interaction_HidingSpot_Under Framework.Interaction.HidingSpot.Under Under furniture
27 Framework_Interaction_HidingSpot_Shadow Framework.Interaction.HidingSpot.Shadow In darkness
28 Framework_Interaction_HidingSpot_Grass Framework.Interaction.HidingSpot.Grass In tall grass
29 Framework_Interaction_Traversal_Vault Framework.Interaction.Traversal.Vault Vault over obstacle
30 Framework_Interaction_Traversal_Mantle Framework.Interaction.Traversal.Mantle Mantle onto ledge
31 Framework_Interaction_Traversal_Slide Framework.Interaction.Traversal.Slide Slide under barrier
32 Framework_Interaction_Traversal_Squeeze Framework.Interaction.Traversal.Squeeze Squeeze through gap
33 Framework_Interaction_Traversal_LedgeGrab Framework.Interaction.Traversal.LedgeGrab Grab ledge
34 Framework_Interaction_Door_Closed Framework.Interaction.Door.Closed Door state closed
35 Framework_Interaction_Door_Open Framework.Interaction.Door.Open Door state open
36 Framework_Interaction_Door_Locked Framework.Interaction.Door.Locked Door locked
37 Framework_Interaction_Door_Barricaded Framework.Interaction.Door.Barricaded Door barricaded

View File

@@ -0,0 +1,28 @@
Name,Tag,DevComment
Framework_Item_Type_Weapon,Framework.Item.Type.Weapon,Firearm or melee weapon
Framework_Item_Type_Consumable,Framework.Item.Type.Consumable,Health pack stim stamina item
Framework_Item_Type_KeyItem,Framework.Item.Type.KeyItem,Story key item
Framework_Item_Type_Document,Framework.Item.Type.Document,Readable document or note
Framework_Item_Type_Collectible,Framework.Item.Type.Collectible,Collectible with set tracking
Framework_Item_Type_Ammo,Framework.Item.Type.Ammo,Ammunition pickup
Framework_Item_Type_Tool,Framework.Item.Type.Tool,Flashlight lockpick etc
Framework_Item_Type_Resource,Framework.Item.Type.Resource,Crafting resource
Framework_Item_Type_Misc,Framework.Item.Type.Misc,Miscellaneous item
Framework_Item_Slot_PrimaryWeapon,Framework.Item.Slot.PrimaryWeapon,Equipped primary weapon slot
Framework_Item_Slot_SecondaryWeapon,Framework.Item.Slot.SecondaryWeapon,Equipped secondary weapon slot
Framework_Item_Slot_Flashlight,Framework.Item.Slot.Flashlight,Equipped flashlight slot
Framework_Item_Slot_Shield,Framework.Item.Slot.Shield,Equipped shield slot
Framework_Item_Slot_Active,Framework.Item.Slot.Active,Active quick-slot item
Framework_Item_Slot_Quick1,Framework.Item.Slot.Quick1,Quick slot 1
Framework_Item_Slot_Quick2,Framework.Item.Slot.Quick2,Quick slot 2
Framework_Item_Slot_Quick3,Framework.Item.Slot.Quick3,Quick slot 3
Framework_Item_Slot_Quick4,Framework.Item.Slot.Quick4,Quick slot 4
Framework_Item_Rarity_Trash,Framework.Item.Rarity.Trash,Trash tier loot
Framework_Item_Rarity_Common,Framework.Item.Rarity.Common,Common tier loot
Framework_Item_Rarity_Uncommon,Framework.Item.Rarity.Uncommon,Uncommon tier loot
Framework_Item_Rarity_Rare,Framework.Item.Rarity.Rare,Rare tier loot
Framework_Item_Rarity_Legendary,Framework.Item.Rarity.Legendary,Legendary tier loot
Framework_Item_Context_Stackable,Framework.Item.Context.Stackable,Item can stack in inventory
Framework_Item_Context_Unique,Framework.Item.Context.Unique,Only one can exist
Framework_Item_Context_Quest,Framework.Item.Context.Quest,Quest-related item
Framework_Item_Context_Droppable,Framework.Item.Context.Droppable,Can be dropped from inventory
1 Name Tag DevComment
2 Framework_Item_Type_Weapon Framework.Item.Type.Weapon Firearm or melee weapon
3 Framework_Item_Type_Consumable Framework.Item.Type.Consumable Health pack stim stamina item
4 Framework_Item_Type_KeyItem Framework.Item.Type.KeyItem Story key item
5 Framework_Item_Type_Document Framework.Item.Type.Document Readable document or note
6 Framework_Item_Type_Collectible Framework.Item.Type.Collectible Collectible with set tracking
7 Framework_Item_Type_Ammo Framework.Item.Type.Ammo Ammunition pickup
8 Framework_Item_Type_Tool Framework.Item.Type.Tool Flashlight lockpick etc
9 Framework_Item_Type_Resource Framework.Item.Type.Resource Crafting resource
10 Framework_Item_Type_Misc Framework.Item.Type.Misc Miscellaneous item
11 Framework_Item_Slot_PrimaryWeapon Framework.Item.Slot.PrimaryWeapon Equipped primary weapon slot
12 Framework_Item_Slot_SecondaryWeapon Framework.Item.Slot.SecondaryWeapon Equipped secondary weapon slot
13 Framework_Item_Slot_Flashlight Framework.Item.Slot.Flashlight Equipped flashlight slot
14 Framework_Item_Slot_Shield Framework.Item.Slot.Shield Equipped shield slot
15 Framework_Item_Slot_Active Framework.Item.Slot.Active Active quick-slot item
16 Framework_Item_Slot_Quick1 Framework.Item.Slot.Quick1 Quick slot 1
17 Framework_Item_Slot_Quick2 Framework.Item.Slot.Quick2 Quick slot 2
18 Framework_Item_Slot_Quick3 Framework.Item.Slot.Quick3 Quick slot 3
19 Framework_Item_Slot_Quick4 Framework.Item.Slot.Quick4 Quick slot 4
20 Framework_Item_Rarity_Trash Framework.Item.Rarity.Trash Trash tier loot
21 Framework_Item_Rarity_Common Framework.Item.Rarity.Common Common tier loot
22 Framework_Item_Rarity_Uncommon Framework.Item.Rarity.Uncommon Uncommon tier loot
23 Framework_Item_Rarity_Rare Framework.Item.Rarity.Rare Rare tier loot
24 Framework_Item_Rarity_Legendary Framework.Item.Rarity.Legendary Legendary tier loot
25 Framework_Item_Context_Stackable Framework.Item.Context.Stackable Item can stack in inventory
26 Framework_Item_Context_Unique Framework.Item.Context.Unique Only one can exist
27 Framework_Item_Context_Quest Framework.Item.Context.Quest Quest-related item
28 Framework_Item_Context_Droppable Framework.Item.Context.Droppable Can be dropped from inventory

View File

@@ -0,0 +1,55 @@
Name,Tag,DevComment
Game_Narrative_Flag,Game.Narrative.Flag,Root narrative flag namespace
Game_Narrative_Phase,Game.Narrative.Phase,Story phase namespace
Game_Narrative_Choice,Game.Narrative.Choice,Dialogue choice consequence tags
Game_Narrative_Ending,Game.Narrative.Ending,Ending evaluation tags
Game_Narrative_Flag_PrologueComplete,Game.Narrative.Flag.PrologueComplete,Prologue chapter completed
Game_Narrative_Flag_Chapter1Complete,Game.Narrative.Flag.Chapter1Complete,Chapter 1 completed
Game_Narrative_Flag_Chapter2Complete,Game.Narrative.Flag.Chapter2Complete,Chapter 2 completed
Game_Narrative_Flag_Chapter3Complete,Game.Narrative.Flag.Chapter3Complete,Chapter 3 completed
Game_Narrative_Flag_Act1Complete,Game.Narrative.Flag.Act1Complete,Act 1 completed
Game_Narrative_Flag_Act2Complete,Game.Narrative.Flag.Act2Complete,Act 2 completed
Game_Narrative_Flag_Act3Complete,Game.Narrative.Flag.Act3Complete,Act 3 completed
Game_Narrative_Flag_FoundKey,Game.Narrative.Flag.FoundKey,Player found the basement key
Game_Narrative_Flag_BasementDoorOpened,Game.Narrative.Flag.BasementDoorOpened,Basement door unlocked
Game_Narrative_Flag_SawMonster,Game.Narrative.Flag.SawMonster,Player first saw the monster
Game_Narrative_Flag_SavedNPC,Game.Narrative.Flag.SavedNPC,Player saved the trapped NPC
Game_Narrative_Phase_Act1,Game.Narrative.Phase.Act1,Story phase: Act 1
Game_Narrative_Phase_Act2,Game.Narrative.Phase.Act2,Story phase: Act 2
Game_Narrative_Phase_Act3,Game.Narrative.Phase.Act3,Story phase: Act 3
Game_Narrative_Phase_Prologue,Game.Narrative.Phase.Prologue,Story phase: Prologue
Game_Narrative_Phase_Epilogue,Game.Narrative.Phase.Epilogue,Story phase: Epilogue
Game_Narrative_Phase_Chapter1,Game.Narrative.Phase.Chapter1,Chapter 1: The Awakening
Game_Narrative_Phase_Chapter2,Game.Narrative.Phase.Chapter2,Chapter 2: Into Darkness
Game_Narrative_Phase_Chapter3,Game.Narrative.Phase.Chapter3,Chapter 3: The Truth
Game_Narrative_Phase_Chapter4,Game.Narrative.Phase.Chapter4,Chapter 4: Confrontation
Game_Narrative_Choice_SparedEnemy,Game.Narrative.Choice.SparedEnemy,Player chose to spare
Game_Narrative_Choice_KilledEnemy,Game.Narrative.Choice.KilledEnemy,Player chose to kill
Game_Narrative_Choice_AcceptedDeal,Game.Narrative.Choice.AcceptedDeal,Player accepted the deal
Game_Narrative_Choice_RejectedDeal,Game.Narrative.Choice.RejectedDeal,Player rejected the deal
Game_Narrative_Choice_RevealedSecret,Game.Narrative.Choice.RevealedSecret,Player revealed the secret
Game_Narrative_Choice_KeptSilent,Game.Narrative.Choice.KeptSilent,Player stayed silent
Game_Narrative_Ending_Good,Game.Narrative.Ending.Good,Good ending path
Game_Narrative_Ending_Bad,Game.Narrative.Ending.Bad,Bad ending path
Game_Narrative_Ending_True,Game.Narrative.Ending.True,True/secret ending path
Game_Narrative_Ending_Sacrifice,Game.Narrative.Ending.Sacrifice,Sacrifice ending path
Game_Narrative_Ending_Escape,Game.Narrative.Ending.Escape,Escape ending path
Game_Narrative_Trial_HospitalEscape,Game.Narrative.Trial.HospitalEscape,Trial: escape the hospital
Game_Narrative_Trial_StealthBasement,Game.Narrative.Trial.StealthBasement,Trial: stealth through basement
Game_Narrative_Trial_SurviveSiege,Game.Narrative.Trial.SurviveSiege,Trial: survive the creature siege
Game_Narrative_Trial_Timebomb,Game.Narrative.Trial.Timebomb,Trial: defuse before timer expires
Game_Narrative_Cutscene_Intro,Game.Narrative.Cutscene.Intro,Intro cutscene
Game_Narrative_Cutscene_Act1End,Game.Narrative.Cutscene.Act1End,Act 1 ending cutscene
Game_Narrative_Cutscene_Act2End,Game.Narrative.Cutscene.Act2End,Act 2 ending cutscene
Game_Narrative_Cutscene_Finale,Game.Narrative.Cutscene.Finale,Final confrontation cutscene
Game_Narrative_Lore_Journal1,Game.Narrative.Lore.Journal1,Lore entry: Dr. Voss Journal 1
Game_Narrative_Lore_Newspaper,Game.Narrative.Lore.Newspaper,Lore entry: Abandoned Newspaper
Game_Narrative_Lore_AudioLog,Game.Narrative.Lore.AudioLog,Lore entry: Audio Log
Framework_Objective_Status_Active,Framework.Objective.Status.Active,Objective currently active
Framework_Objective_Status_Complete,Framework.Objective.Status.Complete,Objective completed
Framework_Objective_Status_Failed,Framework.Objective.Status.Failed,Objective failed
Framework_Objective_Status_Hidden,Framework.Objective.Status.Hidden,Objective hidden until discovered
Framework_Objective_Category_Main,Framework.Objective.Category.Main,Main quest objective
Framework_Objective_Category_Side,Framework.Objective.Category.Side,Optional side objective
Framework_Objective_Category_Hidden,Framework.Objective.Category.Hidden,Secret objective
Framework_Objective_Category_Tutorial,Framework.Objective.Category.Tutorial,Tutorial objective
1 Name Tag DevComment
2 Game_Narrative_Flag Game.Narrative.Flag Root narrative flag namespace
3 Game_Narrative_Phase Game.Narrative.Phase Story phase namespace
4 Game_Narrative_Choice Game.Narrative.Choice Dialogue choice consequence tags
5 Game_Narrative_Ending Game.Narrative.Ending Ending evaluation tags
6 Game_Narrative_Flag_PrologueComplete Game.Narrative.Flag.PrologueComplete Prologue chapter completed
7 Game_Narrative_Flag_Chapter1Complete Game.Narrative.Flag.Chapter1Complete Chapter 1 completed
8 Game_Narrative_Flag_Chapter2Complete Game.Narrative.Flag.Chapter2Complete Chapter 2 completed
9 Game_Narrative_Flag_Chapter3Complete Game.Narrative.Flag.Chapter3Complete Chapter 3 completed
10 Game_Narrative_Flag_Act1Complete Game.Narrative.Flag.Act1Complete Act 1 completed
11 Game_Narrative_Flag_Act2Complete Game.Narrative.Flag.Act2Complete Act 2 completed
12 Game_Narrative_Flag_Act3Complete Game.Narrative.Flag.Act3Complete Act 3 completed
13 Game_Narrative_Flag_FoundKey Game.Narrative.Flag.FoundKey Player found the basement key
14 Game_Narrative_Flag_BasementDoorOpened Game.Narrative.Flag.BasementDoorOpened Basement door unlocked
15 Game_Narrative_Flag_SawMonster Game.Narrative.Flag.SawMonster Player first saw the monster
16 Game_Narrative_Flag_SavedNPC Game.Narrative.Flag.SavedNPC Player saved the trapped NPC
17 Game_Narrative_Phase_Act1 Game.Narrative.Phase.Act1 Story phase: Act 1
18 Game_Narrative_Phase_Act2 Game.Narrative.Phase.Act2 Story phase: Act 2
19 Game_Narrative_Phase_Act3 Game.Narrative.Phase.Act3 Story phase: Act 3
20 Game_Narrative_Phase_Prologue Game.Narrative.Phase.Prologue Story phase: Prologue
21 Game_Narrative_Phase_Epilogue Game.Narrative.Phase.Epilogue Story phase: Epilogue
22 Game_Narrative_Phase_Chapter1 Game.Narrative.Phase.Chapter1 Chapter 1: The Awakening
23 Game_Narrative_Phase_Chapter2 Game.Narrative.Phase.Chapter2 Chapter 2: Into Darkness
24 Game_Narrative_Phase_Chapter3 Game.Narrative.Phase.Chapter3 Chapter 3: The Truth
25 Game_Narrative_Phase_Chapter4 Game.Narrative.Phase.Chapter4 Chapter 4: Confrontation
26 Game_Narrative_Choice_SparedEnemy Game.Narrative.Choice.SparedEnemy Player chose to spare
27 Game_Narrative_Choice_KilledEnemy Game.Narrative.Choice.KilledEnemy Player chose to kill
28 Game_Narrative_Choice_AcceptedDeal Game.Narrative.Choice.AcceptedDeal Player accepted the deal
29 Game_Narrative_Choice_RejectedDeal Game.Narrative.Choice.RejectedDeal Player rejected the deal
30 Game_Narrative_Choice_RevealedSecret Game.Narrative.Choice.RevealedSecret Player revealed the secret
31 Game_Narrative_Choice_KeptSilent Game.Narrative.Choice.KeptSilent Player stayed silent
32 Game_Narrative_Ending_Good Game.Narrative.Ending.Good Good ending path
33 Game_Narrative_Ending_Bad Game.Narrative.Ending.Bad Bad ending path
34 Game_Narrative_Ending_True Game.Narrative.Ending.True True/secret ending path
35 Game_Narrative_Ending_Sacrifice Game.Narrative.Ending.Sacrifice Sacrifice ending path
36 Game_Narrative_Ending_Escape Game.Narrative.Ending.Escape Escape ending path
37 Game_Narrative_Trial_HospitalEscape Game.Narrative.Trial.HospitalEscape Trial: escape the hospital
38 Game_Narrative_Trial_StealthBasement Game.Narrative.Trial.StealthBasement Trial: stealth through basement
39 Game_Narrative_Trial_SurviveSiege Game.Narrative.Trial.SurviveSiege Trial: survive the creature siege
40 Game_Narrative_Trial_Timebomb Game.Narrative.Trial.Timebomb Trial: defuse before timer expires
41 Game_Narrative_Cutscene_Intro Game.Narrative.Cutscene.Intro Intro cutscene
42 Game_Narrative_Cutscene_Act1End Game.Narrative.Cutscene.Act1End Act 1 ending cutscene
43 Game_Narrative_Cutscene_Act2End Game.Narrative.Cutscene.Act2End Act 2 ending cutscene
44 Game_Narrative_Cutscene_Finale Game.Narrative.Cutscene.Finale Final confrontation cutscene
45 Game_Narrative_Lore_Journal1 Game.Narrative.Lore.Journal1 Lore entry: Dr. Voss Journal 1
46 Game_Narrative_Lore_Newspaper Game.Narrative.Lore.Newspaper Lore entry: Abandoned Newspaper
47 Game_Narrative_Lore_AudioLog Game.Narrative.Lore.AudioLog Lore entry: Audio Log
48 Framework_Objective_Status_Active Framework.Objective.Status.Active Objective currently active
49 Framework_Objective_Status_Complete Framework.Objective.Status.Complete Objective completed
50 Framework_Objective_Status_Failed Framework.Objective.Status.Failed Objective failed
51 Framework_Objective_Status_Hidden Framework.Objective.Status.Hidden Objective hidden until discovered
52 Framework_Objective_Category_Main Framework.Objective.Category.Main Main quest objective
53 Framework_Objective_Category_Side Framework.Objective.Category.Side Optional side objective
54 Framework_Objective_Category_Hidden Framework.Objective.Category.Hidden Secret objective
55 Framework_Objective_Category_Tutorial Framework.Objective.Category.Tutorial Tutorial objective

View File

@@ -0,0 +1,35 @@
Name,Tag,DevComment
Framework_Player_State_Alive,Framework.Player.State.Alive,Player is alive and active
Framework_Player_State_Dead,Framework.Player.State.Dead,Player health reached zero
Framework_Player_State_Dying,Framework.Player.State.Dying,Player in downed-but-not-dead state
Framework_Player_State_Hidden,Framework.Player.State.Hidden,Player inside a hiding spot
Framework_Player_State_Interacting,Framework.Player.State.Interacting,Player using an object or UI
Framework_Player_Stress_Low,Framework.Player.Stress.Low,Calm or slightly uneasy
Framework_Player_Stress_Mid,Framework.Player.Stress.Mid,Distressed
Framework_Player_Stress_High,Framework.Player.Stress.High,Panicked
Framework_Player_Stress_Critical,Framework.Player.Stress.Critical,Terrified or catatonic
Framework_Player_Posture_Standing,Framework.Player.Posture.Standing,Default upright posture
Framework_Player_Posture_Crouching,Framework.Player.Posture.Crouching,Crouch-walking or stealth
Framework_Player_Posture_Prone,Framework.Player.Posture.Prone,Lying flat
Framework_Player_Posture_Vaulting,Framework.Player.Posture.Vaulting,Climbing or vaulting obstacle
Framework_Player_Movement_Idle,Framework.Player.Movement.Idle,Not moving
Framework_Player_Movement_Walking,Framework.Player.Movement.Walking,Slow movement
Framework_Player_Movement_Jogging,Framework.Player.Movement.Jogging,Normal movement
Framework_Player_Movement_Sprinting,Framework.Player.Movement.Sprinting,Fast movement with stamina drain
Framework_Player_Movement_Sneaking,Framework.Player.Movement.Sneaking,Quiet crouched movement
Framework_Player_Camera_Default,Framework.Player.Camera.Default,Normal camera
Framework_Player_Camera_Aiming,Framework.Player.Camera.Aiming,ADS zoomed camera
Framework_Player_Camera_Peeking,Framework.Player.Camera.Peeking,Camera offset for hiding peek
Framework_Player_Camera_Injured,Framework.Player.Camera.Injured,Low-health camera effects
Framework_Player_Body_FullBody,Framework.Player.Body.FullBody,Third-person or mirror mode
Framework_Player_Body_ArmsOnly,Framework.Player.Body.ArmsOnly,Default first-person
Framework_Player_Body_ArmsAndShadow,Framework.Player.Body.ArmsAndShadow,Arms plus shadow casting
Framework_Player_Body_Hidden,Framework.Player.Body.Hidden,No body visible (cutscenes/UI)
Framework_Player_Overlay_Clean,Framework.Player.Overlay.Clean,No body overlay
Framework_Player_Overlay_Blood,Framework.Player.Overlay.Blood,Blood splatter on body
Framework_Player_Overlay_Water,Framework.Player.Overlay.Water,Water droplets on body
Framework_Player_Overlay_Mud,Framework.Player.Overlay.Mud,Mud/dirt on body
Framework_Player_Vitals_Stamina_Normal,Framework.Player.Vitals.Stamina.Normal,Full stamina
Framework_Player_Vitals_Stamina_Low,Framework.Player.Vitals.Stamina.Low,Below low threshold
Framework_Player_Vitals_Stamina_Exhausted,Framework.Player.Vitals.Stamina.Exhausted,Below exhausted threshold
Framework_Player_Vitals_Health_Critical,Framework.Player.Vitals.Health.Critical,Below critical health threshold
1 Name Tag DevComment
2 Framework_Player_State_Alive Framework.Player.State.Alive Player is alive and active
3 Framework_Player_State_Dead Framework.Player.State.Dead Player health reached zero
4 Framework_Player_State_Dying Framework.Player.State.Dying Player in downed-but-not-dead state
5 Framework_Player_State_Hidden Framework.Player.State.Hidden Player inside a hiding spot
6 Framework_Player_State_Interacting Framework.Player.State.Interacting Player using an object or UI
7 Framework_Player_Stress_Low Framework.Player.Stress.Low Calm or slightly uneasy
8 Framework_Player_Stress_Mid Framework.Player.Stress.Mid Distressed
9 Framework_Player_Stress_High Framework.Player.Stress.High Panicked
10 Framework_Player_Stress_Critical Framework.Player.Stress.Critical Terrified or catatonic
11 Framework_Player_Posture_Standing Framework.Player.Posture.Standing Default upright posture
12 Framework_Player_Posture_Crouching Framework.Player.Posture.Crouching Crouch-walking or stealth
13 Framework_Player_Posture_Prone Framework.Player.Posture.Prone Lying flat
14 Framework_Player_Posture_Vaulting Framework.Player.Posture.Vaulting Climbing or vaulting obstacle
15 Framework_Player_Movement_Idle Framework.Player.Movement.Idle Not moving
16 Framework_Player_Movement_Walking Framework.Player.Movement.Walking Slow movement
17 Framework_Player_Movement_Jogging Framework.Player.Movement.Jogging Normal movement
18 Framework_Player_Movement_Sprinting Framework.Player.Movement.Sprinting Fast movement with stamina drain
19 Framework_Player_Movement_Sneaking Framework.Player.Movement.Sneaking Quiet crouched movement
20 Framework_Player_Camera_Default Framework.Player.Camera.Default Normal camera
21 Framework_Player_Camera_Aiming Framework.Player.Camera.Aiming ADS zoomed camera
22 Framework_Player_Camera_Peeking Framework.Player.Camera.Peeking Camera offset for hiding peek
23 Framework_Player_Camera_Injured Framework.Player.Camera.Injured Low-health camera effects
24 Framework_Player_Body_FullBody Framework.Player.Body.FullBody Third-person or mirror mode
25 Framework_Player_Body_ArmsOnly Framework.Player.Body.ArmsOnly Default first-person
26 Framework_Player_Body_ArmsAndShadow Framework.Player.Body.ArmsAndShadow Arms plus shadow casting
27 Framework_Player_Body_Hidden Framework.Player.Body.Hidden No body visible (cutscenes/UI)
28 Framework_Player_Overlay_Clean Framework.Player.Overlay.Clean No body overlay
29 Framework_Player_Overlay_Blood Framework.Player.Overlay.Blood Blood splatter on body
30 Framework_Player_Overlay_Water Framework.Player.Overlay.Water Water droplets on body
31 Framework_Player_Overlay_Mud Framework.Player.Overlay.Mud Mud/dirt on body
32 Framework_Player_Vitals_Stamina_Normal Framework.Player.Vitals.Stamina.Normal Full stamina
33 Framework_Player_Vitals_Stamina_Low Framework.Player.Vitals.Stamina.Low Below low threshold
34 Framework_Player_Vitals_Stamina_Exhausted Framework.Player.Vitals.Stamina.Exhausted Below exhausted threshold
35 Framework_Player_Vitals_Health_Critical Framework.Player.Vitals.Health.Critical Below critical health threshold

View File

@@ -0,0 +1,25 @@
Name,Tag,DevComment
Framework_Save_Type_Checkpoint,Framework.Save.Type.Checkpoint,Checkpoint auto-save
Framework_Save_Type_HardSave,Framework.Save.Type.HardSave,Manual save slot
Framework_Save_Type_AutoSave,Framework.Save.Type.AutoSave,Periodic auto-save
Framework_Save_Type_ChapterTransition,Framework.Save.Type.ChapterTransition,Save on chapter load
Framework_Save_Slot_1,Framework.Save.Slot.1,Save slot 1
Framework_Save_Slot_2,Framework.Save.Slot.2,Save slot 2
Framework_Save_Slot_3,Framework.Save.Slot.3,Save slot 3
Framework_Save_Slot_Auto,Framework.Save.Slot.Auto,Autosave slot
Framework_Save_Context_NewGame,Framework.Save.Context.NewGame,First save of new game
Framework_Save_Context_Continue,Framework.Save.Context.Continue,Load existing save
Framework_Save_Context_Delete,Framework.Save.Context.Delete,Delete save slot
Framework_Save_Context_Corrupted,Framework.Save.Context.Corrupted,Save file corrupted
Framework_DeathSpace_Active,Framework.DeathSpace.Active,Player in alternate death space
Framework_DeathSpace_Entered,Framework.DeathSpace.Entered,Player entered death space
Framework_DeathSpace_Exited,Framework.DeathSpace.Exited,Player exited death space
Framework_DeathSpace_FoundExit,Framework.DeathSpace.FoundExit,Player found the exit
Framework_Checkpoint_Active,Framework.Checkpoint.Active,Current active checkpoint
Framework_Checkpoint_Previous,Framework.Checkpoint.Previous,Previous checkpoint
Framework_Checkpoint_Reached,Framework.Checkpoint.Reached,A checkpoint was triggered
Framework_Respawn_Point,Framework.Respawn.Point,Default respawn location
Framework_Respawn_Start,Framework.Respawn.Start,Respawn sequence started
Framework_Respawn_Complete,Framework.Respawn.Complete,Respawn sequence completed
Framework_RunHistory_Death,Framework.RunHistory.Death,Death recorded in run history
Framework_RunHistory_Chapter,Framework.RunHistory.Chapter,Chapter completed in run history
1 Name Tag DevComment
2 Framework_Save_Type_Checkpoint Framework.Save.Type.Checkpoint Checkpoint auto-save
3 Framework_Save_Type_HardSave Framework.Save.Type.HardSave Manual save slot
4 Framework_Save_Type_AutoSave Framework.Save.Type.AutoSave Periodic auto-save
5 Framework_Save_Type_ChapterTransition Framework.Save.Type.ChapterTransition Save on chapter load
6 Framework_Save_Slot_1 Framework.Save.Slot.1 Save slot 1
7 Framework_Save_Slot_2 Framework.Save.Slot.2 Save slot 2
8 Framework_Save_Slot_3 Framework.Save.Slot.3 Save slot 3
9 Framework_Save_Slot_Auto Framework.Save.Slot.Auto Autosave slot
10 Framework_Save_Context_NewGame Framework.Save.Context.NewGame First save of new game
11 Framework_Save_Context_Continue Framework.Save.Context.Continue Load existing save
12 Framework_Save_Context_Delete Framework.Save.Context.Delete Delete save slot
13 Framework_Save_Context_Corrupted Framework.Save.Context.Corrupted Save file corrupted
14 Framework_DeathSpace_Active Framework.DeathSpace.Active Player in alternate death space
15 Framework_DeathSpace_Entered Framework.DeathSpace.Entered Player entered death space
16 Framework_DeathSpace_Exited Framework.DeathSpace.Exited Player exited death space
17 Framework_DeathSpace_FoundExit Framework.DeathSpace.FoundExit Player found the exit
18 Framework_Checkpoint_Active Framework.Checkpoint.Active Current active checkpoint
19 Framework_Checkpoint_Previous Framework.Checkpoint.Previous Previous checkpoint
20 Framework_Checkpoint_Reached Framework.Checkpoint.Reached A checkpoint was triggered
21 Framework_Respawn_Point Framework.Respawn.Point Default respawn location
22 Framework_Respawn_Start Framework.Respawn.Start Respawn sequence started
23 Framework_Respawn_Complete Framework.Respawn.Complete Respawn sequence completed
24 Framework_RunHistory_Death Framework.RunHistory.Death Death recorded in run history
25 Framework_RunHistory_Chapter Framework.RunHistory.Chapter Chapter completed in run history

View File

@@ -0,0 +1,43 @@
Name,Tag,DevComment
Framework_State_Action_Fire,Framework.State.Action.Fire,Firing a weapon
Framework_State_Action_Reload,Framework.State.Action.Reload,Reloading a weapon
Framework_State_Action_Melee,Framework.State.Action.Melee,Melee attack
Framework_State_Action_UseItem,Framework.State.Action.UseItem,Using an item
Framework_State_Action_Interact,Framework.State.Action.Interact,Interacting with world object
Framework_State_Action_OpenMenu,Framework.State.Action.OpenMenu,Opening a menu
Framework_State_Action_Hide,Framework.State.Action.Hide,Entering/occupying hiding spot
Framework_State_Action_Peek,Framework.State.Action.Peek,Peeking from hiding
Framework_State_Action_Sprint,Framework.State.Action.Sprint,Sprinting
Framework_State_Action_Jump,Framework.State.Action.Jump,Jumping
Framework_State_Action_Dodge,Framework.State.Action.Dodge,Dodging/evading
Framework_State_Action_Climb,Framework.State.Action.Climb,Climbing/vaulting
Framework_State_Action_Slide,Framework.State.Action.Slide,Sliding
Framework_State_Action_Squeeze,Framework.State.Action.Squeeze,Squeezing through gap
Framework_State_Action_Grab,Framework.State.Action.Grab,Grabbing a physics object
Framework_State_Action_Throw,Framework.State.Action.Throw,Throwing a held object
Framework_State_Action_Aim,Framework.State.Action.Aim,Aiming down sights
Framework_State_Action_Equip,Framework.State.Action.Equip,Equipping a weapon/item
Framework_State_Action_Holster,Framework.State.Action.Holster,Holstering a weapon
Framework_State_Action_Consume,Framework.State.Action.Consume,Consuming a consumable
Framework_State_Action_Inspect,Framework.State.Action.Inspect,Inspecting an object
Framework_State_Action_ReadDocument,Framework.State.Action.ReadDocument,Reading a document
Framework_State_Action_Dialogue,Framework.State.Action.Dialogue,In dialogue
Framework_State_Action_Choosing,Framework.State.Action.Choosing,Making a dialogue choice
Framework_State_Action_Cutscene,Framework.State.Action.Cutscene,In a cutscene
Framework_State_Overlay_Menu,Framework.State.Overlay.Menu,Menu overlay active
Framework_State_Overlay_Inventory,Framework.State.Overlay.Inventory,Inventory screen open
Framework_State_Overlay_Journal,Framework.State.Overlay.Journal,Journal screen open
Framework_State_Overlay_Map,Framework.State.Overlay.Map,Map screen open
Framework_State_Overlay_Settings,Framework.State.Overlay.Settings,Settings screen open
Framework_State_Overlay_Dialogue,Framework.State.Overlay.Dialogue,Dialogue overlay active
Framework_State_Overlay_Death,Framework.State.Overlay.Death,Death overlay active
Framework_State_Overlay_Loading,Framework.State.Overlay.Loading,Loading screen active
Framework_State_Vital_Health,Framework.State.Vital.Health,Health vital signal
Framework_State_Vital_Stamina,Framework.State.Vital.Stamina,Stamina vital signal
Framework_State_Vital_Stress,Framework.State.Vital.Stress,Stress vital signal
Framework_State_Vital_Fear,Framework.State.Vital.Fear,Fear vital signal
Framework_State_Gating_BlockSprint,Framework.State.Gating.BlockSprint,Gate: sprint blocked
Framework_State_Gating_BlockJump,Framework.State.Gating.BlockJump,Gate: jump blocked
Framework_State_Gating_BlockInteract,Framework.State.Gating.BlockInteract,Gate: interaction blocked
Framework_State_Gating_BlockFire,Framework.State.Gating.BlockFire,Gate: fire blocked
Framework_State_Gating_BlockReload,Framework.State.Gating.BlockReload,Gate: reload blocked
1 Name Tag DevComment
2 Framework_State_Action_Fire Framework.State.Action.Fire Firing a weapon
3 Framework_State_Action_Reload Framework.State.Action.Reload Reloading a weapon
4 Framework_State_Action_Melee Framework.State.Action.Melee Melee attack
5 Framework_State_Action_UseItem Framework.State.Action.UseItem Using an item
6 Framework_State_Action_Interact Framework.State.Action.Interact Interacting with world object
7 Framework_State_Action_OpenMenu Framework.State.Action.OpenMenu Opening a menu
8 Framework_State_Action_Hide Framework.State.Action.Hide Entering/occupying hiding spot
9 Framework_State_Action_Peek Framework.State.Action.Peek Peeking from hiding
10 Framework_State_Action_Sprint Framework.State.Action.Sprint Sprinting
11 Framework_State_Action_Jump Framework.State.Action.Jump Jumping
12 Framework_State_Action_Dodge Framework.State.Action.Dodge Dodging/evading
13 Framework_State_Action_Climb Framework.State.Action.Climb Climbing/vaulting
14 Framework_State_Action_Slide Framework.State.Action.Slide Sliding
15 Framework_State_Action_Squeeze Framework.State.Action.Squeeze Squeezing through gap
16 Framework_State_Action_Grab Framework.State.Action.Grab Grabbing a physics object
17 Framework_State_Action_Throw Framework.State.Action.Throw Throwing a held object
18 Framework_State_Action_Aim Framework.State.Action.Aim Aiming down sights
19 Framework_State_Action_Equip Framework.State.Action.Equip Equipping a weapon/item
20 Framework_State_Action_Holster Framework.State.Action.Holster Holstering a weapon
21 Framework_State_Action_Consume Framework.State.Action.Consume Consuming a consumable
22 Framework_State_Action_Inspect Framework.State.Action.Inspect Inspecting an object
23 Framework_State_Action_ReadDocument Framework.State.Action.ReadDocument Reading a document
24 Framework_State_Action_Dialogue Framework.State.Action.Dialogue In dialogue
25 Framework_State_Action_Choosing Framework.State.Action.Choosing Making a dialogue choice
26 Framework_State_Action_Cutscene Framework.State.Action.Cutscene In a cutscene
27 Framework_State_Overlay_Menu Framework.State.Overlay.Menu Menu overlay active
28 Framework_State_Overlay_Inventory Framework.State.Overlay.Inventory Inventory screen open
29 Framework_State_Overlay_Journal Framework.State.Overlay.Journal Journal screen open
30 Framework_State_Overlay_Map Framework.State.Overlay.Map Map screen open
31 Framework_State_Overlay_Settings Framework.State.Overlay.Settings Settings screen open
32 Framework_State_Overlay_Dialogue Framework.State.Overlay.Dialogue Dialogue overlay active
33 Framework_State_Overlay_Death Framework.State.Overlay.Death Death overlay active
34 Framework_State_Overlay_Loading Framework.State.Overlay.Loading Loading screen active
35 Framework_State_Vital_Health Framework.State.Vital.Health Health vital signal
36 Framework_State_Vital_Stamina Framework.State.Vital.Stamina Stamina vital signal
37 Framework_State_Vital_Stress Framework.State.Vital.Stress Stress vital signal
38 Framework_State_Vital_Fear Framework.State.Vital.Fear Fear vital signal
39 Framework_State_Gating_BlockSprint Framework.State.Gating.BlockSprint Gate: sprint blocked
40 Framework_State_Gating_BlockJump Framework.State.Gating.BlockJump Gate: jump blocked
41 Framework_State_Gating_BlockInteract Framework.State.Gating.BlockInteract Gate: interaction blocked
42 Framework_State_Gating_BlockFire Framework.State.Gating.BlockFire Gate: fire blocked
43 Framework_State_Gating_BlockReload Framework.State.Gating.BlockReload Gate: reload blocked

View File

@@ -1,5 +1,9 @@
# 08 — Health System (`BPC_HealthSystem`)
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Player/BPC_HealthSystem.h` provides the UCLASS shell, `MaxHealth`/`CurrentHealth` variables, `OnHealthChanged` and `OnDeath` event dispatchers. **The C++ stub has NO gameplay logic** — it exists so other C++ classes (`BPC_StateManager`, `BPC_DamageReceptionSystem`) can forward-reference it. **Create a BP child** and build ALL logic from this spec: `TakeDamage()`, `Heal()`, `OnDeath()` transition, damage resistance modifiers, health regen tick, `I_Damageable` interface. See `docs/developer/cpp-integration-guide.md` for setup steps.
>
> ---
## Purpose
Manages the player's health pool, damage application with type-based resistance, health regeneration, death state, and critical-health events. Serves as the central survivability component on the player character.

View File

@@ -1,5 +1,9 @@
# 09 — Stamina System (`BPC_StaminaSystem`)
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Player/BPC_StaminaSystem.h` provides the UCLASS shell, `MaxStamina`/`CurrentStamina` variables, and `OnExhaustionStateChanged` dispatcher. **The C++ stub has NO gameplay logic.** **Create a BP child** and build ALL logic: sprint drain, action costs, exhaustion state machine, regen delay + rate, `CanAffordAction(Cost)` query. See `docs/developer/cpp-integration-guide.md`.
>
> ---
## Purpose
Manages the player's stamina pool — drain during sprinting, jumping, and special actions; regeneration during idle/walking. Prevents action spamming by enforcing minimum stamina thresholds. Drives exhaustion VFX and audio cues.

View File

@@ -1,5 +1,9 @@
# 10 — Stress / Sanity System (`BPC_StressSystem`)
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Player/BPC_StressSystem.h` provides the UCLASS shell, `EStressTier` enum (Calm→Catatonic), `StressTier` variable, and `OnStressTierChanged` dispatcher. **The C++ stub has NO gameplay logic.** **Create a BP child** and build ALL logic: stress accumulation sources, decay during safety, tier transitions, hallucination thresholds. See `docs/developer/cpp-integration-guide.md`.
>
> ---
## Purpose
Tracks the player's psychological state via a stress meter that accumulates from environmental horror, enemy proximity, health deficits, and narrative events. Drives visual distortion, audio hallucinations, gameplay penalties, and narrative branching based on sanity thresholds.

Some files were not shown because too many files have changed in this diff Show More