[code] Add ts instruments & refactor all code
parent
24e515e80d
commit
f572fdebca
|
|
@ -0,0 +1,40 @@
|
|||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
|
||||
# TypeScript declaration files
|
||||
**/*.d.ts
|
||||
|
||||
# ESLint config files
|
||||
.eslintrc.js
|
||||
.eslintrc.cjs
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# Test coverage
|
||||
coverage/
|
||||
|
||||
# Package manager files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
'import',
|
||||
'prefer-arrow',
|
||||
],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'prettier', // Must be last to override other configs
|
||||
],
|
||||
rules: {
|
||||
// TypeScript specific
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/explicit-function-return-type': 'warn',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'error',
|
||||
'@typescript-eslint/prefer-optional-chain': 'error',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/require-await': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
|
||||
// Import rules
|
||||
'import/order': ['error', {
|
||||
'groups': [
|
||||
'builtin',
|
||||
'external',
|
||||
'internal',
|
||||
'parent',
|
||||
'sibling',
|
||||
'index',
|
||||
],
|
||||
'pathGroups': [
|
||||
{
|
||||
'pattern': '#root/**',
|
||||
'group': 'internal',
|
||||
'position': 'before'
|
||||
}
|
||||
],
|
||||
'pathGroupsExcludedImportTypes': ['builtin'],
|
||||
'newlines-between': 'never',
|
||||
'alphabetize': {
|
||||
'order': 'asc',
|
||||
'caseInsensitive': true
|
||||
}
|
||||
}],
|
||||
'import/no-unresolved': 'error',
|
||||
'import/extensions': ['error', 'always', {
|
||||
'ts': 'always',
|
||||
'js': 'always'
|
||||
}],
|
||||
|
||||
// General code quality
|
||||
'no-console': 'off', // Allowed for game development
|
||||
'no-debugger': 'warn',
|
||||
'no-unused-vars': 'off', // Use TypeScript version instead
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
'object-shorthand': 'error',
|
||||
'prefer-template': 'error',
|
||||
'prefer-arrow-callback': 'error',
|
||||
'arrow-body-style': ['error', 'as-needed'],
|
||||
|
||||
// Stylistic (handled by Prettier, but some logical ones)
|
||||
'curly': ['error', 'all'],
|
||||
'brace-style': 'off', // Prettier handles this
|
||||
'comma-dangle': 'off', // Prettier handles this
|
||||
'quotes': 'off', // Prettier handles this
|
||||
'semi': 'off', // Prettier handles this
|
||||
|
||||
// Best practices
|
||||
'eqeqeq': ['error', 'always'],
|
||||
'no-eval': 'error',
|
||||
'no-implied-eval': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-throw-literal': 'error',
|
||||
'no-return-await': 'error',
|
||||
|
||||
// Complexity
|
||||
'complexity': ['warn', 10],
|
||||
'max-lines': ['warn', { max: 500, skipBlankLines: true, skipComments: true }],
|
||||
'max-lines-per-function': ['warn', { max: 100, skipBlankLines: true, skipComments: true }],
|
||||
|
||||
// Naming conventions
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'error',
|
||||
{
|
||||
'selector': 'default',
|
||||
'format': ['PascalCase', 'camelCase']
|
||||
},
|
||||
{
|
||||
'selector': 'function',
|
||||
'format': ['PascalCase', 'camelCase']
|
||||
},
|
||||
{
|
||||
'selector': 'variable',
|
||||
'format': ['camelCase', 'UPPER_CASE', 'PascalCase'],
|
||||
'prefix': ['_', 'BP_', 'AC_', 'WBP_', 'UBP_', 'ABP_', 'IMC_', 'IA_', 'DT_', 'BFL_', 'U', 'A', 'T', 'F']
|
||||
},
|
||||
{
|
||||
'selector': 'variable',
|
||||
'format': ['camelCase', 'UPPER_CASE', 'PascalCase'],
|
||||
'filter': {
|
||||
'regex': '^(?!_|BP_|AC_|WBP_|UBP_|ABP_|IA_|DT_|IMC_|BFL_|U[A-Z]|A[A-Z]|T[A-Z]|F[A-Z])',
|
||||
'match': true
|
||||
}
|
||||
},
|
||||
// Unreal Engine classes with prefixes
|
||||
{
|
||||
'selector': 'class',
|
||||
'format': ['PascalCase'],
|
||||
'prefix': ['_', 'BP_', 'AC_', 'WBP_', 'UBP_', 'ABP_', 'IMC_', 'IA_', 'DT_', 'FT_', 'BFL_', 'U', 'A', 'T', 'F']
|
||||
},
|
||||
// Regular classes without prefix
|
||||
{
|
||||
'selector': 'class',
|
||||
'format': ['PascalCase'],
|
||||
'filter': {
|
||||
'regex': '^(?!_|BP_|AC_|WBP_|UBP_|ABP_|IA_|IMC_|BFL_|U[A-Z]|A[A-Z]|T[A-Z]|F[A-Z])',
|
||||
'match': true
|
||||
}
|
||||
},
|
||||
// Interfaces and Structs
|
||||
{
|
||||
'selector': 'interface',
|
||||
'format': ['PascalCase'],
|
||||
'prefix': ['S_', 'I_', 'I']
|
||||
},
|
||||
// Enums
|
||||
{
|
||||
'selector': 'enum',
|
||||
'format': ['PascalCase'],
|
||||
'prefix': ['E_', 'E']
|
||||
},
|
||||
{
|
||||
'selector': 'enumMember',
|
||||
'format': ['PascalCase']
|
||||
}
|
||||
],
|
||||
|
||||
'@typescript-eslint/no-unsafe-member-access': 'off'
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
'typescript': {
|
||||
'alwaysTryTypes': true,
|
||||
'project': './tsconfig.json'
|
||||
},
|
||||
'node': {
|
||||
'extensions': ['.js', '.ts', '.json']
|
||||
}
|
||||
},
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx']
|
||||
}
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2022: true,
|
||||
node: true,
|
||||
},
|
||||
ignorePatterns: [
|
||||
'node_modules/',
|
||||
'dist/',
|
||||
'build/',
|
||||
'**/*.d.ts'
|
||||
]
|
||||
};
|
||||
|
|
@ -13,6 +13,13 @@
|
|||
!/Config/**
|
||||
!/Plugins/**
|
||||
!/Documentation/**
|
||||
!/.eslintignore
|
||||
!/.eslintrc.js
|
||||
!/.prettierignore
|
||||
!/.prettierrc.js
|
||||
!/package.json
|
||||
!/package-lock.json
|
||||
!/tsconfig.json
|
||||
|
||||
# Only allow .uasset, .umap and .ts files from /Content dir.
|
||||
# .uasset and .umap tracked by git-lfs, don't forget to track
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Package manager files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Generated files
|
||||
**/*.d.ts
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
module.exports = {
|
||||
// Basic formatting
|
||||
printWidth: 80,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
quoteProps: 'as-needed',
|
||||
trailingComma: 'es5',
|
||||
bracketSpacing: true,
|
||||
bracketSameLine: false,
|
||||
arrowParens: 'avoid',
|
||||
|
||||
// Line endings
|
||||
endOfLine: 'lf',
|
||||
|
||||
// TypeScript specific
|
||||
parser: 'typescript',
|
||||
|
||||
// File-specific overrides
|
||||
overrides: [
|
||||
{
|
||||
files: '*.json',
|
||||
options: {
|
||||
parser: 'json',
|
||||
printWidth: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: '*.md',
|
||||
options: {
|
||||
parser: 'markdown',
|
||||
printWidth: 100,
|
||||
proseWrap: 'always',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: '*.{js,ts}',
|
||||
options: {
|
||||
parser: 'typescript',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -1,76 +1,129 @@
|
|||
// Content/Blueprints/BP_MainCharacter.ts
|
||||
// Blueprints/BP_MainCharacter.ts
|
||||
|
||||
import {AC_Movement} from "../Movement/Components/AC_Movement.js";
|
||||
import type {Controller, Float} from "../types.js";
|
||||
import {
|
||||
AddMappingContext,
|
||||
CastToPlayController,
|
||||
EnhancedInputLocalPlayerSubsystem,
|
||||
GetGameTimeInSeconds,
|
||||
} from "../functions.js";
|
||||
import {IMC_Default} from "../Input/IMC_Default.js";
|
||||
import {AC_DebugHUD} from "../Debug/Components/AC_DebugHUD.js";
|
||||
import {AC_ToastSystem} from "../Toasts/Compontents/AC_ToastSystem.js";
|
||||
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
||||
import { IMC_Default } from '#root/Input/IMC_Default.ts';
|
||||
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { Cast } from '#root/UE/Cast.ts';
|
||||
import type { Controller } from '#root/UE/Controller.ts';
|
||||
import { EnhancedInputLocalPlayerSubsystem } from '#root/UE/EnhancedInputLocalPlayerSubsystem.ts';
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import { Pawn } from '#root/UE/Pawn.ts';
|
||||
import type { PlayerController } from '#root/UE/PlayerController.ts';
|
||||
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
||||
|
||||
export class BP_MainCharacter {
|
||||
// Category: "Components"
|
||||
MovementComponent = new AC_Movement();
|
||||
|
||||
// Category: "Components"
|
||||
DebugHUDComponent = new AC_DebugHUD();
|
||||
|
||||
// Category: "Components"
|
||||
ToastSystemComponent = new AC_ToastSystem();
|
||||
|
||||
// Category: "Debug"
|
||||
// Instance Editable: true
|
||||
ShowDebugInfo: boolean = true;
|
||||
/**
|
||||
* Main Character Blueprint
|
||||
* Core player character with deterministic movement system
|
||||
* Integrates debug HUD and toast notification systems
|
||||
*/
|
||||
export class BP_MainCharacter extends Pawn {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
EventReceiveControllerChanged(NewController: Controller): void {
|
||||
const AsPlayerController = CastToPlayController(NewController);
|
||||
const Target = EnhancedInputLocalPlayerSubsystem(AsPlayerController);
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
AddMappingContext(Target, IMC_Default);
|
||||
/**
|
||||
* Handle controller change events - sets up Enhanced Input mapping context
|
||||
*/
|
||||
EventReceiveControllerChanged(NewController: Controller): void {
|
||||
const controller = Cast<PlayerController>(NewController);
|
||||
const system = new EnhancedInputLocalPlayerSubsystem();
|
||||
|
||||
if (controller) {
|
||||
system.AddMappingContext(IMC_Default);
|
||||
}
|
||||
}
|
||||
|
||||
EnhancedInputActionIA_PrevDebugMode() {
|
||||
/** Navigate to previous debug page */
|
||||
EnhancedInputActionPrevDebugMode(): void {
|
||||
if (this.ShowDebugInfo) {
|
||||
this.DebugHUDComponent.PreviousPage();
|
||||
}
|
||||
}
|
||||
|
||||
EnhancedInputActionIA_NextDebugMode() {
|
||||
/** Navigate to next debug page */
|
||||
EnhancedInputActionINextDebugMode(): void {
|
||||
if (this.ShowDebugInfo) {
|
||||
this.DebugHUDComponent.NextPage();
|
||||
}
|
||||
}
|
||||
|
||||
EnhancedInputActionToggleHUD() {
|
||||
/** Toggle debug HUD visibility */
|
||||
EnhancedInputActionToggleHUD(): void {
|
||||
if (this.ShowDebugInfo) {
|
||||
this.DebugHUDComponent.ToggleDebugHUD();
|
||||
}
|
||||
}
|
||||
|
||||
EnhancedInputActionIA_ToggleVisualDebug() {
|
||||
/** Toggle visual debug rendering */
|
||||
EnhancedInputActionToggleVisualDebug(): void {
|
||||
if (this.ShowDebugInfo) {
|
||||
this.DebugHUDComponent.ToggleVisualDebug();
|
||||
}
|
||||
}
|
||||
|
||||
EventBeginPlay() {
|
||||
this.MovementComponent.InitializeMovementSystem();
|
||||
|
||||
/**
|
||||
* Initialize all systems when character spawns
|
||||
* Order: Toast → Debug → Movement (movement last as it may generate debug output)
|
||||
*/
|
||||
EventBeginPlay(): void {
|
||||
// Initialize debug systems first if enabled
|
||||
if (this.ShowDebugInfo) {
|
||||
this.DebugHUDComponent.InitializeDebugHUD(this.MovementComponent);
|
||||
this.ToastSystemComponent.InitializeToastSystem();
|
||||
}
|
||||
this.DebugHUDComponent.InitializeDebugHUD(
|
||||
this.MovementComponent,
|
||||
this.ToastSystemComponent
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize movement system last (may generate debug output)
|
||||
this.MovementComponent.InitializeMovementSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all systems each frame
|
||||
* Called by Unreal Engine game loop
|
||||
*/
|
||||
EventTick(DeltaTime: Float): void {
|
||||
if (this.ShowDebugInfo) {
|
||||
this.DebugHUDComponent.UpdateHUD(GetGameTimeInSeconds(), DeltaTime);
|
||||
this.DebugHUDComponent.UpdateHUD(
|
||||
SystemLibrary.GetGameTimeInSeconds(),
|
||||
DeltaTime
|
||||
);
|
||||
this.ToastSystemComponent.UpdateToastSystem();
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Core movement system component - handles deterministic 3D platformer movement
|
||||
* @category Components
|
||||
*/
|
||||
MovementComponent = new AC_Movement();
|
||||
|
||||
/**
|
||||
* Debug HUD system - displays movement parameters and performance metrics
|
||||
* @category Components
|
||||
*/
|
||||
DebugHUDComponent = new AC_DebugHUD();
|
||||
|
||||
/**
|
||||
* Toast notification system - displays temporary status messages
|
||||
* @category Components
|
||||
*/
|
||||
ToastSystemComponent = new AC_ToastSystem();
|
||||
|
||||
/**
|
||||
* Master debug toggle - controls all debug systems (HUD, toasts, visual debug)
|
||||
* @category Debug
|
||||
* @instanceEditable true
|
||||
*/
|
||||
private ShowDebugInfo: boolean = true;
|
||||
}
|
||||
|
|
|
|||
BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)
BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
// Blueprints/BP_TengriGameMode.ts
|
||||
|
||||
import { BP_MainCharacter } from '#root/Blueprints/BP_MainCharacter.ts';
|
||||
import { GameModeBase } from '#root/UE/GameModeBase.ts';
|
||||
|
||||
export class BP_TengriGameMode extends GameModeBase {
|
||||
DefaultPawnClass = BP_MainCharacter;
|
||||
}
|
||||
|
|
@ -1,123 +1,81 @@
|
|||
// Content/Debug/Components/AC_DebugHUD.ts
|
||||
// Debug/Components/AC_DebugHUD.ts
|
||||
|
||||
import type {AC_Movement} from "../../Movement/Components/AC_Movement.js";
|
||||
import type {Float, Integer} from "../../types.js";
|
||||
import type {S_DebugPage} from "../Structs/S_DebugPage.js";
|
||||
import {AddToArray, CreateWidget, GetFromArray, IsValid, Print, SetArrayElem} from "../../functions.js";
|
||||
import {E_DebugPageID} from "../Enums/E_DebugPageID.js";
|
||||
import {E_DebugUpdateFunction} from "../Enums/E_DebugUpdateFunction.js";
|
||||
import {WBP_DebugHUD} from "../UI/WBP_DebugHUD.js";
|
||||
import type {S_DebugSettings} from "../Structs/S_DebugSettings.js";
|
||||
import {E_DebugMode} from "../Enums/E_DebugMode.js";
|
||||
import {ESlateVisibility} from "../../enums.js";
|
||||
import { E_DebugUpdateFunction } from '#root/Debug/Enums/E_DebugUpdateFunction.ts';
|
||||
import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.ts';
|
||||
import type { S_DebugSettings } from '#root/Debug/Structs/S_DebugSettings.ts';
|
||||
import { DT_DebugPages } from '#root/Debug/Tables/DT_DebugPages.ts';
|
||||
import { WBP_DebugHUD } from '#root/Debug/UI/WBP_DebugHUD.ts';
|
||||
import type { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||
import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { ActorComponent } from '#root/UE/ActorComponent.ts';
|
||||
import { CreateWidget } from '#root/UE/CteateWidget.ts';
|
||||
import type { DataTable } from '#root/UE/DataTable.ts';
|
||||
import { DataTableFunctionLibrary } from '#root/UE/DataTableFunctionLibrary.ts';
|
||||
import { ESlateVisibility } from '#root/UE/ESlateVisibility.ts';
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import type { Integer } from '#root/UE/Integer.ts';
|
||||
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
||||
import { UEArray } from '#root/UE/UEArray.ts';
|
||||
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
||||
|
||||
/**
|
||||
* Debug HUD Controller Component
|
||||
* Manages debug information display system for deterministic movement
|
||||
* Provides real-time performance monitoring and parameter visualization
|
||||
* Part of Stage 2: Debug HUD system implementation
|
||||
*/
|
||||
export class AC_DebugHUD {
|
||||
// Category: "DebugConfig"
|
||||
// Instance Editable: true
|
||||
/**
|
||||
* Debug system configuration settings
|
||||
* Controls visibility, update frequency, and current page
|
||||
* Instance editable for designer customization
|
||||
*/
|
||||
public DebugSettings: S_DebugSettings = {
|
||||
CurrentMode: E_DebugMode.Visible,
|
||||
CurrentPageIndex: 0,
|
||||
ShowVisualDebug: false,
|
||||
UpdateFrequency: 0.0,
|
||||
}
|
||||
export class AC_DebugHUD extends ActorComponent {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// FUNCTIONS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Category: "DebugState"
|
||||
/**
|
||||
* System initialization state flag
|
||||
* Set to true after successful InitializeDebugHUD call
|
||||
*/
|
||||
private IsInitialized: boolean = false;
|
||||
|
||||
// Category: "DebugState"
|
||||
/**
|
||||
* Timestamp of last HUD update (seconds)
|
||||
* Used for update frequency control
|
||||
*/
|
||||
private LastUpdateTime: Float = 0;
|
||||
|
||||
// Category: "DebugState"
|
||||
/**
|
||||
* Current frame counter for performance tracking
|
||||
* Incremented each update cycle
|
||||
*/
|
||||
private FrameCounter: Float = 0;
|
||||
|
||||
// Category: "DebugState"
|
||||
/**
|
||||
* Current frames per second calculation
|
||||
* Calculated as 1.0 / DeltaTime
|
||||
*/
|
||||
private FPS: Float = 0;
|
||||
|
||||
// Category: "DebugState"
|
||||
/**
|
||||
* Reference to movement component being debugged
|
||||
* Set during initialization, used for accessing movement data
|
||||
*/
|
||||
private MovementComponent: AC_Movement | null = null;
|
||||
|
||||
// Category: "Widget Control"
|
||||
/**
|
||||
* Debug HUD widget instance
|
||||
* Created during initialization, manages UI display
|
||||
*/
|
||||
private DebugWidget: WBP_DebugHUD | null = null;
|
||||
|
||||
// Category: "Page System"
|
||||
/**
|
||||
* Array of registered debug pages
|
||||
* Contains all available pages with their data and update functions
|
||||
*/
|
||||
private DebugPages: S_DebugPage[] = [];
|
||||
|
||||
// Category: "HUD Control"
|
||||
// Pure: true
|
||||
/**
|
||||
* Check if debug HUD should be visible
|
||||
* @returns True if system is initialized and mode is visible
|
||||
* @pure Function has no side effects
|
||||
* @category HUD Control
|
||||
* @pure true
|
||||
*/
|
||||
private ShouldShowDebugHUD() {
|
||||
return this.IsInitialized && this.DebugSettings.CurrentMode === E_DebugMode.Visible;
|
||||
private ShouldShowDebugHUD(): boolean {
|
||||
const IsSystemReadyAndVisible = (modeIsVisible: boolean): boolean =>
|
||||
this.IsInitialized && modeIsVisible;
|
||||
|
||||
return IsSystemReadyAndVisible(
|
||||
this.DebugSettings.CurrentMode === ESlateVisibility.Visible
|
||||
);
|
||||
}
|
||||
|
||||
// Category: "HUD Control"
|
||||
// Pure: true
|
||||
/**
|
||||
* Check if debug HUD should update this frame
|
||||
* @param CurrentTime - Current game time in seconds
|
||||
* @returns True if enough time has passed since last update
|
||||
* @pure Function has no side effects
|
||||
* @example
|
||||
* // Update every frame (UpdateFrequency = 0)
|
||||
* ShouldUpdateDebugHUD(gameTime) // returns true
|
||||
* // Update at 30Hz (UpdateFrequency = 30)
|
||||
* ShouldUpdateDebugHUD(gameTime) // returns true every 1/30 seconds
|
||||
* @param CurrentTime - Current game time in seconds
|
||||
* @returns True if enough time has passed since last update
|
||||
* @pure true
|
||||
* @category HUD Control
|
||||
*/
|
||||
private ShouldUpdateDebugHUD(CurrentTime: Float): boolean {
|
||||
if (this.DebugSettings.UpdateFrequency <= 0) {
|
||||
return true; // Update every frame
|
||||
private ShouldUpdateDebugHUD(CurrentTime: Float = 0): boolean {
|
||||
const IsFrequencyLimited = (updateFrequency: Float): boolean =>
|
||||
updateFrequency > 0;
|
||||
|
||||
const IsUpdateIntervalReached = (
|
||||
currentTime: Float,
|
||||
updateFrequency: Float
|
||||
): boolean => currentTime - this.LastUpdateTime >= 1 / updateFrequency;
|
||||
|
||||
if (IsFrequencyLimited(this.DebugSettings.UpdateFrequency)) {
|
||||
return IsUpdateIntervalReached(
|
||||
CurrentTime,
|
||||
this.DebugSettings.UpdateFrequency
|
||||
);
|
||||
} else {
|
||||
return (CurrentTime - this.LastUpdateTime) >= (1.0 / this.DebugSettings.UpdateFrequency);
|
||||
return true; // Update every frame
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Page Management"
|
||||
/**
|
||||
* Register or update a debug page in the system
|
||||
* @param PageData - Page configuration and content data
|
||||
* @private Internal page management method
|
||||
* @example
|
||||
* // Register movement constants page
|
||||
* RegisterDebugPage({
|
||||
|
|
@ -127,279 +85,333 @@ export class AC_DebugHUD {
|
|||
* IsVisible: true,
|
||||
* UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage
|
||||
* })
|
||||
* @param PageData - Page configuration and content data
|
||||
* @category Page Management
|
||||
*/
|
||||
private RegisterDebugPage(PageData: S_DebugPage): void {
|
||||
let existingIndex: Integer = -1;
|
||||
|
||||
this.DebugPages.forEach((page, index) => {
|
||||
if (page.PageID === PageData.PageID) {
|
||||
this.DebugPages[index] = PageData;
|
||||
existingIndex = index;
|
||||
return;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
SetArrayElem(this.DebugPages, existingIndex, PageData);
|
||||
const IsPageExists = (): boolean => existingIndex >= 0;
|
||||
|
||||
if (IsPageExists()) {
|
||||
this.DebugPages.SetArrayElem(existingIndex, PageData);
|
||||
} else {
|
||||
AddToArray(this.DebugPages, PageData);
|
||||
this.DebugPages.Add(PageData);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Page Management"
|
||||
// Pure: true
|
||||
/**
|
||||
* Get all currently visible debug pages
|
||||
* @returns Array of visible pages only
|
||||
* @pure Function has no side effects
|
||||
* @category Page Management
|
||||
* @pure true
|
||||
*/
|
||||
private GetVisiblePages(): S_DebugPage[] {
|
||||
const filteredPages: S_DebugPage[] = [];
|
||||
public GetVisiblePages(): UEArray<S_DebugPage> {
|
||||
const filteredPages: UEArray<S_DebugPage> = new UEArray([]);
|
||||
|
||||
this.DebugPages.forEach((page) => {
|
||||
this.DebugPages.forEach(page => {
|
||||
if (page.IsVisible) {
|
||||
AddToArray(filteredPages, page);
|
||||
filteredPages.Add(page);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return filteredPages;
|
||||
}
|
||||
|
||||
// Category: "Page Management"
|
||||
/**
|
||||
* Get currently selected debug page
|
||||
* @returns Object with page data and validity flag
|
||||
* @pure Function has no side effects
|
||||
* @category Page Management
|
||||
* @pure true
|
||||
*/
|
||||
private GetCurrentPage(): {Page: S_DebugPage | null, IsFound: boolean} {
|
||||
const length = this.GetVisiblePages().length;
|
||||
private GetCurrentPage():
|
||||
| { Page: S_DebugPage | null; IsFound: true }
|
||||
| { Page: null; IsFound: false } {
|
||||
const IsPageIndexInvalid = (
|
||||
length: Integer,
|
||||
currentPageIndex: Integer
|
||||
): boolean => length === 0 || currentPageIndex >= length;
|
||||
|
||||
if (!((length === 0) || (this.DebugSettings.CurrentPageIndex >= length))) {
|
||||
return {Page: GetFromArray(this.DebugPages, this.DebugSettings.CurrentPageIndex), IsFound: true};
|
||||
} else {
|
||||
return {Page: null, IsFound: false};
|
||||
}
|
||||
return IsPageIndexInvalid(
|
||||
this.GetVisiblePages().length,
|
||||
this.DebugSettings.CurrentPageIndex
|
||||
)
|
||||
? { Page: null, IsFound: false }
|
||||
: {
|
||||
Page: this.GetVisiblePages().Get(this.DebugSettings.CurrentPageIndex),
|
||||
IsFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Category: "Navigation"
|
||||
/**
|
||||
* Toggle debug HUD visibility between visible and hidden
|
||||
* @public User input handler for F1 key or similar
|
||||
* @category Navigation
|
||||
*/
|
||||
public ToggleDebugHUD() {
|
||||
this.DebugSettings.CurrentMode = this.DebugSettings.CurrentMode === E_DebugMode.Visible ? E_DebugMode.Hidden : E_DebugMode.Visible;
|
||||
public ToggleDebugHUD(): void {
|
||||
this.DebugSettings.CurrentMode =
|
||||
this.DebugSettings.CurrentMode === ESlateVisibility.Visible
|
||||
? ESlateVisibility.Hidden
|
||||
: ESlateVisibility.Visible;
|
||||
this.UpdateWidgetVisibility();
|
||||
}
|
||||
|
||||
// Category: "Navigation"
|
||||
/**
|
||||
* Navigate to previous debug page
|
||||
* Wraps around to last page if at beginning
|
||||
* @public User input handler for PageUp key
|
||||
* @category Navigation
|
||||
*/
|
||||
public PreviousPage() {
|
||||
public PreviousPage(): void {
|
||||
const length = this.GetVisiblePages().length;
|
||||
|
||||
if (length > 1) {
|
||||
const currentPage = this.DebugSettings.CurrentPageIndex;
|
||||
|
||||
this.DebugSettings.CurrentPageIndex = currentPage - 1 < 0 ? length - 1 : currentPage - 1;
|
||||
const isAtFirstPage = (): boolean => currentPage - 1 < 0;
|
||||
const getLastPageIndex = (): Integer => length - 1;
|
||||
const getPreviousPageIndex = (): Integer => currentPage - 1;
|
||||
|
||||
this.DebugSettings.CurrentPageIndex = isAtFirstPage()
|
||||
? getLastPageIndex()
|
||||
: getPreviousPageIndex();
|
||||
|
||||
this.UpdateCurrentPage();
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Navigation"
|
||||
/**
|
||||
* Navigate to next debug page
|
||||
* Wraps around to first page if at end
|
||||
* @public User input handler for PageDown key
|
||||
* @category Navigation
|
||||
*/
|
||||
public NextPage() {
|
||||
public NextPage(): void {
|
||||
const length = this.GetVisiblePages().length;
|
||||
|
||||
if (length > 1) {
|
||||
this.DebugSettings.CurrentPageIndex = (this.DebugSettings.CurrentPageIndex + 1) % length;
|
||||
const HasMultiplePages = (): boolean => length > 1;
|
||||
const GetNextPageIndex = (currentPageIndex: Integer): Integer =>
|
||||
(currentPageIndex + 1) % length;
|
||||
|
||||
if (HasMultiplePages()) {
|
||||
this.DebugSettings.CurrentPageIndex = GetNextPageIndex(
|
||||
this.DebugSettings.CurrentPageIndex
|
||||
);
|
||||
|
||||
this.UpdateCurrentPage();
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Visual Debug"
|
||||
/**
|
||||
* Toggle visual debug rendering (collision shapes, rays, etc.)
|
||||
* @public User input handler for visual debug toggle
|
||||
* @category Visual Debug
|
||||
*/
|
||||
public ToggleVisualDebug() {
|
||||
public ToggleVisualDebug(): void {
|
||||
this.DebugSettings.ShowVisualDebug = !this.DebugSettings.ShowVisualDebug;
|
||||
|
||||
if (SystemLibrary.IsValid(this.ToastComponent)) {
|
||||
this.ToastComponent.ShowToast(
|
||||
`Visual Debug ${this.DebugSettings.ShowVisualDebug ? 'Enabled' : 'Disabled'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Page Updates"
|
||||
/**
|
||||
* Update content of currently selected page
|
||||
* Calls appropriate update function based on page type
|
||||
* @private Internal update system method
|
||||
* @category Page Updates
|
||||
*/
|
||||
private UpdateCurrentPage() {
|
||||
const {Page, IsFound}: {Page: S_DebugPage, IsFound: boolean} = this.GetCurrentPage();
|
||||
let CurrentPage = Page;
|
||||
private UpdateCurrentPage(): void {
|
||||
let CurrentPage = this.GetCurrentPage().Page;
|
||||
|
||||
if (IsFound) {
|
||||
if (this.GetCurrentPage().IsFound && CurrentPage !== null) {
|
||||
switch (CurrentPage.UpdateFunction) {
|
||||
case E_DebugUpdateFunction.UpdateMovementPage:
|
||||
case E_DebugUpdateFunction.UpdateMovementPage: {
|
||||
CurrentPage = this.UpdateMovementPage(CurrentPage);
|
||||
break;
|
||||
case E_DebugUpdateFunction.UpdateSurfacePage:
|
||||
}
|
||||
case E_DebugUpdateFunction.UpdateSurfacePage: {
|
||||
CurrentPage = this.UpdateSurfacePage(CurrentPage);
|
||||
break;
|
||||
case E_DebugUpdateFunction.UpdatePerformancePage:
|
||||
}
|
||||
case E_DebugUpdateFunction.UpdatePerformancePage: {
|
||||
CurrentPage = this.UpdatePerformancePage(CurrentPage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SetArrayElem(this.DebugPages, this.DebugSettings.CurrentPageIndex, CurrentPage);
|
||||
this.DebugPages.SetArrayElem(
|
||||
this.DebugSettings.CurrentPageIndex,
|
||||
CurrentPage
|
||||
);
|
||||
|
||||
this.UpdateWidgetPage();
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Page Updates"
|
||||
/**
|
||||
* Update movement constants page content
|
||||
* @param Page - Page structure to update
|
||||
* @returns Updated page with current movement data
|
||||
* @private Page-specific update method
|
||||
* @category Page Updates
|
||||
*/
|
||||
private UpdateMovementPage(Page: S_DebugPage): S_DebugPage {
|
||||
if (IsValid(this.MovementComponent)) {
|
||||
public UpdateMovementPage(Page: S_DebugPage): S_DebugPage {
|
||||
if (SystemLibrary.IsValid(this.MovementComponent)) {
|
||||
return {
|
||||
PageID: Page.PageID,
|
||||
Title: Page.Title,
|
||||
Content: `Max Speed: ${this.MovementComponent.MovementConstants.MaxSpeed}\n` +
|
||||
Content:
|
||||
`Max Speed: ${this.MovementComponent.MovementConstants.MaxSpeed}\n` +
|
||||
`Acceleration: ${this.MovementComponent.MovementConstants.Acceleration}\n` +
|
||||
`Friction: ${this.MovementComponent.MovementConstants.Friction}\n` +
|
||||
`Gravity: ${this.MovementComponent.MovementConstants.Gravity}`,
|
||||
IsVisible: Page.IsVisible,
|
||||
UpdateFunction: Page.UpdateFunction
|
||||
}
|
||||
UpdateFunction: Page.UpdateFunction,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
PageID: Page.PageID,
|
||||
Title: Page.Title,
|
||||
Content: 'Movement Component Not Found',
|
||||
IsVisible: Page.IsVisible,
|
||||
UpdateFunction: Page.UpdateFunction
|
||||
}
|
||||
UpdateFunction: Page.UpdateFunction,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Page Updates"
|
||||
/**
|
||||
* Update surface classification page content
|
||||
* @param Page - Page structure to update
|
||||
* @returns Updated page with current surface angle thresholds
|
||||
* @private Page-specific update method
|
||||
* @category Page Updates
|
||||
*/
|
||||
private UpdateSurfacePage(Page: S_DebugPage): S_DebugPage {
|
||||
if (IsValid(this.MovementComponent)) {
|
||||
public UpdateSurfacePage(Page: S_DebugPage): S_DebugPage {
|
||||
if (SystemLibrary.IsValid(this.MovementComponent)) {
|
||||
return {
|
||||
PageID: Page.PageID,
|
||||
Title: Page.Title,
|
||||
Content: `Walkable: ≤${this.MovementComponent.AngleThresholdsDegrees.Walkable}°\n` +
|
||||
Content:
|
||||
`Walkable: ≤${this.MovementComponent.AngleThresholdsDegrees.Walkable}°\n` +
|
||||
`Steep Slope: ${this.MovementComponent.AngleThresholdsDegrees.Walkable}°-${this.MovementComponent.AngleThresholdsDegrees.SteepSlope}°\n` +
|
||||
`Wall: ${this.MovementComponent.AngleThresholdsDegrees.SteepSlope}°-${this.MovementComponent.AngleThresholdsDegrees.Wall}°\n` +
|
||||
`Ceiling: >${this.MovementComponent.AngleThresholdsDegrees.Wall}°`,
|
||||
IsVisible: Page.IsVisible,
|
||||
UpdateFunction: Page.UpdateFunction
|
||||
}
|
||||
UpdateFunction: Page.UpdateFunction,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
PageID: Page.PageID,
|
||||
Title: Page.Title,
|
||||
Content: 'Movement Component Not Found',
|
||||
IsVisible: Page.IsVisible,
|
||||
UpdateFunction: Page.UpdateFunction
|
||||
}
|
||||
UpdateFunction: Page.UpdateFunction,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Page Updates"
|
||||
/**
|
||||
* Update performance metrics page content
|
||||
* @param Page - Page structure to update
|
||||
* @returns Updated page with current performance data
|
||||
* @private Page-specific update method
|
||||
* @category Page Updates
|
||||
*/
|
||||
private UpdatePerformancePage(Page: S_DebugPage): S_DebugPage {
|
||||
if (IsValid(this.MovementComponent)) {
|
||||
public UpdatePerformancePage(Page: S_DebugPage): S_DebugPage {
|
||||
if (SystemLibrary.IsValid(this.MovementComponent)) {
|
||||
const IsUpdatingEveryFrame = (updateFrequency: Float): boolean =>
|
||||
updateFrequency <= 0;
|
||||
|
||||
return {
|
||||
PageID: Page.PageID,
|
||||
Title: Page.Title,
|
||||
Content: `Frame: ${this.FrameCounter}\n` +
|
||||
Content:
|
||||
`Frame: ${this.FrameCounter}\n` +
|
||||
`FPS: ${this.FPS}\n` +
|
||||
`Update Rate: ${this.DebugSettings.UpdateFrequency <= 0 ? 'Every Frame' : (this.DebugSettings.UpdateFrequency + ' Hz')}\n` +
|
||||
`Update Rate: ${IsUpdatingEveryFrame(this.DebugSettings.UpdateFrequency) ? 'Every Frame' : `${this.DebugSettings.UpdateFrequency} Hz`}\n` +
|
||||
`ActivePages: ${this.GetVisiblePages().length}`,
|
||||
IsVisible: Page.IsVisible,
|
||||
UpdateFunction: Page.UpdateFunction
|
||||
}
|
||||
UpdateFunction: Page.UpdateFunction,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
PageID: Page.PageID,
|
||||
Title: Page.Title,
|
||||
Content: 'Movement Component Not Found',
|
||||
IsVisible: Page.IsVisible,
|
||||
UpdateFunction: Page.UpdateFunction
|
||||
}
|
||||
UpdateFunction: Page.UpdateFunction,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Widget Control"
|
||||
/**
|
||||
* Create debug widget instance and add to viewport
|
||||
* @private Internal widget management method
|
||||
* @category Widget Control
|
||||
*/
|
||||
private CreateDebugWidget() {
|
||||
this.DebugWidget = CreateWidget<WBP_DebugHUD>(new WBP_DebugHUD);
|
||||
private CreateDebugWidget(): void {
|
||||
this.DebugWidget = CreateWidget(WBP_DebugHUD);
|
||||
this.DebugWidget.MovementComponent = this.MovementComponent;
|
||||
|
||||
if (IsValid(this.DebugWidget)) {
|
||||
if (SystemLibrary.IsValid(this.DebugWidget)) {
|
||||
this.DebugWidget.AddToViewport();
|
||||
} else {
|
||||
Print('Failed to create debug widget');
|
||||
if (SystemLibrary.IsValid(this.ToastComponent)) {
|
||||
this.ToastComponent.ShowToast('Failed to create debug widget');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Widget Control"
|
||||
/**
|
||||
* Update widget visibility based on current debug mode
|
||||
* @private Internal widget management method
|
||||
* @category Widget Control
|
||||
*/
|
||||
private UpdateWidgetVisibility() {
|
||||
if (IsValid(this.DebugWidget)) {
|
||||
this.DebugWidget.SetVisibility(this.ShouldShowDebugHUD() ? ESlateVisibility.Visible : ESlateVisibility.Hidden);
|
||||
private UpdateWidgetVisibility(): void {
|
||||
if (SystemLibrary.IsValid(this.DebugWidget)) {
|
||||
this.DebugWidget.SetVisibility(
|
||||
this.ShouldShowDebugHUD()
|
||||
? ESlateVisibility.Visible
|
||||
: ESlateVisibility.Hidden
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Widget Communication"
|
||||
/**
|
||||
* Send current page data to debug widget for display
|
||||
* @private Internal widget communication method
|
||||
* @category Widget Communication
|
||||
*/
|
||||
private UpdateWidgetPage() {
|
||||
const {Page, IsFound} = this.GetCurrentPage();
|
||||
private UpdateWidgetPage(): void {
|
||||
if (
|
||||
this.GetCurrentPage().IsFound &&
|
||||
SystemLibrary.IsValid(this.DebugWidget)
|
||||
) {
|
||||
const currentPage = this.GetCurrentPage().Page;
|
||||
const visiblePages = this.GetVisiblePages();
|
||||
|
||||
if (IsFound) {
|
||||
this.DebugWidget.SetHeaderText(Page.Title);
|
||||
this.DebugWidget.SetContentText(Page.Content);
|
||||
this.DebugWidget.SetNavigationText(`Page ${this.DebugSettings.CurrentPageIndex + 1}/${this.GetVisiblePages().length} | PageUp/PageDown - Navigate`);
|
||||
this.DebugWidget.SetHeaderText(currentPage!.Title);
|
||||
this.DebugWidget.SetContentText(currentPage!.Content);
|
||||
this.DebugWidget.SetNavigationText(
|
||||
`Page ${this.DebugSettings.CurrentPageIndex + 1}/${visiblePages.length} | PageUp/PageDown - Navigate`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "HUD Rendering"
|
||||
/**
|
||||
* Main update loop for debug HUD system
|
||||
* Called every frame from game loop
|
||||
* @param CurrentTime - Current game time in seconds
|
||||
* @param DeltaTime - Time since last frame in seconds
|
||||
* @public Called by BP_MainCharacter or game framework
|
||||
* @category HUD Rendering
|
||||
*/
|
||||
public UpdateHUD(CurrentTime: Float, DeltaTime: Float) {
|
||||
if (this.IsInitialized && this.ShouldUpdateDebugHUD(CurrentTime)) {
|
||||
public UpdateHUD(CurrentTime: Float, DeltaTime: Float): void {
|
||||
const IsReadyForUpdate = (shouldUpdateHUD: boolean): boolean =>
|
||||
this.IsInitialized && shouldUpdateHUD;
|
||||
const IsValidDeltaTime = (deltaTime: Float): boolean => deltaTime > 0;
|
||||
const CalculateFPS = (deltaTime: Float): Float => 1 / deltaTime;
|
||||
|
||||
if (IsReadyForUpdate(this.ShouldUpdateDebugHUD(CurrentTime))) {
|
||||
this.FrameCounter++;
|
||||
this.FPS = DeltaTime > 0.0 ? 1.0 / DeltaTime : 0.0;
|
||||
this.FPS = IsValidDeltaTime(DeltaTime) ? CalculateFPS(DeltaTime) : 0;
|
||||
this.LastUpdateTime = CurrentTime;
|
||||
|
||||
if (this.ShouldShowDebugHUD()) {
|
||||
|
|
@ -409,223 +421,146 @@ export class AC_DebugHUD {
|
|||
}
|
||||
}
|
||||
|
||||
// Category: "System Setup"
|
||||
/**
|
||||
* Register default debug pages (Movement, Surface, Performance)
|
||||
* @private Internal system setup method
|
||||
* @category System Setup
|
||||
*/
|
||||
private RegisterDefaultPages() {
|
||||
this.RegisterDebugPage({
|
||||
PageID: E_DebugPageID.MovementInfo,
|
||||
Title: "Movement Constants",
|
||||
Content: '',
|
||||
IsVisible: true,
|
||||
UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage
|
||||
});
|
||||
|
||||
this.RegisterDebugPage({
|
||||
PageID: E_DebugPageID.SurfaceInfo,
|
||||
Title: "Surface Classification",
|
||||
Content: '',
|
||||
IsVisible: true,
|
||||
UpdateFunction: E_DebugUpdateFunction.UpdateSurfacePage
|
||||
});
|
||||
|
||||
this.RegisterDebugPage({
|
||||
PageID: E_DebugPageID.PerformanceInfo,
|
||||
Title: "Performance Metrics",
|
||||
Content: '',
|
||||
IsVisible: true,
|
||||
UpdateFunction: E_DebugUpdateFunction.UpdatePerformancePage
|
||||
private RegisterDefaultPages(): void {
|
||||
DataTableFunctionLibrary.GetDataTableRowNames(this.DebugDataTable).forEach(
|
||||
arrayElement => {
|
||||
this.DebugDataTable.GetDataTableRow(arrayElement, row => {
|
||||
this.RegisterDebugPage(row);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Category: "Testing"
|
||||
/**
|
||||
* Test basic debug system functionality
|
||||
* @returns True if all basic tests pass
|
||||
* @private Internal testing method
|
||||
* Get comprehensive test data for debug system validation
|
||||
* @returns Object containing initialization status, DataTable reference, and pages array
|
||||
* @category Testing
|
||||
*/
|
||||
private TestDebugSystem() {
|
||||
Print('=== DEBUG SYSTEM TESTS ===');
|
||||
|
||||
if (this.IsInitialized) {
|
||||
Print('✅ System initialized');
|
||||
|
||||
if (this.DebugPages.length === 3) {
|
||||
Print('✅ All pages registered');
|
||||
|
||||
if (IsValid(this.MovementComponent)) {
|
||||
Print('✅ Movement component valid');
|
||||
|
||||
if (IsValid(this.DebugWidget)) {
|
||||
Print('✅ Debug widget created');
|
||||
Print('✅ ALL TESTS PASSED');
|
||||
return true;
|
||||
} else {
|
||||
Print('❌ Debug widget not created');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Print('❌ Movement component not valid');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Print(`❌ Expected 3 pages, got ${this.DebugPages.length}`);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Print('❌ System not initialized');
|
||||
return false;
|
||||
}
|
||||
public GetTestData(): {
|
||||
IsInitialized: boolean;
|
||||
DebugDataTable: DataTable<S_DebugPage>;
|
||||
DebugPages: UEArray<S_DebugPage>;
|
||||
} {
|
||||
return {
|
||||
IsInitialized: this.IsInitialized,
|
||||
DebugDataTable: this.DebugDataTable,
|
||||
DebugPages: this.DebugPages,
|
||||
};
|
||||
}
|
||||
|
||||
// Category: "Testing"
|
||||
/**
|
||||
* Test page content generation for all registered pages
|
||||
* @returns True if all pages generate non-empty content
|
||||
* @private Internal testing method
|
||||
*/
|
||||
private TestPageContentGeneration() {
|
||||
let allTestsPassed = true;
|
||||
|
||||
this.DebugPages.forEach((page, index) => {
|
||||
let updatedPage: S_DebugPage = page;
|
||||
|
||||
switch (updatedPage.UpdateFunction) {
|
||||
case E_DebugUpdateFunction.UpdateMovementPage:
|
||||
updatedPage = this.UpdateMovementPage(updatedPage);
|
||||
break;
|
||||
case E_DebugUpdateFunction.UpdateSurfacePage:
|
||||
updatedPage = this.UpdateSurfacePage(updatedPage);
|
||||
break;
|
||||
case E_DebugUpdateFunction.UpdatePerformancePage:
|
||||
updatedPage = this.UpdatePerformancePage(updatedPage);
|
||||
break;
|
||||
}
|
||||
|
||||
if (updatedPage.Content.length > 0) {
|
||||
Print(`✅ Page ${index + 1} content generated`);
|
||||
} else {
|
||||
Print(`❌ Page ${index + 1} content empty`);
|
||||
allTestsPassed = false;
|
||||
return;
|
||||
}
|
||||
})
|
||||
|
||||
return allTestsPassed;
|
||||
}
|
||||
|
||||
// Category: "Testing"
|
||||
// Pure: true
|
||||
/**
|
||||
* Validate current navigation state
|
||||
* @returns True if current page index is valid
|
||||
* @pure Function has no side effects
|
||||
*/
|
||||
private IsValidNavigationState() {
|
||||
if (this.DebugSettings.CurrentPageIndex >= 0) {
|
||||
const visiblePagesLength = this.GetVisiblePages().length;
|
||||
|
||||
return !((visiblePagesLength > 0) && (this.DebugSettings.CurrentPageIndex >= visiblePagesLength));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Testing"
|
||||
/**
|
||||
* Test page navigation functionality
|
||||
* @param StartCurrentPage - Initial page index to restore after test
|
||||
* @returns True if navigation works correctly
|
||||
* @private Internal testing method
|
||||
*/
|
||||
private TestNavigation(StartCurrentPage: Integer) {
|
||||
if (this.IsValidNavigationState()) {
|
||||
if (this.IsValidNavigationState()) {
|
||||
this.NextPage();
|
||||
|
||||
if (this.IsValidNavigationState()) {
|
||||
if (this.IsValidNavigationState()) {
|
||||
this.PreviousPage();
|
||||
|
||||
if (this.IsValidNavigationState()) {
|
||||
this.DebugSettings.CurrentPageIndex = StartCurrentPage;
|
||||
|
||||
if (this.IsValidNavigationState()) {
|
||||
return true;
|
||||
} else {
|
||||
Print('❌ Navigation: Failed to restore valid state');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Print('❌ Navigation: PrevPage failed — State became invalid after PreviousPage');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Print('❌ Navigation: PreviousPage failed — Invalid state before PreviousPage');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Print('❌ Navigation: NextPage failed — State became invalid after NextPage');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Print('❌ Navigation: NextPage failed — Invalid state before NextPage');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Print('❌ Navigation: Invalid initial state');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Testing"
|
||||
/**
|
||||
* Run comprehensive test suite for debug system
|
||||
* Tests initialization, page content generation, and navigation
|
||||
* @private Called during initialization for validation
|
||||
*/
|
||||
private RunAllTests() {
|
||||
Print('=== COMPREHENSIVE DEBUG SYSTEM TESTING ===');
|
||||
|
||||
if (this.TestDebugSystem()) {
|
||||
if (this.TestPageContentGeneration()) {
|
||||
if (this.TestNavigation(this.DebugSettings.CurrentPageIndex)) {
|
||||
Print('✅ ALL TESTS PASSED');
|
||||
} else {
|
||||
Print('❌ Navigation Tests: Failed');
|
||||
}
|
||||
} else {
|
||||
Print('❌ Page Content Generation Tests: Failed');
|
||||
}
|
||||
} else {
|
||||
Print('❌ System Tests: Failed');
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "System Setup"
|
||||
/**
|
||||
* Initialize debug HUD system with movement component reference
|
||||
* Sets up pages, creates widget, runs tests, and starts display
|
||||
* @param MovementComponentRef - Reference to movement component to debug
|
||||
* @public Called by BP_MainCharacter during initialization
|
||||
* @param ToastComponentRef - Reference to toast system for notifications
|
||||
* @example
|
||||
* // Initialize debug HUD in main character
|
||||
* this.DebugHUDComponent.InitializeDebugHUD(this.MovementComponent);
|
||||
* @category System Setup
|
||||
*/
|
||||
public InitializeDebugHUD(MovementComponentRef: AC_Movement) {
|
||||
public InitializeDebugHUD(
|
||||
MovementComponentRef: AC_Movement,
|
||||
ToastComponentRef: AC_ToastSystem
|
||||
): void {
|
||||
this.MovementComponent = MovementComponentRef;
|
||||
this.ToastComponent = ToastComponentRef;
|
||||
this.IsInitialized = true;
|
||||
this.FrameCounter = 0;
|
||||
this.LastUpdateTime = 0;
|
||||
this.FPS = 0;
|
||||
this.RegisterDefaultPages();
|
||||
this.CreateDebugWidget();
|
||||
this.RunAllTests();
|
||||
this.DebugSettings.CurrentPageIndex = 0;
|
||||
this.UpdateWidgetVisibility();
|
||||
Print('=== DEBUG HUD INITIALIZED ===');
|
||||
this.ToastComponent.ShowToast(
|
||||
'Debug HUD Initialized',
|
||||
E_MessageType.Success
|
||||
);
|
||||
this.UpdateCurrentPage();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Reference to movement component being debugged
|
||||
* Set during initialization, used for accessing movement data
|
||||
* @category Components
|
||||
*/
|
||||
private MovementComponent: AC_Movement | null = null;
|
||||
|
||||
/**
|
||||
* Reference to toast system component for debug messaging
|
||||
* Set during initialization, used for displaying debug notifications
|
||||
* @category Components
|
||||
*/
|
||||
public ToastComponent: AC_ToastSystem | null = null;
|
||||
|
||||
/**
|
||||
* Debug system configuration settings
|
||||
* Controls visibility, update frequency, and current page
|
||||
* Instance editable for designer customization
|
||||
* @category DebugConfig
|
||||
* @instanceEditable true
|
||||
*/
|
||||
public DebugSettings: S_DebugSettings = {
|
||||
CurrentMode: ESlateVisibility.Visible,
|
||||
CurrentPageIndex: 0,
|
||||
ShowVisualDebug: false,
|
||||
UpdateFrequency: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* System initialization state flag
|
||||
* Set to true after successful InitializeDebugHUD call
|
||||
* @category DebugState
|
||||
*/
|
||||
private IsInitialized: boolean = false;
|
||||
|
||||
/**
|
||||
* Timestamp of last HUD update (seconds)
|
||||
* Used for update frequency control
|
||||
* @category DebugState
|
||||
*/
|
||||
private LastUpdateTime: Float = 0;
|
||||
|
||||
/**
|
||||
* Current frame counter for performance tracking
|
||||
* Incremented each update cycle
|
||||
* @category DebugState
|
||||
*/
|
||||
private FrameCounter: Float = 0;
|
||||
|
||||
/**
|
||||
* Current frames per second calculation
|
||||
* Calculated as 1.0 / DeltaTime
|
||||
* @category DebugState
|
||||
*/
|
||||
private FPS: Float = 0;
|
||||
|
||||
/**
|
||||
* Debug HUD widget instance
|
||||
* Created during initialization, manages UI display
|
||||
* @category Widget Control
|
||||
*/
|
||||
private DebugWidget: WBP_DebugHUD | null = null;
|
||||
|
||||
/**
|
||||
* Array of registered debug pages
|
||||
* Contains all available pages with their data and update functions
|
||||
* @category Page System
|
||||
*/
|
||||
private DebugPages: UEArray<S_DebugPage> = new UEArray([]);
|
||||
|
||||
/**
|
||||
* DataTable reference for debug pages storage
|
||||
* Contains structured page data with Name-based indexing for efficient lookup
|
||||
* @category Page System
|
||||
*/
|
||||
private DebugDataTable: DataTable<S_DebugPage> = DT_DebugPages;
|
||||
}
|
||||
|
|
|
|||
BIN
Content/Debug/Components/AC_DebugHUD.uasset (Stored with Git LFS)
BIN
Content/Debug/Components/AC_DebugHUD.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,6 +0,0 @@
|
|||
// Content/Debug/Enums/E_DebugMode.ts
|
||||
|
||||
export enum E_DebugMode {
|
||||
Hidden = "Hidden",
|
||||
Visible = "Visible",
|
||||
}
|
||||
BIN
Content/Debug/Enums/E_DebugMode.uasset (Stored with Git LFS)
BIN
Content/Debug/Enums/E_DebugMode.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
|||
// Content/Debug/Enums/E_DebugPageID.ts
|
||||
// Debug/Enums/E_DebugPageID.ts
|
||||
|
||||
export enum E_DebugPageID {
|
||||
MovementInfo = "MovementInfo",
|
||||
SurfaceInfo = "SurfaceInfo",
|
||||
PerformanceInfo = "PerformanceInfo"
|
||||
MovementInfo = 'MovementInfo',
|
||||
SurfaceInfo = 'SurfaceInfo',
|
||||
PerformanceInfo = 'PerformanceInfo',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Content/Enums/E_DebugUpdateFunction.ts
|
||||
// Debug/Enums/E_DebugUpdateFunction.ts
|
||||
|
||||
export enum E_DebugUpdateFunction {
|
||||
UpdateMovementPage = "UpdateMovementPage",
|
||||
UpdateSurfacePage = "UpdateSurfacePage",
|
||||
UpdatePerformancePage = "UpdatePerformancePage"
|
||||
UpdateMovementPage = 'UpdateMovementPage',
|
||||
UpdateSurfacePage = 'UpdateSurfacePage',
|
||||
UpdatePerformancePage = 'UpdatePerformancePage',
|
||||
}
|
||||
|
|
|
|||
BIN
Content/Debug/Structs/S_DebugColors.uasset (Stored with Git LFS)
BIN
Content/Debug/Structs/S_DebugColors.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,8 +1,8 @@
|
|||
// Content/Debug/Structs/S_DebugPage.ts
|
||||
// Debug/Structs/S_DebugPage.ts
|
||||
|
||||
import type {E_DebugPageID} from "../Enums/E_DebugPageID.js";
|
||||
import type {Text} from "../../types.js";
|
||||
import type {E_DebugUpdateFunction} from "../Enums/E_DebugUpdateFunction.js";
|
||||
import type { E_DebugPageID } from '#root/Debug/Enums/E_DebugPageID.js';
|
||||
import type { E_DebugUpdateFunction } from '#root/Debug/Enums/E_DebugUpdateFunction.js';
|
||||
import type { Text } from '#root/UE/Text.ts';
|
||||
|
||||
export interface S_DebugPage {
|
||||
PageID: E_DebugPageID;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// Content/Debug/Enums/S_DebugSettings.ts
|
||||
// Debug/Enums/S_DebugSettings.ts
|
||||
|
||||
import type {E_DebugMode} from "../Enums/E_DebugMode.js";
|
||||
import type {Float, Integer} from "../../types.js";
|
||||
import type { ESlateVisibility } from '#root/UE/ESlateVisibility.ts';
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import type { Integer } from '#root/UE/Integer.ts';
|
||||
|
||||
export interface S_DebugSettings {
|
||||
CurrentMode: E_DebugMode;
|
||||
CurrentMode: ESlateVisibility;
|
||||
CurrentPageIndex: Integer;
|
||||
ShowVisualDebug: boolean;
|
||||
UpdateFrequency: Float;
|
||||
|
|
|
|||
BIN
Content/Debug/Structs/S_DebugSettings.uasset (Stored with Git LFS)
BIN
Content/Debug/Structs/S_DebugSettings.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -0,0 +1,39 @@
|
|||
// Debug/Tables/DT_DebugPages.ts
|
||||
|
||||
import { E_DebugPageID } from '#root/Debug/Enums/E_DebugPageID.ts';
|
||||
import { E_DebugUpdateFunction } from '#root/Debug/Enums/E_DebugUpdateFunction.ts';
|
||||
import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.ts';
|
||||
import { DataTable } from '#root/UE/DataTable.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEArray } from '#root/UE/UEArray.ts';
|
||||
|
||||
export const DT_DebugPages = new DataTable<S_DebugPage>(
|
||||
null,
|
||||
new Name('DT_DebugPages'),
|
||||
new UEArray<S_DebugPage & { Name: Name }>(
|
||||
{
|
||||
Name: new Name('MovementInfo'),
|
||||
PageID: E_DebugPageID.MovementInfo,
|
||||
Title: 'Movement Constants',
|
||||
Content: '',
|
||||
IsVisible: true,
|
||||
UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage,
|
||||
},
|
||||
{
|
||||
Name: new Name('SurfaceInfo'),
|
||||
PageID: E_DebugPageID.SurfaceInfo,
|
||||
Title: 'Surface Classification',
|
||||
Content: '',
|
||||
IsVisible: true,
|
||||
UpdateFunction: E_DebugUpdateFunction.UpdateSurfacePage,
|
||||
},
|
||||
{
|
||||
Name: new Name('PerformanceInfo'),
|
||||
PageID: E_DebugPageID.PerformanceInfo,
|
||||
Title: 'Performance Metrics',
|
||||
Content: '',
|
||||
IsVisible: true,
|
||||
UpdateFunction: E_DebugUpdateFunction.UpdatePerformancePage,
|
||||
}
|
||||
)
|
||||
);
|
||||
Binary file not shown.
|
|
@ -0,0 +1,112 @@
|
|||
// Debug/Tests/FT_DebugNavigation.ts
|
||||
|
||||
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
||||
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
||||
import type { Integer } from '#root/UE/Integer.ts';
|
||||
|
||||
/**
|
||||
* Functional Test: Debug HUD Navigation System
|
||||
* Tests page navigation state management during NextPage/PreviousPage operations
|
||||
*/
|
||||
export class FT_DebugNavigation extends FunctionalTest {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Test entry point - validates navigation state during page operations
|
||||
* Uses nested validation to ensure CurrentPageIndex stays valid
|
||||
*/
|
||||
EventStartTest(): void {
|
||||
this.DebugHUDComponent.InitializeDebugHUD(
|
||||
this.MovementComponent,
|
||||
this.ToastSystemComponent
|
||||
);
|
||||
|
||||
this.IfValid('Debug HUD: Navigation invalid initial state', () => {
|
||||
this.IfValid(
|
||||
'Debug HUD: NextPage failed — Invalid state before NextPage',
|
||||
() => {
|
||||
this.DebugHUDComponent.NextPage();
|
||||
|
||||
this.IfValid(
|
||||
'Debug HUD: NextPage failed — State became invalid after NextPage',
|
||||
() => {
|
||||
this.DebugHUDComponent.PreviousPage();
|
||||
|
||||
this.IfValid(
|
||||
'Debug HUD: PrevPage failed — State became invalid after PreviousPage',
|
||||
() => {
|
||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// MACROS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Validates current page index and executes callback if state is valid
|
||||
* @param Message - Error message if validation fails
|
||||
* @param Out - Callback to execute if state is valid
|
||||
*/
|
||||
private IfValid(Message: string, Out: () => void): void {
|
||||
const IsPageIndexOutOfBounds = (
|
||||
visiblePagesLength: Integer,
|
||||
currentPage: Integer
|
||||
): boolean => visiblePagesLength > 0 && currentPage >= visiblePagesLength;
|
||||
|
||||
const IsPageIndexNonNegative = (currentPage: Integer): boolean =>
|
||||
currentPage >= 0;
|
||||
|
||||
if (
|
||||
!IsPageIndexOutOfBounds(
|
||||
this.DebugHUDComponent.GetVisiblePages().length,
|
||||
this.DebugHUDComponent.DebugSettings.CurrentPageIndex
|
||||
) &&
|
||||
IsPageIndexNonNegative(
|
||||
this.DebugHUDComponent.DebugSettings.CurrentPageIndex
|
||||
)
|
||||
) {
|
||||
Out();
|
||||
} else {
|
||||
this.FinishTest(EFunctionalTestResult.Failed, Message);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Movement system component - required for debug HUD initialization
|
||||
* @category Components
|
||||
*/
|
||||
MovementComponent = new AC_Movement();
|
||||
|
||||
/**
|
||||
* Debug HUD system - primary component under test
|
||||
* Tests page navigation state management
|
||||
* @category Components
|
||||
*/
|
||||
DebugHUDComponent = new AC_DebugHUD();
|
||||
|
||||
/**
|
||||
* Toast notification system - required for debug HUD initialization
|
||||
* @category Components
|
||||
*/
|
||||
ToastSystemComponent = new AC_ToastSystem();
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,109 @@
|
|||
// Debug/Tests/FT_DebugPageContentGenerator.ts
|
||||
|
||||
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
||||
import { E_DebugPageID } from '#root/Debug/Enums/E_DebugPageID.ts';
|
||||
import { E_DebugUpdateFunction } from '#root/Debug/Enums/E_DebugUpdateFunction.ts';
|
||||
import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.ts';
|
||||
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
||||
|
||||
/**
|
||||
* Functional Test: Debug HUD Page Content Generation
|
||||
* Validates that all registered debug pages generate non-empty content
|
||||
*/
|
||||
export class FT_DebugPageContentGenerator extends FunctionalTest {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Test entry point - validates content generation for all debug pages
|
||||
* Iterates through all pages and tests their update functions
|
||||
*/
|
||||
EventStartTest(): void {
|
||||
this.DebugHUDComponent.InitializeDebugHUD(
|
||||
this.MovementComponent,
|
||||
this.ToastSystemComponent
|
||||
);
|
||||
|
||||
this.DebugHUDComponent.GetTestData().DebugPages.forEach(
|
||||
(arrayElement, arrayIndex) => {
|
||||
this.UpdatedPage = arrayElement;
|
||||
|
||||
switch (this.UpdatedPage.UpdateFunction) {
|
||||
case E_DebugUpdateFunction.UpdateMovementPage: {
|
||||
this.UpdatedPage = this.DebugHUDComponent.UpdateMovementPage(
|
||||
this.UpdatedPage
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case E_DebugUpdateFunction.UpdateSurfacePage: {
|
||||
this.UpdatedPage = this.DebugHUDComponent.UpdateSurfacePage(
|
||||
this.UpdatedPage
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case E_DebugUpdateFunction.UpdatePerformancePage: {
|
||||
this.UpdatedPage = this.DebugHUDComponent.UpdatePerformancePage(
|
||||
this.UpdatedPage
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.UpdatedPage.Content.length > 0) {
|
||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
`DebugHUD: Page ${arrayIndex + 1} content empty`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Movement system component - required for debug HUD initialization
|
||||
* @category Components
|
||||
*/
|
||||
MovementComponent = new AC_Movement();
|
||||
|
||||
/**
|
||||
* Debug HUD system - primary component under test
|
||||
* Tests page content generation functionality
|
||||
* @category Components
|
||||
*/
|
||||
DebugHUDComponent = new AC_DebugHUD();
|
||||
|
||||
/**
|
||||
* Toast notification system - required for debug HUD initialization
|
||||
* @category Components
|
||||
*/
|
||||
ToastSystemComponent = new AC_ToastSystem();
|
||||
|
||||
/**
|
||||
* Working copy of debug page for content generation testing
|
||||
* Updated during test execution for each page
|
||||
* @category Test State
|
||||
*/
|
||||
UpdatedPage: S_DebugPage = {
|
||||
PageID: E_DebugPageID.MovementInfo,
|
||||
Title: '',
|
||||
Content: '',
|
||||
IsVisible: false,
|
||||
UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage,
|
||||
};
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,95 @@
|
|||
// Debug/Tests/FT_DebugSystem.ts
|
||||
|
||||
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
||||
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { DataTableFunctionLibrary } from '#root/UE/DataTableFunctionLibrary.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
||||
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
||||
|
||||
/**
|
||||
* Functional Test: Debug System Basic Functionality
|
||||
* Validates initialization, component validity, and data table consistency
|
||||
*/
|
||||
export class FT_DebugSystem extends FunctionalTest {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Test entry point - validates basic debug system functionality
|
||||
* Uses nested validation to check initialization, page count, and component validity
|
||||
*/
|
||||
EventStartTest(): void {
|
||||
this.DebugHUDComponent.InitializeDebugHUD(
|
||||
this.MovementComponent,
|
||||
this.ToastSystemComponent
|
||||
);
|
||||
|
||||
if (this.DebugHUDComponent.GetTestData().IsInitialized) {
|
||||
if (
|
||||
DataTableFunctionLibrary.GetDataTableRowNames(
|
||||
this.DebugHUDComponent.GetTestData().DebugDataTable
|
||||
).length === this.DebugHUDComponent.GetTestData().DebugPages.length
|
||||
) {
|
||||
if (SystemLibrary.IsValid(this.MovementComponent)) {
|
||||
if (SystemLibrary.IsValid(this.DebugHUDComponent)) {
|
||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
'DebugHUD component not valid'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
'Debug HUD: Movement component not valid'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
`Debug HUD: Expected ${this.DebugHUDComponent.GetTestData().DebugPages.length} pages, got ${
|
||||
DataTableFunctionLibrary.GetDataTableRowNames(
|
||||
this.DebugHUDComponent.GetTestData().DebugDataTable
|
||||
).length
|
||||
}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
'Debug HUD failed to initialize'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Movement system component - required for debug HUD initialization
|
||||
* @category Components
|
||||
*/
|
||||
MovementComponent = new AC_Movement();
|
||||
|
||||
/**
|
||||
* Debug HUD system - primary component under test
|
||||
* Tests basic system initialization and component validity
|
||||
* @category Components
|
||||
*/
|
||||
DebugHUDComponent = new AC_DebugHUD();
|
||||
|
||||
/**
|
||||
* Toast notification system - required for debug HUD initialization
|
||||
* @category Components
|
||||
*/
|
||||
ToastSystemComponent = new AC_ToastSystem();
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,132 +1,158 @@
|
|||
// Content/Debug/UI/WBP_DebugHUD.ts
|
||||
// Debug/UI/WBP_DebugHUD.ts
|
||||
|
||||
import type {AC_Movement} from "../../Movement/Components/AC_Movement.js";
|
||||
import {TextBox, Widget} from "../../classes.js";
|
||||
import type {Text} from "../../types.js";
|
||||
import {IsValid} from "../../functions.js";
|
||||
import type { AC_Movement } from '#root/Movement/Components/AC_Movement.js';
|
||||
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
||||
import type { Text } from '#root/UE/Text.ts';
|
||||
import { TextBlock } from '#root/UE/TextBlock.ts';
|
||||
import { UserWidget } from '#root/UE/UserWidget.ts';
|
||||
|
||||
/**
|
||||
* Debug HUD Widget for displaying system information
|
||||
* Provides real-time debug information display with multiple pages
|
||||
* Part of the deterministic movement system debug infrastructure
|
||||
*/
|
||||
export class WBP_DebugHUD extends Widget{
|
||||
// Category: "Components"
|
||||
/**
|
||||
* Reference to movement component for accessing debug data
|
||||
* Set by AC_DebugHUD during initialization
|
||||
*/
|
||||
public MovementComponent: AC_Movement | null = null;
|
||||
export class WBP_DebugHUD extends UserWidget {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Category: "PageData"
|
||||
/**
|
||||
* Current page title text
|
||||
* Updated by AC_DebugHUD when switching pages
|
||||
*/
|
||||
private HeaderText: Text = "Debug HUD";
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Category: "PageData"
|
||||
/**
|
||||
* Current page content text
|
||||
* Contains the main debug information for current page
|
||||
* Widget construction event - initializes all text displays
|
||||
* Called automatically when widget is created
|
||||
*/
|
||||
private ContentText: Text = "Text";
|
||||
EventConstruct(): void {
|
||||
this.UpdateHeaderDisplay();
|
||||
this.UpdateContentDisplay();
|
||||
this.UpdateNavigationDisplay();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// FUNCTIONS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Category: "PageData"
|
||||
/**
|
||||
* Navigation instructions text
|
||||
* Shows current page index and navigation controls
|
||||
* Update header text box with current header text
|
||||
* @category Display Updates
|
||||
*/
|
||||
private NavigationText: Text = "Navigation";
|
||||
|
||||
// Category: "UI Components"
|
||||
/**
|
||||
* Text box widget for displaying page header/title
|
||||
* Positioned at top of debug HUD
|
||||
*/
|
||||
private HeaderTextBox = new TextBox();
|
||||
|
||||
// Category: "UI Components"
|
||||
/**
|
||||
* Text box widget for displaying main debug content
|
||||
* Contains multi-line debug information for current page
|
||||
*/
|
||||
private ContentTextBox = new TextBox();
|
||||
|
||||
// Category: "UI Components"
|
||||
/**
|
||||
* Text box widget for displaying navigation help
|
||||
* Shows page counter and keyboard shortcuts
|
||||
*/
|
||||
private NavigationTextBox = new TextBox();
|
||||
|
||||
// Category: "Display Updates"
|
||||
private UpdateHeaderDisplay() {
|
||||
if (IsValid(this.HeaderTextBox)) {
|
||||
private UpdateHeaderDisplay(): void {
|
||||
if (SystemLibrary.IsValid(this.HeaderTextBox)) {
|
||||
this.HeaderTextBox.SetText(this.HeaderText);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Display Updates"
|
||||
/**
|
||||
* Update header text box with current header text
|
||||
* Called automatically when header text changes
|
||||
* @private Internal display update method
|
||||
* Update content text box with current content text
|
||||
* @category Display Updates
|
||||
*/
|
||||
private UpdateContentDisplay() {
|
||||
if (IsValid(this.ContentTextBox)) {
|
||||
private UpdateContentDisplay(): void {
|
||||
if (SystemLibrary.IsValid(this.ContentTextBox)) {
|
||||
this.ContentTextBox.SetText(this.ContentText);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Display Updates"
|
||||
/**
|
||||
* Update navigation text box with current navigation text
|
||||
* Called automatically when navigation text changes
|
||||
* @private Internal display update method
|
||||
* @category Display Updates
|
||||
*/
|
||||
private UpdateNavigationDisplay() {
|
||||
if (IsValid(this.NavigationTextBox)) {
|
||||
private UpdateNavigationDisplay(): void {
|
||||
if (SystemLibrary.IsValid(this.NavigationTextBox)) {
|
||||
this.NavigationTextBox.SetText(this.NavigationText);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Public Interface"
|
||||
/**
|
||||
* Set new header text and update display
|
||||
* @param NewHeaderText - New title text for current debug page
|
||||
* @example
|
||||
* // Set movement page header
|
||||
* SetHeaderText("Movement Constants")
|
||||
* @category Public Interface
|
||||
*/
|
||||
public SetHeaderText(NewHeaderText: string): void {
|
||||
public SetHeaderText(NewHeaderText: Text): void {
|
||||
this.HeaderText = NewHeaderText;
|
||||
this.UpdateHeaderDisplay();
|
||||
}
|
||||
|
||||
// Category: "Public Interface"
|
||||
/**
|
||||
* Set new content text and update display
|
||||
* @param NewContentText - New debug content (supports multi-line text)
|
||||
* @example
|
||||
* // Set movement constants content
|
||||
* SetContentText("Max Speed: 600\nAcceleration: 2000\nFriction: 8.0")
|
||||
* @category Public Interface
|
||||
*/
|
||||
public SetContentText(NewContentText: string): void {
|
||||
public SetContentText(NewContentText: Text): void {
|
||||
this.ContentText = NewContentText;
|
||||
this.UpdateContentDisplay();
|
||||
}
|
||||
|
||||
// Category: "Public Interface"
|
||||
/**
|
||||
* Set new navigation text and update display
|
||||
* @param NewNavigationText - Navigation instructions and page info
|
||||
* @example
|
||||
* // Set navigation with page counter
|
||||
* SetNavigationText("Page 1/3 | PageUp/PageDown - Navigate")
|
||||
* @category Public Interface
|
||||
*/
|
||||
public SetNavigationText(NewNavigationText: string): void {
|
||||
public SetNavigationText(NewNavigationText: Text): void {
|
||||
this.NavigationText = NewNavigationText;
|
||||
this.UpdateNavigationDisplay();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Reference to movement component for accessing debug data
|
||||
* Set by AC_DebugHUD during initialization
|
||||
* @category Components
|
||||
*/
|
||||
public MovementComponent: AC_Movement | null = null;
|
||||
|
||||
/**
|
||||
* Current page title text
|
||||
* Updated by AC_DebugHUD when switching pages
|
||||
* @category PageData
|
||||
*/
|
||||
private HeaderText: Text = 'Debug HUD';
|
||||
|
||||
/**
|
||||
* Current page content text
|
||||
* Contains the main debug information for current page
|
||||
* @category PageData
|
||||
*/
|
||||
private ContentText: Text = 'Text';
|
||||
|
||||
/**
|
||||
* Navigation instructions text
|
||||
* Shows current page index and navigation controls
|
||||
* @category PageData
|
||||
*/
|
||||
private NavigationText: Text = 'Navigation';
|
||||
|
||||
/**
|
||||
* Text box widget for displaying page header/title
|
||||
* Positioned at top of debug HUD
|
||||
* @category UI Components
|
||||
*/
|
||||
private HeaderTextBox = new TextBlock();
|
||||
|
||||
/**
|
||||
* Text box widget for displaying main debug content
|
||||
* Contains multi-line debug information for current page
|
||||
* @category UI Components
|
||||
*/
|
||||
private ContentTextBox = new TextBlock();
|
||||
|
||||
/**
|
||||
* Text box widget for displaying navigation help
|
||||
* Shows page counter and keyboard shortcuts
|
||||
* @category UI Components
|
||||
*/
|
||||
private NavigationTextBox = new TextBlock();
|
||||
}
|
||||
|
|
|
|||
BIN
Content/Debug/UI/WBP_DebugHUD.uasset (Stored with Git LFS)
BIN
Content/Debug/UI/WBP_DebugHUD.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Debug/UI/WBP_TestResult.uasset (Stored with Git LFS)
BIN
Content/Debug/UI/WBP_TestResult.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,5 +1,6 @@
|
|||
// Content/Input/Actions/IA_LeftTrigger.ts
|
||||
// Input/Actions/IA_LeftTrigger.ts
|
||||
|
||||
import type {InputMapping} from "../../types.js";
|
||||
import { InputAction } from '#root/UE/InputAction.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
|
||||
export const IA_LeftTrigger: InputMapping = {}
|
||||
export const IA_LeftTrigger = new InputAction(null, new Name('IA_LeftTrigger'));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
// Input/Actions/IA_Look.ts
|
||||
|
||||
import { InputAction } from '#root/UE/InputAction.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
|
||||
export const IA_Look = new InputAction(null, new Name('IA_Look'));
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// Input/Actions/IA_Move.ts
|
||||
|
||||
import { InputAction } from '#root/UE/InputAction.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
|
||||
export const IA_Move = new InputAction(null, new Name('IA_Move'));
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Content/Input/Actions/IA_NextDebugMode.ts
|
||||
// Input/Actions/IA_NextDebugMode.ts
|
||||
|
||||
import type {InputMapping} from "../../types.js";
|
||||
import { InputAction } from '#root/UE/InputAction.ts';
|
||||
|
||||
export const IA_NextDebugMode: InputMapping = {}
|
||||
export const IA_NextDebugMode = new InputAction(null, 'IA_NextDebugMode');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Content/Input/Actions/IA_PrevDebugMode.ts
|
||||
// Input/Actions/IA_PrevDebugMode.ts
|
||||
|
||||
import type {InputMapping} from "../../types.js";
|
||||
import { InputAction } from '#root/UE/InputAction.ts';
|
||||
|
||||
export const IA_PrevDebugMode: InputMapping = {}
|
||||
export const IA_PrevDebugMode = new InputAction(null, 'IA_PrevDebugMode');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Content/Input/Actions/IA_RightTrigger.ts
|
||||
// Input/Actions/IA_RightTrigger.ts
|
||||
|
||||
import type {InputMapping} from "../../types.js";
|
||||
import { InputAction } from '#root/UE/InputAction.ts';
|
||||
|
||||
export const IA_RightTrigger: InputMapping = {}
|
||||
export const IA_RightTrigger = new InputAction(null, 'IA_RightTrigger');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Content/Input/Actions/IA_ToggleHUD.ts
|
||||
// Input/Actions/IA_ToggleHUD.ts
|
||||
|
||||
import type {InputMapping} from "../../types.js";
|
||||
import { InputAction } from '#root/UE/InputAction.ts';
|
||||
|
||||
export const IA_ToggleHUD: InputMapping = {}
|
||||
export const IA_ToggleHUD = new InputAction(null, 'IA_ToggleHUD');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
// Content/Input/Actions/IA_ToggleVisualDebug.ts
|
||||
// Input/Actions/IA_ToggleVisualDebug.ts
|
||||
|
||||
import type {InputMapping} from "../../types.js";
|
||||
import { InputAction } from '#root/UE/InputAction.ts';
|
||||
|
||||
export const IA_ToggleVisualDebug: InputMapping = {}
|
||||
export const IA_ToggleVisualDebug = new InputAction(
|
||||
null,
|
||||
'IA_ToggleVisualDebug'
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
// Input/Enums/E_InputDeviceType.ts
|
||||
|
||||
export enum E_InputDeviceType {
|
||||
Unknown = 'Unknown',
|
||||
Keyboard = 'Keyboard',
|
||||
Gamepad = 'Gamepad',
|
||||
}
|
||||
|
|
@ -1,18 +1,24 @@
|
|||
// Content/Input/IMC_Default.ts
|
||||
// Input/IMC_Default.ts
|
||||
|
||||
import type {InputMappingContext} from "../types.js";
|
||||
import {IA_LeftTrigger} from "./Actions/IA_LeftTrigger.js";
|
||||
import {IA_RightTrigger} from "./Actions/IA_RightTrigger.js";
|
||||
import {IA_NextDebugMode} from "./Actions/IA_NextDebugMode.js";
|
||||
import {IA_PrevDebugMode} from "./Actions/IA_PrevDebugMode.js";
|
||||
import {IA_ToggleHUD} from "./Actions/IA_ToggleHUD.js";
|
||||
import {IA_ToggleVisualDebug} from "./Actions/IA_ToggleVisualDebug.js";
|
||||
import { IA_LeftTrigger } from '#root/Input/Actions/IA_LeftTrigger.ts';
|
||||
import { IA_Move } from '#root/Input/Actions/IA_Move.ts';
|
||||
import { IA_NextDebugMode } from '#root/Input/Actions/IA_NextDebugMode.ts';
|
||||
import { IA_PrevDebugMode } from '#root/Input/Actions/IA_PrevDebugMode.ts';
|
||||
import { IA_RightTrigger } from '#root/Input/Actions/IA_RightTrigger.ts';
|
||||
import { IA_ToggleHUD } from '#root/Input/Actions/IA_ToggleHUD.ts';
|
||||
import { IA_ToggleVisualDebug } from '#root/Input/Actions/IA_ToggleVisualDebug.ts';
|
||||
import { InputMappingContext } from '#root/UE/InputMappingContext.ts';
|
||||
import type { Key } from '#root/UE/Key.ts';
|
||||
|
||||
export const IMC_Default: InputMappingContext = [
|
||||
IA_LeftTrigger,
|
||||
IA_RightTrigger,
|
||||
IA_NextDebugMode,
|
||||
IA_PrevDebugMode,
|
||||
IA_ToggleHUD,
|
||||
export const IMC_Default = new InputMappingContext();
|
||||
|
||||
IMC_Default.mapKey(IA_LeftTrigger, 'IA_LeftTrigger' as unknown as Key);
|
||||
IMC_Default.mapKey(IA_RightTrigger, 'IA_RightTrigger' as unknown as Key);
|
||||
IMC_Default.mapKey(IA_NextDebugMode, 'IA_NextDebugMode' as unknown as Key);
|
||||
IMC_Default.mapKey(IA_PrevDebugMode, 'IA_PrevDebugMode' as unknown as Key);
|
||||
IMC_Default.mapKey(IA_ToggleHUD, 'IA_ToggleHUD' as unknown as Key);
|
||||
IMC_Default.mapKey(
|
||||
IA_ToggleVisualDebug,
|
||||
]
|
||||
'IA_ToggleVisualDebug' as unknown as Key
|
||||
);
|
||||
IMC_Default.mapKey(IA_Move, 'IA_Move' as unknown as Key);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Content/Levels/TestLevel.ts
|
||||
// Levels/TestLevel.ts
|
||||
|
||||
import {BP_MainCharacter} from "../Blueprints/BP_MainCharacter.js";
|
||||
import { BP_MainCharacter } from '#root/Blueprints/BP_MainCharacter.ts';
|
||||
|
||||
const MainCharacter = new BP_MainCharacter();
|
||||
new BP_MainCharacter();
|
||||
|
|
|
|||
BIN
Content/Levels/TestLevel.umap (Stored with Git LFS)
BIN
Content/Levels/TestLevel.umap (Stored with Git LFS)
Binary file not shown.
|
|
@ -0,0 +1,85 @@
|
|||
// Math/Libraries/BFL_Vectors.ts
|
||||
|
||||
import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts';
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import { MathLibrary } from '#root/UE/MathLibrary.ts';
|
||||
import { Vector } from '#root/UE/Vector.ts';
|
||||
|
||||
/**
|
||||
* Blueprint Function Library: Vector Mathematics
|
||||
* Pure mathematical functions for vector operations and surface angle calculations
|
||||
* Used by movement system for deterministic surface classification
|
||||
*/
|
||||
export class BFL_VectorsClass extends BlueprintFunctionLibrary {
|
||||
constructor(
|
||||
outer: null | BlueprintFunctionLibrary = null,
|
||||
name: string = 'BFL_Vectors'
|
||||
) {
|
||||
super(outer, name);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// FUNCTIONS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Calculate angle between two normalized vectors
|
||||
* @param Vector1 - First normalized vector
|
||||
* @param Vector2 - Second normalized vector
|
||||
* @returns Angle between vectors in radians (0 to π)
|
||||
* @example
|
||||
* // 90° angle between X and Z axes
|
||||
* GetAngleBetweenVectors(new Vector(1,0,0), new Vector(0,0,1)) // returns π/2
|
||||
*/
|
||||
public GetAngleBetweenVectors(Vector1: Vector, Vector2: Vector): Float {
|
||||
/**
|
||||
* Internal calculation using dot product and arccosine
|
||||
*/
|
||||
const CalculateAngleBetweenVectors = (v1: Vector, v2: Vector): Float =>
|
||||
MathLibrary.Acos(MathLibrary.Dot(v1, v2));
|
||||
|
||||
return CalculateAngleBetweenVectors(Vector1, Vector2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate surface normal vector from angle in degrees
|
||||
* @param AngleDegrees - Angle from horizontal in degrees (0-180)
|
||||
* @returns Normalized surface normal vector
|
||||
* @example
|
||||
* // Flat surface (0°)
|
||||
* GetNormalFromAngle(0) // returns Vector(0,0,1)
|
||||
* // Vertical wall (90°)
|
||||
* GetNormalFromAngle(90) // returns Vector(1,0,0)
|
||||
*/
|
||||
public GetNormalFromAngle(AngleDegrees: Float): Vector {
|
||||
/**
|
||||
* Calculate X component using sine of angle
|
||||
*/
|
||||
const CalculateX = (angle: Float): Float =>
|
||||
MathLibrary.Sin(MathLibrary.DegreesToRadians(angle));
|
||||
|
||||
/**
|
||||
* Calculate Z component using cosine of angle
|
||||
*/
|
||||
const CalculateZ = (angle: Float): Float =>
|
||||
MathLibrary.Cos(MathLibrary.DegreesToRadians(angle));
|
||||
|
||||
return new Vector(CalculateX(AngleDegrees), 0, CalculateZ(AngleDegrees));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate angle between surface normal and up vector
|
||||
* @param SurfaceNormal - Normalized surface normal vector
|
||||
* @returns Angle from horizontal plane in radians (0 = flat, π/2 = vertical)
|
||||
* @example
|
||||
* // Flat surface
|
||||
* GetSurfaceAngle(new Vector(0,0,1)) // returns 0
|
||||
* // Vertical wall
|
||||
* GetSurfaceAngle(new Vector(1,0,0)) // returns π/2
|
||||
*/
|
||||
public GetSurfaceAngle(SurfaceNormal: Vector): Float {
|
||||
return this.GetAngleBetweenVectors(SurfaceNormal, new Vector(0, 0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
export const BFL_Vectors = new BFL_VectorsClass();
|
||||
Binary file not shown.
|
|
@ -1,94 +1,24 @@
|
|||
// Content/Movement/Components/AC_Movement.ts
|
||||
// Movement/Components/AC_Movement.ts
|
||||
|
||||
import type {S_MovementConstants} from "../Structs/S_MovementConstants.js";
|
||||
import type {S_AngleThresholds} from "../Structs/S_AngleThresholds.js";
|
||||
import type {Float, Vector} from "../../types.js";
|
||||
import {acos, cos, D2R, Dot, Print, sin} from "../../functions.js";
|
||||
import {E_SurfaceType} from "../Enums/E_SurfaceType.js";
|
||||
import type {S_SurfaceTestCase} from "../Structs/S_SurfaceTestCase.js";
|
||||
import { BFL_Vectors } from '#root/Math/Libraries/BFL_Vectors.ts';
|
||||
import { E_SurfaceType } from '#root/Movement/Enums/E_SurfaceType.ts';
|
||||
import { S_AngleThresholds } from '#root/Movement/Structs/S_AngleThresholds.ts';
|
||||
import type { S_MovementConstants } from '#root/Movement/Structs/S_MovementConstants.ts';
|
||||
import { ActorComponent } from '#root/UE/ActorComponent.ts';
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import { MathLibrary } from '#root/UE/MathLibrary.ts';
|
||||
import type { Vector } from '#root/UE/Vector.ts';
|
||||
|
||||
export class AC_Movement {
|
||||
// === Movement configuration exposed to designers ===
|
||||
// Category: "Movement Config"
|
||||
// Instance editable: true
|
||||
public readonly MovementConstants: S_MovementConstants = {
|
||||
MaxSpeed: 600.0,
|
||||
Acceleration: 2000.0,
|
||||
Friction: 8.0,
|
||||
Gravity: 980.0,
|
||||
}
|
||||
|
||||
// Category: "Movement Config"
|
||||
// Instance editable: true
|
||||
readonly AngleThresholdsDegrees: S_AngleThresholds = {
|
||||
Walkable: 50.0, // degrees
|
||||
SteepSlope: 85.0, // degrees
|
||||
Wall: 95.0, // degrees
|
||||
};
|
||||
// =====================================================
|
||||
|
||||
// Category: "Debug"
|
||||
IsInitialized: boolean = false;
|
||||
|
||||
// Category: "Debug"
|
||||
// Instance editable: true
|
||||
ShowDebugInfo: boolean = true;
|
||||
|
||||
// === Runtime cached values for performance (radians) ===
|
||||
// Category: "InternalCache"
|
||||
AngleThresholdsRads: S_AngleThresholds;
|
||||
// =====================================================
|
||||
|
||||
// Category: "Math"
|
||||
// Pure: true
|
||||
/**
|
||||
* Calculate angle between two normalized vectors
|
||||
* @param Vector1 - First normalized vector
|
||||
* @param Vector2 - Second normalized vector
|
||||
* @returns Angle between vectors in radians (0 to π)
|
||||
* @example
|
||||
* // 90° angle between X and Z axes
|
||||
* GetAngleBetweenVectors([1,0,0], [0,0,1]) // returns π/2
|
||||
* Movement System Component
|
||||
* Core deterministic movement system for 3D platformer
|
||||
* Handles surface classification and movement physics calculations
|
||||
*/
|
||||
GetAngleBetweenVectors(Vector1: Vector, Vector2: Vector) {
|
||||
return acos(Dot(Vector1, Vector2));
|
||||
}
|
||||
export class AC_Movement extends ActorComponent {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// FUNCTIONS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Category: "Math"
|
||||
// Pure: true
|
||||
/**
|
||||
* Calculate angle between surface normal and up vector
|
||||
* @param SurfaceNormal - Normalized surface normal vector
|
||||
* @returns Angle from horizontal plane in radians (0 = flat, π/2 = vertical)
|
||||
* @example
|
||||
* // Flat surface
|
||||
* GetSurfaceAngle([0,0,1]) // returns 0
|
||||
* // Vertical wall
|
||||
* GetSurfaceAngle([1,0,0]) // returns π/2
|
||||
*/
|
||||
GetSurfaceAngle(SurfaceNormal: Vector): Float {
|
||||
return this.GetAngleBetweenVectors(SurfaceNormal, [0.0, 0.0, 1.0]);
|
||||
}
|
||||
|
||||
// Category: "Math"
|
||||
// Pure: true
|
||||
/**
|
||||
* Generate surface normal vector from angle in degrees
|
||||
* @param AngleDegrees - Angle from horizontal in degrees (0-180)
|
||||
* @returns Normalized surface normal vector
|
||||
* @example
|
||||
* // Flat surface (0°)
|
||||
* GenerateNormalFromAngle(0) // returns [0,0,1]
|
||||
* // Vertical wall (90°)
|
||||
* GenerateNormalFromAngle(90) // returns [1,0,0]
|
||||
*/
|
||||
GenerateNormalFromAngle(AngleDegrees: Float): Vector {
|
||||
const AngleRads = D2R(AngleDegrees);
|
||||
return [sin(AngleRads), 0.0, cos(AngleRads)];
|
||||
}
|
||||
|
||||
// Category: "Surface Detection"
|
||||
// Pure: true
|
||||
/**
|
||||
* Classify surface type based on normal vector
|
||||
* @param SurfaceNormal - Normalized surface normal vector
|
||||
|
|
@ -96,140 +26,163 @@ export class AC_Movement {
|
|||
* @returns Surface type classification
|
||||
* @example
|
||||
* // Classify flat ground
|
||||
* ClassifySurface([0,0,1], thresholds) // returns E_SurfaceType.Walkable
|
||||
* ClassifySurface(new Vector(0,0,1), thresholds) // returns E_SurfaceType.Walkable
|
||||
* @pure true
|
||||
* @category Surface Detection
|
||||
*/
|
||||
ClassifySurface(SurfaceNormal: Vector, AngleThresholds: S_AngleThresholds): E_SurfaceType {
|
||||
const SurfaceAngle = this.GetSurfaceAngle(SurfaceNormal);
|
||||
public ClassifySurface(
|
||||
SurfaceNormal: Vector,
|
||||
AngleThresholds: S_AngleThresholds
|
||||
): E_SurfaceType {
|
||||
const SurfaceAngle = BFL_Vectors.GetSurfaceAngle(SurfaceNormal);
|
||||
|
||||
if (SurfaceAngle <= AngleThresholds.Walkable) {
|
||||
/**
|
||||
* Check if angle is within walkable range
|
||||
*/
|
||||
const IsWalkableAngle = (walkableAngle: Float): boolean =>
|
||||
SurfaceAngle <= walkableAngle;
|
||||
|
||||
/**
|
||||
* Check if angle is within steep slope range
|
||||
*/
|
||||
const IsSteepSlopeAngle = (steepSlopeAngle: Float): boolean =>
|
||||
SurfaceAngle <= steepSlopeAngle;
|
||||
|
||||
/**
|
||||
* Check if angle is within wall range
|
||||
*/
|
||||
const IsWallAngle = (wallAngle: Float): boolean =>
|
||||
SurfaceAngle <= wallAngle;
|
||||
|
||||
if (IsWalkableAngle(AngleThresholds.Walkable)) {
|
||||
return E_SurfaceType.Walkable;
|
||||
} else if (SurfaceAngle <= AngleThresholds.SteepSlope) {
|
||||
} else if (IsSteepSlopeAngle(AngleThresholds.SteepSlope)) {
|
||||
return E_SurfaceType.SteepSlope;
|
||||
} else if (SurfaceAngle <= AngleThresholds.Wall) {
|
||||
} else if (IsWallAngle(AngleThresholds.Wall)) {
|
||||
return E_SurfaceType.Wall;
|
||||
} else {
|
||||
return E_SurfaceType.Ceiling;
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Surface Detection"
|
||||
// Pure: true
|
||||
/**
|
||||
* Check if surface allows normal walking movement
|
||||
* @param SurfaceType - Surface type to check
|
||||
* @returns True if surface is walkable
|
||||
* @pure true
|
||||
* @category Surface Detection
|
||||
*/
|
||||
IsSurfaceWalkable(SurfaceType: E_SurfaceType): boolean {
|
||||
private IsSurfaceWalkable(SurfaceType: E_SurfaceType): boolean {
|
||||
return SurfaceType === E_SurfaceType.Walkable;
|
||||
}
|
||||
|
||||
// Category: "Surface Detection"
|
||||
// Pure: true
|
||||
/**
|
||||
* Check if surface causes sliding behavior
|
||||
* @param SurfaceType - Surface type to check
|
||||
* @returns True if surface is steep slope
|
||||
* @pure true
|
||||
* @category Surface Detection
|
||||
*/
|
||||
IsSurfaceSteep(SurfaceType: E_SurfaceType): boolean {
|
||||
private IsSurfaceSteep(SurfaceType: E_SurfaceType): boolean {
|
||||
return SurfaceType === E_SurfaceType.SteepSlope;
|
||||
}
|
||||
|
||||
// Category: "Surface Detection"
|
||||
// Pure: true
|
||||
/**
|
||||
* Check if surface blocks movement (collision)
|
||||
* @param SurfaceType - Surface type to check
|
||||
* @returns True if surface is a wall
|
||||
* @pure true
|
||||
* @category Surface Detection
|
||||
*/
|
||||
IsSurfaceWall(SurfaceType: E_SurfaceType): boolean {
|
||||
private IsSurfaceWall(SurfaceType: E_SurfaceType): boolean {
|
||||
return SurfaceType === E_SurfaceType.Wall;
|
||||
}
|
||||
|
||||
// Category: "Surface Detection"
|
||||
// Pure: true
|
||||
/**
|
||||
* Check if surface is overhead (ceiling)
|
||||
* @param SurfaceType - Surface type to check
|
||||
* @returns True if surface is ceiling
|
||||
* @pure true
|
||||
* @category Surface Detection
|
||||
*/
|
||||
IsSurfaceCeiling(SurfaceType: E_SurfaceType): boolean {
|
||||
private IsSurfaceCeiling(SurfaceType: E_SurfaceType): boolean {
|
||||
return SurfaceType === E_SurfaceType.Ceiling;
|
||||
}
|
||||
|
||||
// Category: "Surface Detection"
|
||||
// Pure: true
|
||||
/**
|
||||
* Check if no surface detected (airborne state)
|
||||
* @param SurfaceType - Surface type to check
|
||||
* @returns True if no surface contact
|
||||
* @pure true
|
||||
* @category Surface Detection
|
||||
*/
|
||||
IsSurfaceNone(SurfaceType: E_SurfaceType): boolean {
|
||||
private IsSurfaceNone(SurfaceType: E_SurfaceType): boolean {
|
||||
return SurfaceType === E_SurfaceType.None;
|
||||
}
|
||||
|
||||
// Category: "Debug"
|
||||
PrintDebugInfo() {
|
||||
Print('=== Movement System Initialized ===');
|
||||
Print(`Walkable: ≤ ${this.AngleThresholdsDegrees.Walkable}°, Steep: ${this.AngleThresholdsDegrees.Walkable}°-${this.AngleThresholdsDegrees.SteepSlope}°, Wall: ${this.AngleThresholdsDegrees.SteepSlope}°-${this.AngleThresholdsDegrees.Wall}°, Ceiling: >${this.AngleThresholdsDegrees.Wall}°`);
|
||||
Print(`Max Speed: ${this.MovementConstants.MaxSpeed}, Acceleration: ${this.MovementConstants.Acceleration}, Friction: ${this.MovementConstants.Friction}, Gravity: ${this.MovementConstants.Gravity}`);
|
||||
Print('==================================');
|
||||
}
|
||||
|
||||
// Category: "Debug"
|
||||
/**
|
||||
* Run comprehensive surface classification test suite
|
||||
* @returns True if all tests pass
|
||||
*/
|
||||
TestSurfaceClassification() {
|
||||
let AllTestsPassed: boolean = true;
|
||||
|
||||
const TestCases: S_SurfaceTestCase[] = [
|
||||
{ AngleDegrees: 0.0, ExpectedType: E_SurfaceType.Walkable, Description: "Flat surface" },
|
||||
{ AngleDegrees: 25.0, ExpectedType: E_SurfaceType.Walkable, Description: "Gentle slope" },
|
||||
{ AngleDegrees: 49.0, ExpectedType: E_SurfaceType.Walkable, Description: "Max walkable" },
|
||||
{ AngleDegrees: 51.0, ExpectedType: E_SurfaceType.SteepSlope, Description: "Steep slope" },
|
||||
{ AngleDegrees: 70.0, ExpectedType: E_SurfaceType.SteepSlope, Description: "Very steep" },
|
||||
{ AngleDegrees: 84.0, ExpectedType: E_SurfaceType.SteepSlope, Description: "Max steep" },
|
||||
{ AngleDegrees: 90.0, ExpectedType: E_SurfaceType.Wall, Description: "Vertical wall" },
|
||||
{ AngleDegrees: 94.0, ExpectedType: E_SurfaceType.Wall, Description: "Max wall" },
|
||||
{ AngleDegrees: 120.0, ExpectedType: E_SurfaceType.Ceiling, Description: "Overhang" },
|
||||
{ AngleDegrees: 180.0, ExpectedType: E_SurfaceType.Ceiling, Description: "Ceiling" }
|
||||
];
|
||||
|
||||
for (let i = 0; i < TestCases.length; i++) {
|
||||
const testCase = TestCases[i];
|
||||
const normal = this.GenerateNormalFromAngle(testCase.AngleDegrees);
|
||||
const result = this.ClassifySurface(normal, this.AngleThresholdsRads);
|
||||
|
||||
if (result === testCase.ExpectedType) {
|
||||
Print(`✅ Test ${i+1} PASS: ${testCase.Description} (${testCase.AngleDegrees}°) = ${testCase.ExpectedType}`);
|
||||
} else {
|
||||
Print(`❌ Test ${i+1} FAIL: ${testCase.Description} (${testCase.AngleDegrees}°) expected ${testCase.ExpectedType}, got ${result}`);
|
||||
AllTestsPassed = false;
|
||||
}
|
||||
}
|
||||
|
||||
return AllTestsPassed;
|
||||
}
|
||||
|
||||
// Category: "System"
|
||||
/**
|
||||
* Initialize movement system with angle conversion and testing
|
||||
* Initialize movement system with angle conversion
|
||||
* Converts degree thresholds to radians for runtime performance
|
||||
* Runs automated tests if debug mode enabled
|
||||
* @category System
|
||||
*/
|
||||
InitializeMovementSystem() {
|
||||
public InitializeMovementSystem(): void {
|
||||
this.IsInitialized = true;
|
||||
|
||||
this.AngleThresholdsRads = {
|
||||
Walkable: D2R(this.AngleThresholdsDegrees.Walkable),
|
||||
SteepSlope: D2R(this.AngleThresholdsDegrees.SteepSlope),
|
||||
Wall: D2R(this.AngleThresholdsDegrees.Wall),
|
||||
Walkable: MathLibrary.DegreesToRadians(
|
||||
this.AngleThresholdsDegrees.Walkable
|
||||
),
|
||||
SteepSlope: MathLibrary.DegreesToRadians(
|
||||
this.AngleThresholdsDegrees.SteepSlope
|
||||
),
|
||||
Wall: MathLibrary.DegreesToRadians(this.AngleThresholdsDegrees.Wall),
|
||||
};
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Movement physics constants
|
||||
* Controls speed, acceleration, friction, and gravity values
|
||||
* @category Movement Config
|
||||
* @instanceEditable true
|
||||
*/
|
||||
public readonly MovementConstants: S_MovementConstants = {
|
||||
MaxSpeed: 600.0,
|
||||
Acceleration: 2000.0,
|
||||
Friction: 8.0,
|
||||
Gravity: 980.0,
|
||||
};
|
||||
|
||||
if (this.ShowDebugInfo) {
|
||||
this.PrintDebugInfo();
|
||||
this.TestSurfaceClassification();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Surface classification angle thresholds in degrees
|
||||
* Walkable ≤50°, SteepSlope ≤85°, Wall ≤95°, Ceiling >95°
|
||||
* @category Movement Config
|
||||
* @instanceEditable true
|
||||
*/
|
||||
public readonly AngleThresholdsDegrees: S_AngleThresholds = {
|
||||
Walkable: 50.0,
|
||||
SteepSlope: 85.0,
|
||||
Wall: 95.0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Runtime cached angle thresholds in radians
|
||||
* Converted from degrees during initialization for performance
|
||||
* @category Internal Cache
|
||||
*/
|
||||
private AngleThresholdsRads: S_AngleThresholds = {
|
||||
Walkable: 0.0,
|
||||
SteepSlope: 0.0,
|
||||
Wall: 0.0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Flag indicating if movement system has been initialized
|
||||
* Ensures angle thresholds are converted before use
|
||||
* @category Debug
|
||||
*/
|
||||
public IsInitialized = false;
|
||||
}
|
||||
|
|
|
|||
BIN
Content/Movement/Components/AC_Movement.uasset (Stored with Git LFS)
BIN
Content/Movement/Components/AC_Movement.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,9 +1,9 @@
|
|||
// Content/Movement/Enums/E_SurfaceType.ts
|
||||
// Movement/Enums/E_SurfaceType.ts
|
||||
|
||||
export enum E_SurfaceType {
|
||||
None = "None",
|
||||
Walkable = "Walkable",
|
||||
SteepSlope = "SteepSlope",
|
||||
Wall = "Wall",
|
||||
Ceiling = "Ceiling"
|
||||
None = 'None',
|
||||
Walkable = 'Walkable',
|
||||
SteepSlope = 'SteepSlope',
|
||||
Wall = 'Wall',
|
||||
Ceiling = 'Ceiling',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// Content/Movement/Structs/S_AngleThresholds.ts
|
||||
// Movement/Structs/S_AngleThresholds.ts
|
||||
|
||||
import type {Float} from "../../types.js";
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
|
||||
export interface S_AngleThresholds {
|
||||
Walkable: Float,
|
||||
SteepSlope: Float,
|
||||
Wall: Float,
|
||||
Walkable: Float;
|
||||
SteepSlope: Float;
|
||||
Wall: Float;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Content/Movement/Structs/S_MovementConstants.ts
|
||||
// Movement/Structs/S_MovementConstants.ts
|
||||
|
||||
import type {Float} from "../../types.js";
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
|
||||
export interface S_MovementConstants {
|
||||
MaxSpeed: Float;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Content/Movement/Structs/S_SurfaceTestCase.ts
|
||||
// Movement/Structs/S_SurfaceTestCase.ts
|
||||
|
||||
import type {Float} from "../../types.js";
|
||||
import type {E_SurfaceType} from "../Enums/E_SurfaceType.js";
|
||||
import type { E_SurfaceType } from '#root/Movement/Enums/E_SurfaceType.ts';
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
|
||||
export interface S_SurfaceTestCase {
|
||||
AngleDegrees: Float;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
// Movement/Tests/FT_SurfaceClassification.ts
|
||||
|
||||
import { BFL_Vectors } from '#root/Math/Libraries/BFL_Vectors.ts';
|
||||
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||
import { E_SurfaceType } from '#root/Movement/Enums/E_SurfaceType.ts';
|
||||
import { S_AngleThresholds } from '#root/Movement/Structs/S_AngleThresholds.ts';
|
||||
import type { S_SurfaceTestCase } from '#root/Movement/Structs/S_SurfaceTestCase.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
||||
import { MathLibrary } from '#root/UE/MathLibrary.ts';
|
||||
|
||||
/**
|
||||
* Functional Test: Surface Classification System
|
||||
* Tests angle-based surface type detection across all boundary conditions
|
||||
* Validates Walkable/SteepSlope/Wall/Ceiling classification accuracy
|
||||
*/
|
||||
export class FT_SurfaceClassification extends FunctionalTest {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Test preparation - converts angle thresholds to radians
|
||||
* Called before main test execution to prepare test data
|
||||
*/
|
||||
EventPrepareTest(): void {
|
||||
this.AngleThresholdsRads = {
|
||||
Walkable: MathLibrary.DegreesToRadians(
|
||||
this.MovementComponent.AngleThresholdsDegrees.Walkable
|
||||
),
|
||||
SteepSlope: MathLibrary.DegreesToRadians(
|
||||
this.MovementComponent.AngleThresholdsDegrees.SteepSlope
|
||||
),
|
||||
Wall: MathLibrary.DegreesToRadians(
|
||||
this.MovementComponent.AngleThresholdsDegrees.Wall
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Test execution - validates surface classification for all test cases
|
||||
* Tests boundary conditions and edge cases for each surface type
|
||||
*/
|
||||
EventStartTest(): void {
|
||||
this.TestCases.forEach(
|
||||
({ AngleDegrees, ExpectedType, Description }, arrayIndex) => {
|
||||
const surfaceType = this.MovementComponent.ClassifySurface(
|
||||
BFL_Vectors.GetNormalFromAngle(AngleDegrees),
|
||||
this.AngleThresholdsRads
|
||||
);
|
||||
|
||||
if (surfaceType === ExpectedType) {
|
||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
`Movement Component test ${arrayIndex + 1} FAIL: ${Description} (${AngleDegrees}°) expected ${ExpectedType}, got ${surfaceType}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Movement system component - provides surface classification functionality
|
||||
* @category Components
|
||||
*/
|
||||
private MovementComponent = new AC_Movement();
|
||||
|
||||
/**
|
||||
* Comprehensive test cases covering all surface type boundaries
|
||||
* Tests edge cases and typical angles for each classification
|
||||
* @category Test Data
|
||||
*/
|
||||
private TestCases: S_SurfaceTestCase[] = [
|
||||
{
|
||||
AngleDegrees: 0.0,
|
||||
ExpectedType: E_SurfaceType.Walkable,
|
||||
Description: 'Flat surface',
|
||||
},
|
||||
{
|
||||
AngleDegrees: 25.0,
|
||||
ExpectedType: E_SurfaceType.Walkable,
|
||||
Description: 'Gentle slope',
|
||||
},
|
||||
{
|
||||
AngleDegrees: 49.0,
|
||||
ExpectedType: E_SurfaceType.Walkable,
|
||||
Description: 'Max walkable',
|
||||
},
|
||||
{
|
||||
AngleDegrees: 51.0,
|
||||
ExpectedType: E_SurfaceType.SteepSlope,
|
||||
Description: 'Steep slope',
|
||||
},
|
||||
{
|
||||
AngleDegrees: 70.0,
|
||||
ExpectedType: E_SurfaceType.SteepSlope,
|
||||
Description: 'Very steep',
|
||||
},
|
||||
{
|
||||
AngleDegrees: 84.0,
|
||||
ExpectedType: E_SurfaceType.SteepSlope,
|
||||
Description: 'Max steep',
|
||||
},
|
||||
{
|
||||
AngleDegrees: 90.0,
|
||||
ExpectedType: E_SurfaceType.Wall,
|
||||
Description: 'Vertical wall',
|
||||
},
|
||||
{
|
||||
AngleDegrees: 94.0,
|
||||
ExpectedType: E_SurfaceType.Wall,
|
||||
Description: 'Max wall',
|
||||
},
|
||||
{
|
||||
AngleDegrees: 120.0,
|
||||
ExpectedType: E_SurfaceType.Ceiling,
|
||||
Description: 'Overhang',
|
||||
},
|
||||
{
|
||||
AngleDegrees: 180.0,
|
||||
ExpectedType: E_SurfaceType.Ceiling,
|
||||
Description: 'Ceiling',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Runtime cached angle thresholds in radians
|
||||
* Converted during preparation for accurate classification testing
|
||||
* @category Test State
|
||||
*/
|
||||
private AngleThresholdsRads: S_AngleThresholds = {
|
||||
Walkable: 0.0,
|
||||
SteepSlope: 0.0,
|
||||
Wall: 0.0,
|
||||
};
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,271 @@
|
|||
// Toasts/Components/AC_ToastSystem.ts
|
||||
|
||||
import type { S_ToastMessage } from '#root/Toasts/Structs/S_ToastMessage.ts';
|
||||
import type { S_ToastSettings } from '#root/Toasts/Structs/S_ToastSettings.ts';
|
||||
import type { WBP_Toast } from '#root/Toasts/UI/WBP_Toast.ts';
|
||||
import { WBP_ToastContainer } from '#root/Toasts/UI/WBP_ToastContainer.ts';
|
||||
import { ActorComponent } from '#root/UE/ActorComponent.ts';
|
||||
import { CreateWidget } from '#root/UE/CteateWidget.ts';
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import type { Integer } from '#root/UE/Integer.ts';
|
||||
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
||||
import type { Text } from '#root/UE/Text.ts';
|
||||
import { UEArray } from '#root/UE/UEArray.ts';
|
||||
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
||||
|
||||
/**
|
||||
* Toast Notification System Component
|
||||
* Manages temporary status messages with automatic positioning and lifecycle
|
||||
* Provides visual feedback for debug operations and system events
|
||||
*/
|
||||
export class AC_ToastSystem extends ActorComponent {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// FUNCTIONS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Check if toast system should process updates this frame
|
||||
* @returns True if system is initialized and enabled
|
||||
* @pure
|
||||
* @category Toast Management
|
||||
*/
|
||||
private ShouldProcessToasts(): boolean {
|
||||
/**
|
||||
* Validate system state and settings
|
||||
*/
|
||||
const function1 = (isEnabled: boolean): boolean =>
|
||||
this.IsInitialized && isEnabled;
|
||||
|
||||
return function1(this.ToastSettings.IsEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove expired toasts from the system
|
||||
* @category Toast Lifecycle
|
||||
*/
|
||||
private RemoveExpiredToasts(): void {
|
||||
let i = 0;
|
||||
|
||||
/**
|
||||
* Check if there are more toasts to process
|
||||
*/
|
||||
const HasMoreToastsToCheck = (activeToastsLength: Integer): boolean =>
|
||||
i < activeToastsLength;
|
||||
|
||||
while (HasMoreToastsToCheck(this.ActiveToasts.length)) {
|
||||
/**
|
||||
* Check if toast has exceeded its display duration
|
||||
*/
|
||||
const IsToastExpired = (
|
||||
currentTime: Float,
|
||||
createdTime: Float,
|
||||
duration: Float
|
||||
): boolean => currentTime - createdTime > duration;
|
||||
|
||||
const el = this.ActiveToasts.Get(i);
|
||||
|
||||
if (
|
||||
IsToastExpired(
|
||||
SystemLibrary.GetGameTimeInSeconds(),
|
||||
el.CreatedTime,
|
||||
el.Duration
|
||||
)
|
||||
) {
|
||||
const widget = this.ToastWidgets.Get(i);
|
||||
|
||||
if (SystemLibrary.IsValid(widget)) {
|
||||
if (SystemLibrary.IsValid(this.ToastContainer)) {
|
||||
this.ToastContainer.RemoveToast(widget);
|
||||
}
|
||||
}
|
||||
|
||||
this.ActiveToasts.RemoveIndex(i);
|
||||
this.ToastWidgets.RemoveIndex(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce maximum visible toasts limit
|
||||
* Removes oldest toasts when limit is exceeded
|
||||
* @category Toast Lifecycle
|
||||
*/
|
||||
private EnforceToastLimit(): void {
|
||||
/**
|
||||
* Check if current toast count exceeds maximum allowed
|
||||
*/
|
||||
const ExceedsToastLimit = (
|
||||
activeToastsLength: Integer,
|
||||
maxVisibleToasts: Integer
|
||||
): boolean => activeToastsLength >= maxVisibleToasts;
|
||||
|
||||
while (
|
||||
ExceedsToastLimit(
|
||||
this.ActiveToasts.length,
|
||||
this.ToastSettings.MaxVisibleToasts
|
||||
)
|
||||
) {
|
||||
const widget = this.ToastWidgets.Get(0);
|
||||
|
||||
if (SystemLibrary.IsValid(widget)) {
|
||||
if (SystemLibrary.IsValid(this.ToastContainer)) {
|
||||
this.ToastContainer.RemoveToast(widget);
|
||||
}
|
||||
}
|
||||
|
||||
this.ActiveToasts.RemoveIndex(0);
|
||||
this.ToastWidgets.RemoveIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and display a new toast message
|
||||
* @param Message - Text to display in the toast
|
||||
* @param Type - Type of toast (affects color and styling)
|
||||
* @param Duration - How long to display toast (optional, uses default)
|
||||
* @returns Toast ID for tracking
|
||||
* @example
|
||||
* // Create success toast
|
||||
* ShowToast("Test passed!", E_MessageType.Success, 2.0)
|
||||
* // Create error toast with default duration
|
||||
* ShowToast("Test failed!", E_MessageType.Error)
|
||||
* @category Toast Creation
|
||||
*/
|
||||
public ShowToast(
|
||||
Message: Text = '',
|
||||
Type: E_MessageType = E_MessageType.Info,
|
||||
Duration: Float = 0
|
||||
): Integer {
|
||||
if (this.ShouldProcessToasts()) {
|
||||
const toastDuration =
|
||||
Duration !== 0 ? Duration : this.ToastSettings.DefaultDuration;
|
||||
|
||||
const currentTime = SystemLibrary.GetGameTimeInSeconds();
|
||||
|
||||
const newToast: S_ToastMessage = {
|
||||
ID: this.NextToastID++,
|
||||
Message,
|
||||
Type,
|
||||
Duration: toastDuration,
|
||||
CreatedTime: currentTime,
|
||||
};
|
||||
|
||||
this.EnforceToastLimit();
|
||||
|
||||
if (SystemLibrary.IsValid(this.ToastContainer)) {
|
||||
const toastWidget = this.ToastContainer.AddToast(Message, Type);
|
||||
|
||||
if (SystemLibrary.IsValid(toastWidget)) {
|
||||
this.ActiveToasts.Add(newToast);
|
||||
this.ToastWidgets.Add(toastWidget);
|
||||
this.LogToConsole(Type, Message);
|
||||
return newToast.ID;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.LogToConsole(Type, Message);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main update loop for toast system
|
||||
* Removes expired toasts automatically
|
||||
* @category System Main Loop
|
||||
*/
|
||||
public UpdateToastSystem(): void {
|
||||
if (this.ShouldProcessToasts()) {
|
||||
this.RemoveExpiredToasts();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get toast widgets for testing purposes
|
||||
* @returns Array of active toast widgets
|
||||
* @category Testing
|
||||
*/
|
||||
public GetTestData(): UEArray<WBP_Toast> {
|
||||
return this.ToastWidgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize toast system with container widget
|
||||
* @example
|
||||
* // Initialize toast system in main character
|
||||
* this.ToastSystemComponent.InitializeToastSystem();
|
||||
* @category System Setup
|
||||
*/
|
||||
public InitializeToastSystem(): void {
|
||||
this.ToastContainer = CreateWidget(WBP_ToastContainer);
|
||||
this.ToastContainer.InitializeContainer();
|
||||
this.IsInitialized = true;
|
||||
this.NextToastID = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log toast message to console if enabled
|
||||
* @param Type - Message type for formatting
|
||||
* @param Message - Message text to log
|
||||
* @category System Utilities
|
||||
*/
|
||||
public LogToConsole(Type: E_MessageType, Message: Text): void {
|
||||
if (this.ToastSettings.AlsoLogToConsole) {
|
||||
SystemLibrary.PrintString(`[${Type}] ${Message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Toast system configuration settings
|
||||
* Controls capacity, duration, and logging behavior
|
||||
* @category System Config
|
||||
* @instanceEditable true
|
||||
*/
|
||||
public ToastSettings: S_ToastSettings = {
|
||||
MaxVisibleToasts: 5,
|
||||
DefaultDuration: 3.0,
|
||||
AlsoLogToConsole: true,
|
||||
IsEnabled: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* System initialization state flag
|
||||
* Set to true after successful InitializeToastSystem call
|
||||
* @category System State
|
||||
*/
|
||||
private IsInitialized: boolean = false;
|
||||
|
||||
/**
|
||||
* Next unique ID for new toasts
|
||||
* Incremented for each new toast
|
||||
* @category System State
|
||||
*/
|
||||
private NextToastID: Integer = 1;
|
||||
|
||||
/**
|
||||
* Toast container widget that handles positioning and layout
|
||||
* @category Container Management
|
||||
*/
|
||||
private ToastContainer: WBP_ToastContainer | null = null;
|
||||
|
||||
/**
|
||||
* Array of currently active toast messages
|
||||
* Contains all visible toasts with metadata
|
||||
* @category Toast Management
|
||||
*/
|
||||
private ActiveToasts: UEArray<S_ToastMessage> = new UEArray([]);
|
||||
|
||||
/**
|
||||
* Array of toast widget instances
|
||||
* Corresponds to ActiveToasts array for UI display
|
||||
* @category Widget Management
|
||||
*/
|
||||
private ToastWidgets: UEArray<WBP_Toast> = new UEArray([]);
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,192 +0,0 @@
|
|||
// Content/Toasts/Components/AC_ToastSystem.ts
|
||||
|
||||
// TODO: Написать тесты
|
||||
|
||||
import {AddToArray, CreateWidget, GetGameTimeInSeconds, IsValid, Print, RemoveIndexFromArray} from "../../functions.js";
|
||||
import {WBP_ToastContainer} from "../UI/WBP_ToastContainer.js";
|
||||
import type {Float, Integer, Text} from "../../types.js";
|
||||
import {E_MessageType} from "../../UI/Enums/E_MessageType.js";
|
||||
import type {S_ToastMessage} from "../Structs/S_ToastMessage.js";
|
||||
import type {S_ToastSettings} from "../Structs/S_ToastSettings.js";
|
||||
import type {WBP_Toast} from "../UI/WBP_Toast.js";
|
||||
|
||||
export class AC_ToastSystem {
|
||||
// Category: "System Configuration"
|
||||
// Instance Editable: true
|
||||
/**
|
||||
* Toast system configuration settings
|
||||
* Simplified - no positioning settings needed
|
||||
* Instance editable for designer customization
|
||||
*/
|
||||
public ToastSettings: S_ToastSettings = {
|
||||
MaxVisibleToasts: 5,
|
||||
DefaultDuration: 3.0,
|
||||
AlsoLogToConsole: true,
|
||||
IsEnabled: true
|
||||
};
|
||||
|
||||
// Category: "System State"
|
||||
/**
|
||||
* System initialization state flag
|
||||
* Set to true after successful InitializeToastSystem call
|
||||
*/
|
||||
private IsInitialized: boolean = false;
|
||||
|
||||
// Category: "System State"
|
||||
/**
|
||||
* Next unique ID for new toasts
|
||||
* Incremented for each new toast
|
||||
*/
|
||||
private NextToastID: Integer = 1;
|
||||
|
||||
// Category: "Container Management"
|
||||
/**
|
||||
* Toast container widget that handles all positioning
|
||||
* Replaces manual position management
|
||||
*/
|
||||
private ToastContainer: WBP_ToastContainer | null = null;
|
||||
|
||||
// Category: "Toast Management"
|
||||
/**
|
||||
* Array of currently active toast messages
|
||||
* Contains all visible toasts
|
||||
*/
|
||||
private ActiveToasts: S_ToastMessage[] = [];
|
||||
|
||||
// Category: "Widget Management"
|
||||
/**
|
||||
* Array of toast widget instances
|
||||
* Corresponds to ActiveToasts array
|
||||
*/
|
||||
private ToastWidgets: WBP_Toast[] = [];
|
||||
|
||||
// Category: "Toast Management"
|
||||
// Pure: true
|
||||
/**
|
||||
* Check if toast system should process updates this frame
|
||||
* @returns True if system is initialized and enabled
|
||||
* @pure Function has no side effects
|
||||
*/
|
||||
private ShouldProcessToasts(): boolean {
|
||||
return this.IsInitialized && this.ToastSettings.IsEnabled && IsValid(this.ToastContainer);
|
||||
}
|
||||
|
||||
// Category: "Toast Lifecycle"
|
||||
/**
|
||||
* Remove expired toasts from the system
|
||||
* @private Internal cleanup method
|
||||
*/
|
||||
private RemoveExpiredToasts(): void {
|
||||
let i = 0;
|
||||
|
||||
while (i < this.ActiveToasts.length) {
|
||||
if ((GetGameTimeInSeconds() - this.ActiveToasts[i].CreatedTime) > this.ActiveToasts[i].Duration) {
|
||||
if (IsValid(this.ToastWidgets[i]) && IsValid(this.ToastContainer)) {
|
||||
this.ToastContainer.RemoveToast(this.ToastWidgets[i]);
|
||||
}
|
||||
|
||||
RemoveIndexFromArray(this.ActiveToasts, i);
|
||||
RemoveIndexFromArray(this.ToastWidgets, i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Toast Lifecycle"
|
||||
/**
|
||||
* Enforce maximum visible toasts limit
|
||||
* Uses container's capacity management
|
||||
* @private Internal management method
|
||||
*/
|
||||
private EnforceToastLimit(): void {
|
||||
while(this.ActiveToasts.length >= this.ToastSettings.MaxVisibleToasts) {
|
||||
if (IsValid(this.ToastWidgets[0])) {
|
||||
this.ToastContainer.RemoveToast(this.ToastWidgets[0]);
|
||||
}
|
||||
|
||||
RemoveIndexFromArray(this.ActiveToasts, 0);
|
||||
RemoveIndexFromArray(this.ToastWidgets, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Toast Creation"
|
||||
/**
|
||||
* Create and display a new toast message
|
||||
* Uses container for automatic positioning
|
||||
* @param Message - Text to display in the toast
|
||||
* @param Type - Type of toast (affects color and styling)
|
||||
* @param Duration - How long to display toast (optional, uses default)
|
||||
* @returns Toast ID for tracking
|
||||
* @public Main interface for creating toasts
|
||||
* @example
|
||||
* // Create success toast
|
||||
* ShowToast("Test passed!", E_ToastType.Success, 2.0)
|
||||
* // Create error toast with default duration
|
||||
* ShowToast("Test failed!", E_ToastType.Error)
|
||||
*/
|
||||
public ShowToast(Message: Text, Type: E_MessageType, Duration?: Float): Integer {
|
||||
if (this.ShouldProcessToasts()) {
|
||||
const toastDuration = Duration === 0 ? this.ToastSettings.DefaultDuration : Duration;
|
||||
const currentTime = GetGameTimeInSeconds();
|
||||
const newToast: S_ToastMessage = {
|
||||
ID: this.NextToastID++,
|
||||
Message: Message,
|
||||
Type: Type,
|
||||
Duration: toastDuration,
|
||||
CreatedTime: currentTime,
|
||||
}
|
||||
|
||||
this.EnforceToastLimit();
|
||||
|
||||
const toastWidget = this.ToastContainer.AddToast(Message, Type);
|
||||
|
||||
if (IsValid(toastWidget)) {
|
||||
AddToArray(this.ActiveToasts, newToast);
|
||||
AddToArray(this.ToastWidgets, toastWidget);
|
||||
|
||||
if (this.ToastSettings.AlsoLogToConsole) {
|
||||
Print(`[${Type}] ${Message}`, false, true);
|
||||
}
|
||||
|
||||
return newToast.ID;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ToastSettings.AlsoLogToConsole) {
|
||||
Print(`[${Type}] ${Message}`, false, true);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Category: "System Main Loop"
|
||||
/**
|
||||
* Simplified update loop for toast system
|
||||
* Container handles positioning automatically
|
||||
* @public Called by BP_MainCharacter or game framework
|
||||
*/
|
||||
public UpdateToastSystem(): void {
|
||||
if (this.ShouldProcessToasts()) {
|
||||
this.RemoveExpiredToasts();
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "System Setup"
|
||||
/**
|
||||
* Initialize toast system with container
|
||||
* @public Called by BP_MainCharacter during initialization
|
||||
* @example
|
||||
* // Initialize toast system in main character
|
||||
* this.ToastSystemComponent.InitializeToastSystem();
|
||||
*/
|
||||
public InitializeToastSystem(): void {
|
||||
this.ToastContainer = CreateWidget(new WBP_ToastContainer);
|
||||
this.ToastContainer.InitializeContainer();
|
||||
this.IsInitialized = true;
|
||||
this.NextToastID = 1;
|
||||
this.ShowToast(("Toast System Initialized" as Text), E_MessageType.Info);
|
||||
}
|
||||
}
|
||||
BIN
Content/Toasts/Compontents/AC_ToastSystem.uasset (Stored with Git LFS)
BIN
Content/Toasts/Compontents/AC_ToastSystem.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,7 +1,9 @@
|
|||
// Content/Toasts/Structs/S_ToastMessage.ts
|
||||
// Toasts/Structs/S_ToastMessage.ts
|
||||
|
||||
import type {Float, Integer, Text} from "../../types.js";
|
||||
import type {E_MessageType} from "../../UI/Enums/E_MessageType.js";
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import type { Integer } from '#root/UE/Integer.ts';
|
||||
import type { Text } from '#root/UE/Text.ts';
|
||||
import type { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
||||
|
||||
export interface S_ToastMessage {
|
||||
ID: Integer;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Content/Toasts/Structs/S_ToastSettings.ts
|
||||
// Toasts/Structs/S_ToastSettings.ts
|
||||
|
||||
import type {Float, Integer} from "../../types.js";
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import type { Integer } from '#root/UE/Integer.ts';
|
||||
|
||||
export interface S_ToastSettings {
|
||||
MaxVisibleToasts: Integer;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
// Toasts/Tests/FT_ToastLimit.ts
|
||||
|
||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
||||
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
||||
|
||||
/**
|
||||
* Functional Test: Toast System Capacity Management
|
||||
* Validates that the toast system enforces MaxVisibleToasts limit correctly
|
||||
* Tests that oldest toasts are removed when limit is exceeded
|
||||
*/
|
||||
export class FT_ToastLimit extends FunctionalTest {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Test execution - validates toast limit enforcement
|
||||
* Creates more toasts than allowed and verifies limit is enforced
|
||||
*/
|
||||
EventStartTest(): void {
|
||||
this.ToastComponent.InitializeToastSystem();
|
||||
|
||||
// Create MaxVisibleToasts + 3 toasts to test overflow handling
|
||||
for (
|
||||
let i = 1;
|
||||
i <= this.ToastComponent.ToastSettings.MaxVisibleToasts + 3;
|
||||
i++
|
||||
) {
|
||||
this.ToastComponent.ShowToast(
|
||||
`Limit test toast ${i}`,
|
||||
E_MessageType.Info,
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
// Verify that only MaxVisibleToasts are actually visible
|
||||
if (
|
||||
this.ToastComponent.GetTestData().length ===
|
||||
this.ToastComponent.ToastSettings.MaxVisibleToasts
|
||||
) {
|
||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
`Expected ${this.ToastComponent.ToastSettings.MaxVisibleToasts} to display, got ${this.ToastComponent.GetTestData().length}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Toast notification system - component under test
|
||||
* @category Components
|
||||
*/
|
||||
private ToastComponent = new AC_ToastSystem();
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,65 @@
|
|||
// Toasts/Tests/FT_ToastsDurationHandling.ts
|
||||
|
||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
||||
import type { Integer } from '#root/UE/Integer.ts';
|
||||
|
||||
/**
|
||||
* Functional Test: Toast Duration and Lifecycle Management
|
||||
* Validates basic toast creation and ID assignment functionality
|
||||
* Tests that toasts return valid IDs when created successfully
|
||||
*/
|
||||
export class FT_ToastsDurationHandling extends FunctionalTest {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Test execution - validates basic toast creation functionality
|
||||
* Creates two toasts and verifies they return valid IDs
|
||||
*/
|
||||
EventStartTest(): void {
|
||||
this.ToastComponent.InitializeToastSystem();
|
||||
this.toast1 = this.ToastComponent.ShowToast();
|
||||
this.toast2 = this.ToastComponent.ShowToast();
|
||||
|
||||
/**
|
||||
* Check if both toasts were created successfully by verifying positive IDs
|
||||
*/
|
||||
const AreToastsCreatedSuccessfully = (): boolean =>
|
||||
this.toast1 > 0 && this.toast2 > 0;
|
||||
|
||||
if (AreToastsCreatedSuccessfully()) {
|
||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
||||
} else {
|
||||
this.FinishTest(EFunctionalTestResult.Failed, `Failed to create toasts`);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Toast notification system - component under test
|
||||
* @category Components
|
||||
*/
|
||||
private ToastComponent = new AC_ToastSystem();
|
||||
|
||||
/**
|
||||
* ID of first test toast
|
||||
* @category Test State
|
||||
*/
|
||||
private toast1: Integer = 0;
|
||||
|
||||
/**
|
||||
* ID of second test toast
|
||||
* @category Test State
|
||||
*/
|
||||
private toast2: Integer = 0;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,105 @@
|
|||
// Toasts/Tests/FT_ToastsEdgeCases.ts
|
||||
|
||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
||||
import type { Integer } from '#root/UE/Integer.ts';
|
||||
import { StringLibrary } from '#root/UE/StringLibrary.ts';
|
||||
import type { Text } from '#root/UE/Text.ts';
|
||||
import { TextLibrary } from '#root/UE/TextLibrary.ts';
|
||||
|
||||
/**
|
||||
* Functional Test: Toast System Edge Cases
|
||||
* Tests toast system robustness with unusual input conditions
|
||||
* Validates handling of empty, very long, and multiline messages
|
||||
*/
|
||||
export class FT_ToastsEdgeCases extends FunctionalTest {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Test preparation - generates very long text for stress testing
|
||||
* Creates 500-character string to test text handling limits
|
||||
*/
|
||||
EventPrepareTest(): void {
|
||||
for (let i = 0; i < 500; i++) {
|
||||
this.longText = TextLibrary.StringToText(
|
||||
StringLibrary.Append(TextLibrary.TextToString(this.longText), 'A')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test execution - validates edge case handling
|
||||
* Tests empty text, very long text, and multiline text scenarios
|
||||
*/
|
||||
EventStartTest(): void {
|
||||
this.ToastComponent.InitializeToastSystem();
|
||||
|
||||
// Test empty message handling
|
||||
this.emptyToast = this.ToastComponent.ShowToast();
|
||||
|
||||
// Test very long message handling (500 characters)
|
||||
this.longToast = this.ToastComponent.ShowToast(this.longText);
|
||||
|
||||
// Test multiline message handling
|
||||
this.specialToast = this.ToastComponent.ShowToast(`Test
|
||||
Multiline
|
||||
Message`);
|
||||
|
||||
/**
|
||||
* Check if all edge case toasts were created successfully
|
||||
*/
|
||||
const AreToastsCreatedSuccessfully = (): boolean =>
|
||||
this.emptyToast > 0 && this.longToast > 0 && this.specialToast > 0;
|
||||
|
||||
if (AreToastsCreatedSuccessfully()) {
|
||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
`Edge case failures: empty=${this.emptyToast}, long=${this.longToast}, special=${this.specialToast}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Toast notification system - component under test
|
||||
* @category Components
|
||||
*/
|
||||
private ToastComponent = new AC_ToastSystem();
|
||||
|
||||
/**
|
||||
* ID of toast created with empty message
|
||||
* @category Test State
|
||||
*/
|
||||
private emptyToast: Integer = 0;
|
||||
|
||||
/**
|
||||
* ID of toast created with very long message (500 chars)
|
||||
* @category Test State
|
||||
*/
|
||||
private longToast: Integer = 0;
|
||||
|
||||
/**
|
||||
* ID of toast created with multiline message
|
||||
* @category Test State
|
||||
*/
|
||||
private specialToast: Integer = 0;
|
||||
|
||||
/**
|
||||
* Generated long text string for stress testing
|
||||
* Built during test preparation phase
|
||||
* @category Test Data
|
||||
*/
|
||||
private longText: Text = '';
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,54 @@
|
|||
// Toasts/Tests/FT_ToastsSystemInitialization.ts
|
||||
|
||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
||||
|
||||
/**
|
||||
* Functional Test: Toast System Initialization
|
||||
* Validates that toast system initializes with correct default settings
|
||||
* Tests IsEnabled state and MaxVisibleToasts configuration
|
||||
*/
|
||||
export class FT_ToastsSystemInitialization extends FunctionalTest {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Test execution - validates initialization and default settings
|
||||
* Uses nested validation to check system state after initialization
|
||||
*/
|
||||
EventStartTest(): void {
|
||||
this.ToastComponent.InitializeToastSystem();
|
||||
|
||||
if (this.ToastComponent.ToastSettings.IsEnabled) {
|
||||
if (this.ToastComponent.ToastSettings.MaxVisibleToasts > 0) {
|
||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
'Invalid state: max visible toasts less then 1'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
'Invalid state: enabled=false'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Toast notification system - component under test
|
||||
* @category Components
|
||||
*/
|
||||
private ToastComponent = new AC_ToastSystem();
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,71 @@
|
|||
// Toasts/Tests/FT_ToastsToastCreation.ts
|
||||
|
||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
||||
import { UEArray } from '#root/UE/UEArray.ts';
|
||||
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
||||
|
||||
/**
|
||||
* Functional Test: Toast Creation by Message Type
|
||||
* Validates toast creation functionality for all message types
|
||||
* Tests that each type (Info, Success, Warning, Error, Debug) creates valid toasts
|
||||
*/
|
||||
export class FT_ToastsToastCreation extends FunctionalTest {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// GRAPHS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
// EventGraph
|
||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Test execution - validates toast creation for each message type
|
||||
* Iterates through all message types and verifies successful creation
|
||||
*/
|
||||
EventStartTest(): void {
|
||||
this.ToastComponent.InitializeToastSystem();
|
||||
|
||||
this.ToastTypes.forEach(arrayElement => {
|
||||
// Create toast for current message type and check if valid ID is returned
|
||||
if (
|
||||
this.ToastComponent.ShowToast(
|
||||
`Test ${arrayElement} message`,
|
||||
arrayElement,
|
||||
1
|
||||
) <= 0
|
||||
) {
|
||||
this.FinishTest(
|
||||
EFunctionalTestResult.Failed,
|
||||
`Failed to create ${arrayElement} toast`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Toast notification system - component under test
|
||||
* @category Components
|
||||
*/
|
||||
private ToastComponent = new AC_ToastSystem();
|
||||
|
||||
/**
|
||||
* Array of all message types to test
|
||||
* Covers complete range of supported toast types
|
||||
* @category Test Data
|
||||
*/
|
||||
private ToastTypes: UEArray<E_MessageType> = new UEArray([
|
||||
E_MessageType.Info,
|
||||
E_MessageType.Success,
|
||||
E_MessageType.Warning,
|
||||
E_MessageType.Error,
|
||||
E_MessageType.Debug,
|
||||
]);
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,119 +1,113 @@
|
|||
// Content/Toasts/UI/WBP_Toast.ts
|
||||
// Toasts/UI/WBP_Toast.ts
|
||||
|
||||
import {Border, TextBox, Widget} from "../../classes.js";
|
||||
import {E_MessageType} from "../../UI/Enums/E_MessageType.js";
|
||||
import type {Color, Text} from "../../types.js";
|
||||
import {IsValid} from "../../functions.js";
|
||||
import { Border } from '#root/UE/Border.ts';
|
||||
import { MathLibrary } from '#root/UE/MathLibrary.ts';
|
||||
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
||||
import type { Text } from '#root/UE/Text.ts';
|
||||
import { TextBlock } from '#root/UE/TextBlock.ts';
|
||||
import { UserWidget } from '#root/UE/UserWidget.ts';
|
||||
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
||||
import { BFL_Colors } from '#root/UI/Libraries/BFL_Colors.ts';
|
||||
|
||||
export class WBP_Toast extends Widget {
|
||||
// Category: "Toast Data"
|
||||
/**
|
||||
* Current message text displayed in this toast
|
||||
* Individual Toast Notification Widget
|
||||
* Displays a single toast message with type-based styling
|
||||
* Managed by WBP_ToastContainer for positioning and lifecycle
|
||||
*/
|
||||
private MessageText: Text = "" as Text;
|
||||
export class WBP_Toast extends UserWidget {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// FUNCTIONS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Category: "Toast Data"
|
||||
/**
|
||||
* Toast type for color and styling
|
||||
* Update background styling based on toast type
|
||||
* Uses color library to get appropriate color for message type
|
||||
* @category Display Updates
|
||||
*/
|
||||
private ToastType: E_MessageType = E_MessageType.Info;
|
||||
|
||||
// Category: "UI Components"
|
||||
/**
|
||||
* Text box widget for displaying toast message
|
||||
* Styled based on toast type
|
||||
*/
|
||||
private MessageTextBox = new TextBox();
|
||||
|
||||
// Category: "UI Components"
|
||||
/**
|
||||
* Background panel for toast styling
|
||||
* Color changes based on toast type
|
||||
*/
|
||||
private BackgroundPanel = new Border();
|
||||
|
||||
// Category: "Toast Styling"
|
||||
/**
|
||||
* Get color based on toast type
|
||||
* @param Type - Toast type
|
||||
* @returns Color string in hex format
|
||||
* @pure Function has no side effects
|
||||
*/
|
||||
private GetToastColor(Type: E_MessageType): Color {
|
||||
switch (Type) {
|
||||
case E_MessageType.Info:
|
||||
return {R: 74, G: 144, B: 226, A: 100}; // Blue
|
||||
case E_MessageType.Success:
|
||||
return {R: 92, G: 184, B: 92, A: 100}; // Green
|
||||
case E_MessageType.Warning:
|
||||
return {R: 240, G: 173, B: 78, A: 100}; // Orange
|
||||
case E_MessageType.Error:
|
||||
return {R: 217, G: 83, B: 79, A: 100}; // Red
|
||||
case E_MessageType.Debug:
|
||||
return {R: 108, G: 177, B: 125, A: 100}; // Gray
|
||||
case E_MessageType.Test:
|
||||
return {R: 142, G: 68, B: 142, A: 100}; // Purple
|
||||
private UpdateBackgroundStyling(): void {
|
||||
if (SystemLibrary.IsValid(this.BackgroundPanel)) {
|
||||
this.BackgroundPanel.SetBrushColor(
|
||||
MathLibrary.ColorToLinearColor(
|
||||
BFL_Colors.GetColorByMessageType(this.ToastType, 100)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Display Updates"
|
||||
/**
|
||||
* Update message text box with current message
|
||||
* @private Internal display update method
|
||||
* Update message text box with current message text
|
||||
* @category Display Updates
|
||||
*/
|
||||
private UpdateMessageDisplay(): void {
|
||||
if (IsValid(this.MessageTextBox)) {
|
||||
if (SystemLibrary.IsValid(this.MessageTextBox)) {
|
||||
this.MessageTextBox.SetText(this.MessageText);
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Display Updates"
|
||||
/**
|
||||
* Update background styling based on toast type
|
||||
* @private Internal display update method
|
||||
*/
|
||||
private UpdateBackgroundStyling(): void {
|
||||
if (IsValid(this.BackgroundPanel)) {
|
||||
this.BackgroundPanel.SetBrushColor(this.GetToastColor(this.ToastType));
|
||||
}
|
||||
}
|
||||
|
||||
// Category: "Public Interface"
|
||||
/**
|
||||
* Set toast message and update display
|
||||
* @param Message - New message text
|
||||
* @example
|
||||
* // Set success message
|
||||
* SetMessage("Test completed successfully!")
|
||||
* @category Public Interface
|
||||
*/
|
||||
public SetMessage(Message: Text): void {
|
||||
this.MessageText = Message;
|
||||
this.UpdateMessageDisplay();
|
||||
}
|
||||
|
||||
// Category: "Public Interface"
|
||||
/**
|
||||
* Set toast type and update styling
|
||||
* @param Type - New toast type
|
||||
* @example
|
||||
* // Set as error toast
|
||||
* SetToastType(E_ToastType.Error)
|
||||
* SetToastType(E_MessageType.Error)
|
||||
* @category Public Interface
|
||||
*/
|
||||
public SetToastType(Type: E_MessageType): void {
|
||||
this.ToastType = Type;
|
||||
this.UpdateBackgroundStyling();
|
||||
}
|
||||
|
||||
// Category: "Widget Lifecycle"
|
||||
/**
|
||||
* Initialize toast widget with message and type
|
||||
* Adds widget to viewport for display
|
||||
* @param Message - Initial message text
|
||||
* @param Type - Initial toast type
|
||||
* @public Called when creating new toast
|
||||
* @category Widget Lifecycle
|
||||
*/
|
||||
public InitializeToast(Message: Text, Type: E_MessageType): void {
|
||||
this.SetMessage(Message);
|
||||
this.SetToastType(Type);
|
||||
this.AddToViewport();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Current message text displayed in this toast
|
||||
* @category Toast Data
|
||||
*/
|
||||
private MessageText: Text = '';
|
||||
|
||||
/**
|
||||
* Toast type for color and styling determination
|
||||
* @category Toast Data
|
||||
*/
|
||||
private ToastType: E_MessageType = E_MessageType.Info;
|
||||
|
||||
/**
|
||||
* Text widget for displaying the toast message
|
||||
* @category UI Components
|
||||
*/
|
||||
private MessageTextBox = new TextBlock();
|
||||
|
||||
/**
|
||||
* Background border widget for toast styling and color
|
||||
* @category UI Components
|
||||
*/
|
||||
private BackgroundPanel = new Border();
|
||||
}
|
||||
|
|
|
|||
BIN
Content/Toasts/UI/WBP_Toast.uasset (Stored with Git LFS)
BIN
Content/Toasts/UI/WBP_Toast.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,61 +1,76 @@
|
|||
// Content/Toasts/UI/WBP_ToastContainer.ts
|
||||
// Toasts/UI/WBP_ToastContainer.ts
|
||||
|
||||
import {VerticalBox, Widget} from "../../classes.js";
|
||||
import {ESlateVisibility} from "../../enums.js";
|
||||
import type {Text} from "../../types.js";
|
||||
import type {E_MessageType} from "../../UI/Enums/E_MessageType.js";
|
||||
import {WBP_Toast} from "./WBP_Toast.js";
|
||||
import {AddToArray, CreateWidget, IsValid, RemoveItemFromArray} from "../../functions.js";
|
||||
import { WBP_Toast } from '#root/Toasts/UI/WBP_Toast.ts';
|
||||
import { CreateWidget } from '#root/UE/CteateWidget.ts';
|
||||
import { ESlateVisibility } from '#root/UE/ESlateVisibility.ts';
|
||||
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
||||
import type { Text } from '#root/UE/Text.ts';
|
||||
import { UEArray } from '#root/UE/UEArray.ts';
|
||||
import { UserWidget } from '#root/UE/UserWidget.ts';
|
||||
import { VerticalBox } from '#root/UE/VerticalBox.ts';
|
||||
import type { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
||||
|
||||
export class WBP_ToastContainer extends Widget {
|
||||
// Category: "Layout"
|
||||
/**
|
||||
* Vertical box container for automatic toast stacking
|
||||
* Handles spacing and positioning automatically
|
||||
* Toast Container Widget
|
||||
* Manages layout and positioning of multiple toast notifications
|
||||
* Automatically stacks toasts vertically with proper spacing
|
||||
*/
|
||||
private ToastVerticalBox = new VerticalBox();
|
||||
export class WBP_ToastContainer extends UserWidget {
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// FUNCTIONS
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Category: "Container Management"
|
||||
/**
|
||||
* Array of child toast widgets for management
|
||||
* Create and add new toast to container
|
||||
* @param Message - Toast message text
|
||||
* @param Type - Toast type for styling
|
||||
* @returns Created toast widget reference
|
||||
* @category Toast Management
|
||||
*/
|
||||
private ChildToasts: WBP_Toast[] = [];
|
||||
public AddToast(Message: Text, Type: E_MessageType): WBP_Toast {
|
||||
const toast = CreateWidget(WBP_Toast);
|
||||
toast.InitializeToast(Message, Type);
|
||||
this.ToastVerticalBox.AddChild(toast);
|
||||
this.ChildToasts.Add(toast);
|
||||
return toast;
|
||||
}
|
||||
|
||||
// Category: "Container Setup"
|
||||
/**
|
||||
* Initialize toast container
|
||||
* Sets up vertical box with proper spacing
|
||||
* Remove toast from container and hide it
|
||||
* @param Toast - Toast widget to remove
|
||||
* @category Toast Management
|
||||
*/
|
||||
public RemoveToast(Toast: WBP_Toast): void {
|
||||
if (SystemLibrary.IsValid(Toast)) {
|
||||
this.ChildToasts.Remove(Toast);
|
||||
Toast.SetVisibility(ESlateVisibility.Hidden);
|
||||
Toast.RemoveFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize container widget and add to viewport
|
||||
* @category Container Setup
|
||||
*/
|
||||
public InitializeContainer(): void {
|
||||
this.SetVisibility(ESlateVisibility.Visible);
|
||||
this.AddToViewport();
|
||||
}
|
||||
|
||||
// Category: "Toast Management"
|
||||
/**
|
||||
* Add new toast to container
|
||||
* @param Message - Toast message
|
||||
* @param Type - Toast type
|
||||
* @returns Created toast widget reference
|
||||
*/
|
||||
public AddToast(Message: Text, Type: E_MessageType): WBP_Toast {
|
||||
const toast = CreateWidget(new WBP_Toast());
|
||||
toast.InitializeToast(Message, Type);
|
||||
this.ToastVerticalBox.AddChild(toast);
|
||||
AddToArray(this.ChildToasts, toast);
|
||||
return toast;
|
||||
}
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
// VARIABLES
|
||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Category: "Toast Management"
|
||||
/**
|
||||
* Remove toast from container
|
||||
* @param Toast - Toast widget to remove
|
||||
* Vertical layout container for automatic toast stacking
|
||||
* Handles spacing and positioning automatically
|
||||
* @category Layout
|
||||
*/
|
||||
public RemoveToast(Toast: WBP_Toast): void {
|
||||
if (IsValid(Toast)) {
|
||||
RemoveItemFromArray(this.ChildToasts, Toast);
|
||||
Toast.SetVisibility(ESlateVisibility.Hidden);
|
||||
Toast.RemoveFromParent();
|
||||
}
|
||||
}
|
||||
private ToastVerticalBox = new VerticalBox();
|
||||
|
||||
/**
|
||||
* Array of managed child toast widgets
|
||||
* @category Container Management
|
||||
*/
|
||||
private ChildToasts: UEArray<WBP_Toast> = new UEArray([]);
|
||||
}
|
||||
|
|
|
|||
BIN
Content/Toasts/UI/WBP_ToastContainer.uasset (Stored with Git LFS)
BIN
Content/Toasts/UI/WBP_ToastContainer.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -0,0 +1,10 @@
|
|||
// UE/Actor.ts
|
||||
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class Actor extends UEObject {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// UE/ActorComponent.ts
|
||||
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class ActorComponent extends UEObject {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// UE/BlueprintFunctionLibrary.ts
|
||||
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class BlueprintFunctionLibrary extends UEObject {
|
||||
constructor(
|
||||
outer: UEObject | null = null,
|
||||
name: string = 'BlueprintFunctionLibrary'
|
||||
) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// UE/Border.ts
|
||||
|
||||
import { ContentWidget } from '#root/UE/ContentWidget.ts';
|
||||
import type { LinearColor } from '#root/UE/LinearColor.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class Border extends ContentWidget {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
|
||||
public SetBrushColor(color: LinearColor): void {
|
||||
console.log(color);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// UE/Byte.ts
|
||||
|
||||
export type Byte = number;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// Content/Cast.ts
|
||||
|
||||
export function Cast<T>(obj: unknown): T | null {
|
||||
return (obj as T) || null;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// UE/Color.ts
|
||||
|
||||
import { StructBase } from '#root/UE/StructBase.ts';
|
||||
import type { UInt8 } from '#root/UE/UInt8.ts';
|
||||
|
||||
export class Color extends StructBase {
|
||||
constructor(
|
||||
public B: UInt8 = 0,
|
||||
public G: UInt8 = 0,
|
||||
public R: UInt8 = 0,
|
||||
public A: UInt8 = 0
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// UE/ContentWidget.ts
|
||||
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { PanelWidget } from '#root/UE/PanelWidget.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class ContentWidget extends PanelWidget {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// UE/Controller.ts
|
||||
|
||||
import { Actor } from '#root/UE/Actor.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class Controller extends Actor {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
|
||||
public CastToPlayerController(): Controller {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// UE/CreateWidget.ts
|
||||
|
||||
import type { UserWidget } from '#root/UE/UserWidget.ts';
|
||||
|
||||
type WidgetConstructor<T extends UserWidget> = new () => T;
|
||||
|
||||
export function CreateWidget<T extends UserWidget>(
|
||||
widgetClass: WidgetConstructor<T>
|
||||
): T {
|
||||
return new widgetClass();
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// UE/DataAsset.ts
|
||||
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class DataAsset extends UEObject {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// UE/DataTable.ts
|
||||
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import type { UEArray } from '#root/UE/UEArray.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class DataTable<T> extends UEObject {
|
||||
private rows: Map<Name, T> = new Map();
|
||||
|
||||
constructor(
|
||||
outer: UEObject | null = null,
|
||||
name: Name = Name.NONE,
|
||||
initialData: UEArray<T & { Name: Name }> = [] as unknown as UEArray<
|
||||
T & { Name: Name }
|
||||
>
|
||||
) {
|
||||
super(outer, name);
|
||||
|
||||
initialData.forEach(row => {
|
||||
this.rows.set(row.Name, row);
|
||||
});
|
||||
}
|
||||
|
||||
public GetDataTableRow(
|
||||
rowName: Name,
|
||||
rowFound?: (row: T) => void,
|
||||
rowNotFound?: () => void
|
||||
): void | T | undefined {
|
||||
const row = this.rows.get(rowName);
|
||||
|
||||
if (!rowFound && !rowNotFound) {
|
||||
return row;
|
||||
}
|
||||
|
||||
if (row) {
|
||||
rowFound?.(row);
|
||||
} else {
|
||||
rowNotFound?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// UE/DataTableFunctionLibrary.ts
|
||||
|
||||
import { BlueprintFunctionLibrary } from '#root/UE/BlueprintFunctionLibrary.ts';
|
||||
import type { DataTable } from '#root/UE/DataTable.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
|
||||
class DataTableFunctionLibraryClass extends BlueprintFunctionLibrary {
|
||||
constructor(
|
||||
outer: null | BlueprintFunctionLibrary = null,
|
||||
name: string = 'DataTableFunctionLibrary'
|
||||
) {
|
||||
super(outer, name);
|
||||
}
|
||||
|
||||
public GetDataTableRowNames<T>(table: DataTable<T>): Name[] {
|
||||
return Array.from(table['rows'].keys());
|
||||
}
|
||||
}
|
||||
|
||||
export const DataTableFunctionLibrary = new DataTableFunctionLibraryClass();
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// UE/EFunctionalTestResult.ts
|
||||
|
||||
export enum EFunctionalTestResult {
|
||||
'Default' = 'Default',
|
||||
'Invalid' = 'Invalid',
|
||||
'Error' = 'Error',
|
||||
'Running' = 'Running',
|
||||
'Failed' = 'Failed',
|
||||
'Succeeded' = 'Succeeded',
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// UE/ESlateVisibility.ts
|
||||
|
||||
export enum ESlateVisibility {
|
||||
Visible = 'Visible',
|
||||
Collapsed = 'Collapsed',
|
||||
Hidden = 'Hidden',
|
||||
NotHitTestableSelfAndAllChildren = 'NotHitTestableSelfAndAllChildren',
|
||||
NotHitTestableSelfOnly = 'NotHitTestableSelfOnly',
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// UE/EnhancedActionKeyMapping.ts
|
||||
|
||||
import type { InputAction } from '#root/UE/InputAction.ts';
|
||||
import type { InputModifier } from '#root/UE/InputModifier.ts';
|
||||
import type { InputTrigger } from '#root/UE/InputTrigger.ts';
|
||||
import type { Key } from '#root/UE/Key.ts';
|
||||
import { StructBase } from '#root/UE/StructBase.ts';
|
||||
import type { UEArray } from '#root/UE/UEArray.ts';
|
||||
|
||||
export class EnhancedActionKeyMapping extends StructBase {
|
||||
public action: InputAction;
|
||||
public key: Key;
|
||||
public modifiers: UEArray<InputModifier>;
|
||||
public triggers: UEArray<InputTrigger>;
|
||||
|
||||
constructor(
|
||||
triggers: UEArray<InputTrigger>,
|
||||
modifiers: UEArray<InputModifier>,
|
||||
action: InputAction,
|
||||
key: Key
|
||||
) {
|
||||
super();
|
||||
|
||||
this.action = action;
|
||||
this.key = key;
|
||||
this.modifiers = modifiers;
|
||||
this.triggers = triggers;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// UE/EnhancedInputLocalPlayerSubsystem.ts
|
||||
|
||||
import type { InputMappingContext } from '#root/UE/InputMappingContext.ts';
|
||||
import type { Integer } from '#root/UE/Integer.ts';
|
||||
import { LocalPlayerSubsystem } from '#root/UE/LocalPlayerSubsystem.ts';
|
||||
import { ModifyContextOptions } from '#root/UE/ModifyContextOptions.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class EnhancedInputLocalPlayerSubsystem extends LocalPlayerSubsystem {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
|
||||
public AddMappingContext(
|
||||
mappingContent: InputMappingContext,
|
||||
priority: Integer = 0,
|
||||
options: ModifyContextOptions = new ModifyContextOptions(true, false, false)
|
||||
): void {
|
||||
console.log(mappingContent, priority, options);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// UE/Float.ts
|
||||
|
||||
export type Float = number;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// UE/FunctionalTest.ts
|
||||
|
||||
import { Actor } from '#root/UE/Actor.ts';
|
||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class FunctionalTest extends Actor {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
|
||||
public FinishTest(
|
||||
testResult: EFunctionalTestResult = EFunctionalTestResult.Default,
|
||||
message: string = ''
|
||||
): void {
|
||||
console.log(
|
||||
`Test Finished with result: ${testResult}. Message: ${message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// Content/GameModeBase.ts
|
||||
|
||||
import { Info } from '#root/UE/Info.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class GameModeBase extends Info {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// UE/Info.ts
|
||||
|
||||
import { Actor } from '#root/UE/Actor.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class Info extends Actor {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// UE/InputAction.ts
|
||||
|
||||
import { DataAsset } from '#root/UE/DataAsset.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import type { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class InputAction extends DataAsset {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// UE/InputMappingContext.ts
|
||||
|
||||
import { DataAsset } from '#root/UE/DataAsset.ts';
|
||||
import { EnhancedActionKeyMapping } from '#root/UE/EnhancedActionKeyMapping.ts';
|
||||
import type { InputAction } from '#root/UE/InputAction.ts';
|
||||
import type { InputModifier } from '#root/UE/InputModifier.ts';
|
||||
import type { InputTrigger } from '#root/UE/InputTrigger.ts';
|
||||
import type { Key } from '#root/UE/Key.ts';
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import type { UEArray } from '#root/UE/UEArray.ts';
|
||||
import type { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class InputMappingContext extends DataAsset {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
|
||||
public mapKey(action: InputAction, toKey: Key): EnhancedActionKeyMapping {
|
||||
return new EnhancedActionKeyMapping(
|
||||
[] as unknown as UEArray<InputTrigger>,
|
||||
[] as unknown as UEArray<InputModifier>,
|
||||
action,
|
||||
toKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// UE/InputModifier.ts
|
||||
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class InputModifier extends UEObject {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// UE/InputTrigger.ts
|
||||
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class InputTrigger extends UEObject {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// UE/Integer.ts
|
||||
|
||||
export type Integer = number;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// Content/UE/Key.ts
|
||||
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { StructBase } from '#root/UE/StructBase.ts';
|
||||
|
||||
export class Key extends StructBase {
|
||||
public keyName: Name;
|
||||
|
||||
constructor(keyName: Name | string = Name.NONE) {
|
||||
super();
|
||||
|
||||
if (keyName instanceof Name) {
|
||||
this.keyName = keyName;
|
||||
} else {
|
||||
this.keyName = new Name(keyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// UE/LinearColor.ts
|
||||
|
||||
import type { Float } from '#root/UE/Float.ts';
|
||||
import { StructBase } from '#root/UE/StructBase.ts';
|
||||
|
||||
export class LinearColor extends StructBase {
|
||||
public A: Float = 0;
|
||||
public R: Float = 0;
|
||||
public G: Float = 0;
|
||||
public B: Float = 0;
|
||||
|
||||
constructor(r: Float = 0, g: Float = 0, b: Float = 0, a: Float = 0) {
|
||||
super();
|
||||
this.A = a;
|
||||
this.R = r;
|
||||
this.G = g;
|
||||
this.B = b;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// UE/LocalPlayerSubsystem.ts
|
||||
|
||||
import { Name } from '#root/UE/Name.ts';
|
||||
import { Subsystem } from '#root/UE/Subsystem.ts';
|
||||
import { UEObject } from '#root/UE/UEObject.ts';
|
||||
|
||||
export class LocalPlayerSubsystem extends Subsystem {
|
||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
||||
super(outer, name);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue