[code] Add ts instruments & refactor all code

main
Nikolay Petrov 2025-09-02 21:44:13 +05:00
parent 24e515e80d
commit f572fdebca
135 changed files with 7084 additions and 1237 deletions

40
.eslintignore Normal file
View File

@ -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

181
.eslintrc.js Normal file
View File

@ -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'
]
};

7
.gitignore vendored
View File

@ -13,6 +13,13 @@
!/Config/** !/Config/**
!/Plugins/** !/Plugins/**
!/Documentation/** !/Documentation/**
!/.eslintignore
!/.eslintrc.js
!/.prettierignore
!/.prettierrc.js
!/package.json
!/package-lock.json
!/tsconfig.json
# Only allow .uasset, .umap and .ts files from /Content dir. # Only allow .uasset, .umap and .ts files from /Content dir.
# .uasset and .umap tracked by git-lfs, don't forget to track # .uasset and .umap tracked by git-lfs, don't forget to track

26
.prettierignore Normal file
View File

@ -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

44
.prettierrc.js Normal file
View File

@ -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',
},
},
],
};

View File

@ -1,76 +1,129 @@
// Content/Blueprints/BP_MainCharacter.ts // Blueprints/BP_MainCharacter.ts
import {AC_Movement} from "../Movement/Components/AC_Movement.js"; import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
import type {Controller, Float} from "../types.js"; import { IMC_Default } from '#root/Input/IMC_Default.ts';
import { import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
AddMappingContext, import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
CastToPlayController, import { Cast } from '#root/UE/Cast.ts';
EnhancedInputLocalPlayerSubsystem, import type { Controller } from '#root/UE/Controller.ts';
GetGameTimeInSeconds, import { EnhancedInputLocalPlayerSubsystem } from '#root/UE/EnhancedInputLocalPlayerSubsystem.ts';
} from "../functions.js"; import type { Float } from '#root/UE/Float.ts';
import {IMC_Default} from "../Input/IMC_Default.js"; import { Pawn } from '#root/UE/Pawn.ts';
import {AC_DebugHUD} from "../Debug/Components/AC_DebugHUD.js"; import type { PlayerController } from '#root/UE/PlayerController.ts';
import {AC_ToastSystem} from "../Toasts/Compontents/AC_ToastSystem.js"; import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
export class BP_MainCharacter { /**
// Category: "Components" * Main Character Blueprint
MovementComponent = new AC_Movement(); * Core player character with deterministic movement system
* Integrates debug HUD and toast notification systems
// Category: "Components" */
DebugHUDComponent = new AC_DebugHUD(); export class BP_MainCharacter extends Pawn {
// ════════════════════════════════════════════════════════════════════════════════════════
// Category: "Components" // GRAPHS
ToastSystemComponent = new AC_ToastSystem(); // ════════════════════════════════════════════════════════════════════════════════════════
// Category: "Debug"
// Instance Editable: true
ShowDebugInfo: boolean = true;
// ────────────────────────────────────────────────────────────────────────────────────────
// EventGraph // 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) { if (this.ShowDebugInfo) {
this.DebugHUDComponent.PreviousPage(); this.DebugHUDComponent.PreviousPage();
} }
} }
EnhancedInputActionIA_NextDebugMode() { /** Navigate to next debug page */
EnhancedInputActionINextDebugMode(): void {
if (this.ShowDebugInfo) { if (this.ShowDebugInfo) {
this.DebugHUDComponent.NextPage(); this.DebugHUDComponent.NextPage();
} }
} }
EnhancedInputActionToggleHUD() { /** Toggle debug HUD visibility */
EnhancedInputActionToggleHUD(): void {
if (this.ShowDebugInfo) { if (this.ShowDebugInfo) {
this.DebugHUDComponent.ToggleDebugHUD(); this.DebugHUDComponent.ToggleDebugHUD();
} }
} }
EnhancedInputActionIA_ToggleVisualDebug() { /** Toggle visual debug rendering */
EnhancedInputActionToggleVisualDebug(): void {
if (this.ShowDebugInfo) { if (this.ShowDebugInfo) {
this.DebugHUDComponent.ToggleVisualDebug(); 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) { if (this.ShowDebugInfo) {
this.DebugHUDComponent.InitializeDebugHUD(this.MovementComponent);
this.ToastSystemComponent.InitializeToastSystem(); 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 { EventTick(DeltaTime: Float): void {
if (this.ShowDebugInfo) { if (this.ShowDebugInfo) {
this.DebugHUDComponent.UpdateHUD(GetGameTimeInSeconds(), DeltaTime); this.DebugHUDComponent.UpdateHUD(
SystemLibrary.GetGameTimeInSeconds(),
DeltaTime
);
this.ToastSystemComponent.UpdateToastSystem(); 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)

Binary file not shown.

View File

@ -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;
}

View File

@ -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 { E_DebugUpdateFunction } from '#root/Debug/Enums/E_DebugUpdateFunction.ts';
import type {Float, Integer} from "../../types.js"; import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.ts';
import type {S_DebugPage} from "../Structs/S_DebugPage.js"; import type { S_DebugSettings } from '#root/Debug/Structs/S_DebugSettings.ts';
import {AddToArray, CreateWidget, GetFromArray, IsValid, Print, SetArrayElem} from "../../functions.js"; import { DT_DebugPages } from '#root/Debug/Tables/DT_DebugPages.ts';
import {E_DebugPageID} from "../Enums/E_DebugPageID.js"; import { WBP_DebugHUD } from '#root/Debug/UI/WBP_DebugHUD.ts';
import {E_DebugUpdateFunction} from "../Enums/E_DebugUpdateFunction.js"; import type { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
import {WBP_DebugHUD} from "../UI/WBP_DebugHUD.js"; import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
import type {S_DebugSettings} from "../Structs/S_DebugSettings.js"; import { ActorComponent } from '#root/UE/ActorComponent.ts';
import {E_DebugMode} from "../Enums/E_DebugMode.js"; import { CreateWidget } from '#root/UE/CteateWidget.ts';
import {ESlateVisibility} from "../../enums.js"; 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 * Debug HUD Controller Component
* Manages debug information display system for deterministic movement * Manages debug information display system for deterministic movement
* Provides real-time performance monitoring and parameter visualization * Provides real-time performance monitoring and parameter visualization
* Part of Stage 2: Debug HUD system implementation
*/ */
export class AC_DebugHUD { export class AC_DebugHUD extends ActorComponent {
// Category: "DebugConfig" // ════════════════════════════════════════════════════════════════════════════════════════
// Instance Editable: true // FUNCTIONS
/** // ════════════════════════════════════════════════════════════════════════════════════════
* 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,
}
// 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 * Check if debug HUD should be visible
* @returns True if system is initialized and mode is visible * @returns True if system is initialized and mode is visible
* @pure Function has no side effects * @category HUD Control
* @pure true
*/ */
private ShouldShowDebugHUD() { private ShouldShowDebugHUD(): boolean {
return this.IsInitialized && this.DebugSettings.CurrentMode === E_DebugMode.Visible; 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 * 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 * @example
* // Update every frame (UpdateFrequency = 0) * // Update every frame (UpdateFrequency = 0)
* ShouldUpdateDebugHUD(gameTime) // returns true * ShouldUpdateDebugHUD(gameTime) // returns true
* // Update at 30Hz (UpdateFrequency = 30) * // Update at 30Hz (UpdateFrequency = 30)
* ShouldUpdateDebugHUD(gameTime) // returns true every 1/30 seconds * 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 { private ShouldUpdateDebugHUD(CurrentTime: Float = 0): boolean {
if (this.DebugSettings.UpdateFrequency <= 0) { const IsFrequencyLimited = (updateFrequency: Float): boolean =>
return true; // Update every frame 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 { } 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 * Register or update a debug page in the system
* @param PageData - Page configuration and content data
* @private Internal page management method
* @example * @example
* // Register movement constants page * // Register movement constants page
* RegisterDebugPage({ * RegisterDebugPage({
@ -127,279 +85,333 @@ export class AC_DebugHUD {
* IsVisible: true, * IsVisible: true,
* UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage * UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage
* }) * })
* @param PageData - Page configuration and content data
* @category Page Management
*/ */
private RegisterDebugPage(PageData: S_DebugPage): void { private RegisterDebugPage(PageData: S_DebugPage): void {
let existingIndex: Integer = -1; let existingIndex: Integer = -1;
this.DebugPages.forEach((page, index) => { this.DebugPages.forEach((page, index) => {
if (page.PageID === PageData.PageID) { if (page.PageID === PageData.PageID) {
this.DebugPages[index] = PageData; existingIndex = index;
return; return;
} }
}) });
if (existingIndex >= 0) { const IsPageExists = (): boolean => existingIndex >= 0;
SetArrayElem(this.DebugPages, existingIndex, PageData);
if (IsPageExists()) {
this.DebugPages.SetArrayElem(existingIndex, PageData);
} else { } else {
AddToArray(this.DebugPages, PageData); this.DebugPages.Add(PageData);
} }
} }
// Category: "Page Management"
// Pure: true
/** /**
* Get all currently visible debug pages * Get all currently visible debug pages
* @returns Array of visible pages only * @returns Array of visible pages only
* @pure Function has no side effects * @category Page Management
* @pure true
*/ */
private GetVisiblePages(): S_DebugPage[] { public GetVisiblePages(): UEArray<S_DebugPage> {
const filteredPages: S_DebugPage[] = []; const filteredPages: UEArray<S_DebugPage> = new UEArray([]);
this.DebugPages.forEach((page) => { this.DebugPages.forEach(page => {
if (page.IsVisible) { if (page.IsVisible) {
AddToArray(filteredPages, page); filteredPages.Add(page);
} }
}) });
return filteredPages; return filteredPages;
} }
// Category: "Page Management"
/** /**
* Get currently selected debug page * Get currently selected debug page
* @returns Object with page data and validity flag * @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} { private GetCurrentPage():
const length = this.GetVisiblePages().length; | { 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 IsPageIndexInvalid(
return {Page: GetFromArray(this.DebugPages, this.DebugSettings.CurrentPageIndex), IsFound: true}; this.GetVisiblePages().length,
} else { this.DebugSettings.CurrentPageIndex
return {Page: null, IsFound: false}; )
} ? { Page: null, IsFound: false }
: {
Page: this.GetVisiblePages().Get(this.DebugSettings.CurrentPageIndex),
IsFound: true,
};
} }
// Category: "Navigation"
/** /**
* Toggle debug HUD visibility between visible and hidden * Toggle debug HUD visibility between visible and hidden
* @public User input handler for F1 key or similar * @category Navigation
*/ */
public ToggleDebugHUD() { public ToggleDebugHUD(): void {
this.DebugSettings.CurrentMode = this.DebugSettings.CurrentMode === E_DebugMode.Visible ? E_DebugMode.Hidden : E_DebugMode.Visible; this.DebugSettings.CurrentMode =
this.DebugSettings.CurrentMode === ESlateVisibility.Visible
? ESlateVisibility.Hidden
: ESlateVisibility.Visible;
this.UpdateWidgetVisibility();
} }
// Category: "Navigation"
/** /**
* Navigate to previous debug page * Navigate to previous debug page
* Wraps around to last page if at beginning * 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; const length = this.GetVisiblePages().length;
if (length > 1) { if (length > 1) {
const currentPage = this.DebugSettings.CurrentPageIndex; 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 * Navigate to next debug page
* Wraps around to first page if at end * 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; const length = this.GetVisiblePages().length;
if (length > 1) { const HasMultiplePages = (): boolean => length > 1;
this.DebugSettings.CurrentPageIndex = (this.DebugSettings.CurrentPageIndex + 1) % length; 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.) * 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; 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 * Update content of currently selected page
* Calls appropriate update function based on page type * Calls appropriate update function based on page type
* @private Internal update system method * @category Page Updates
*/ */
private UpdateCurrentPage() { private UpdateCurrentPage(): void {
const {Page, IsFound}: {Page: S_DebugPage, IsFound: boolean} = this.GetCurrentPage(); let CurrentPage = this.GetCurrentPage().Page;
let CurrentPage = Page;
if (IsFound) { if (this.GetCurrentPage().IsFound && CurrentPage !== null) {
switch (CurrentPage.UpdateFunction) { switch (CurrentPage.UpdateFunction) {
case E_DebugUpdateFunction.UpdateMovementPage: case E_DebugUpdateFunction.UpdateMovementPage: {
CurrentPage = this.UpdateMovementPage(CurrentPage); CurrentPage = this.UpdateMovementPage(CurrentPage);
break; break;
case E_DebugUpdateFunction.UpdateSurfacePage: }
case E_DebugUpdateFunction.UpdateSurfacePage: {
CurrentPage = this.UpdateSurfacePage(CurrentPage); CurrentPage = this.UpdateSurfacePage(CurrentPage);
break; break;
case E_DebugUpdateFunction.UpdatePerformancePage: }
case E_DebugUpdateFunction.UpdatePerformancePage: {
CurrentPage = this.UpdatePerformancePage(CurrentPage); CurrentPage = this.UpdatePerformancePage(CurrentPage);
break; }
} }
SetArrayElem(this.DebugPages, this.DebugSettings.CurrentPageIndex, CurrentPage); this.DebugPages.SetArrayElem(
this.DebugSettings.CurrentPageIndex,
CurrentPage
);
this.UpdateWidgetPage(); this.UpdateWidgetPage();
} }
} }
// Category: "Page Updates"
/** /**
* Update movement constants page content * Update movement constants page content
* @param Page - Page structure to update * @param Page - Page structure to update
* @returns Updated page with current movement data * @returns Updated page with current movement data
* @private Page-specific update method * @category Page Updates
*/ */
private UpdateMovementPage(Page: S_DebugPage): S_DebugPage { public UpdateMovementPage(Page: S_DebugPage): S_DebugPage {
if (IsValid(this.MovementComponent)) { if (SystemLibrary.IsValid(this.MovementComponent)) {
return { return {
PageID: Page.PageID, PageID: Page.PageID,
Title: Page.Title, 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` + `Acceleration: ${this.MovementComponent.MovementConstants.Acceleration}\n` +
`Friction: ${this.MovementComponent.MovementConstants.Friction}\n` + `Friction: ${this.MovementComponent.MovementConstants.Friction}\n` +
`Gravity: ${this.MovementComponent.MovementConstants.Gravity}`, `Gravity: ${this.MovementComponent.MovementConstants.Gravity}`,
IsVisible: Page.IsVisible, IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction UpdateFunction: Page.UpdateFunction,
} };
} else { } else {
return { return {
PageID: Page.PageID, PageID: Page.PageID,
Title: Page.Title, Title: Page.Title,
Content: 'Movement Component Not Found', Content: 'Movement Component Not Found',
IsVisible: Page.IsVisible, IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction UpdateFunction: Page.UpdateFunction,
} };
} }
} }
// Category: "Page Updates"
/** /**
* Update surface classification page content * Update surface classification page content
* @param Page - Page structure to update * @param Page - Page structure to update
* @returns Updated page with current surface angle thresholds * @returns Updated page with current surface angle thresholds
* @private Page-specific update method * @category Page Updates
*/ */
private UpdateSurfacePage(Page: S_DebugPage): S_DebugPage { public UpdateSurfacePage(Page: S_DebugPage): S_DebugPage {
if (IsValid(this.MovementComponent)) { if (SystemLibrary.IsValid(this.MovementComponent)) {
return { return {
PageID: Page.PageID, PageID: Page.PageID,
Title: Page.Title, 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` + `Steep Slope: ${this.MovementComponent.AngleThresholdsDegrees.Walkable}°-${this.MovementComponent.AngleThresholdsDegrees.SteepSlope}°\n` +
`Wall: ${this.MovementComponent.AngleThresholdsDegrees.SteepSlope}°-${this.MovementComponent.AngleThresholdsDegrees.Wall}°\n` + `Wall: ${this.MovementComponent.AngleThresholdsDegrees.SteepSlope}°-${this.MovementComponent.AngleThresholdsDegrees.Wall}°\n` +
`Ceiling: >${this.MovementComponent.AngleThresholdsDegrees.Wall}°`, `Ceiling: >${this.MovementComponent.AngleThresholdsDegrees.Wall}°`,
IsVisible: Page.IsVisible, IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction UpdateFunction: Page.UpdateFunction,
} };
} else { } else {
return { return {
PageID: Page.PageID, PageID: Page.PageID,
Title: Page.Title, Title: Page.Title,
Content: 'Movement Component Not Found', Content: 'Movement Component Not Found',
IsVisible: Page.IsVisible, IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction UpdateFunction: Page.UpdateFunction,
} };
} }
} }
// Category: "Page Updates"
/** /**
* Update performance metrics page content * Update performance metrics page content
* @param Page - Page structure to update * @param Page - Page structure to update
* @returns Updated page with current performance data * @returns Updated page with current performance data
* @private Page-specific update method * @category Page Updates
*/ */
private UpdatePerformancePage(Page: S_DebugPage): S_DebugPage { public UpdatePerformancePage(Page: S_DebugPage): S_DebugPage {
if (IsValid(this.MovementComponent)) { if (SystemLibrary.IsValid(this.MovementComponent)) {
const IsUpdatingEveryFrame = (updateFrequency: Float): boolean =>
updateFrequency <= 0;
return { return {
PageID: Page.PageID, PageID: Page.PageID,
Title: Page.Title, Title: Page.Title,
Content: `Frame: ${this.FrameCounter}\n` + Content:
`Frame: ${this.FrameCounter}\n` +
`FPS: ${this.FPS}\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}`, `ActivePages: ${this.GetVisiblePages().length}`,
IsVisible: Page.IsVisible, IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction UpdateFunction: Page.UpdateFunction,
} };
} else { } else {
return { return {
PageID: Page.PageID, PageID: Page.PageID,
Title: Page.Title, Title: Page.Title,
Content: 'Movement Component Not Found', Content: 'Movement Component Not Found',
IsVisible: Page.IsVisible, IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction UpdateFunction: Page.UpdateFunction,
} };
} }
} }
// Category: "Widget Control"
/** /**
* Create debug widget instance and add to viewport * Create debug widget instance and add to viewport
* @private Internal widget management method * @category Widget Control
*/ */
private CreateDebugWidget() { private CreateDebugWidget(): void {
this.DebugWidget = CreateWidget<WBP_DebugHUD>(new WBP_DebugHUD); this.DebugWidget = CreateWidget(WBP_DebugHUD);
this.DebugWidget.MovementComponent = this.MovementComponent; this.DebugWidget.MovementComponent = this.MovementComponent;
if (IsValid(this.DebugWidget)) { if (SystemLibrary.IsValid(this.DebugWidget)) {
this.DebugWidget.AddToViewport(); this.DebugWidget.AddToViewport();
} else { } 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 * Update widget visibility based on current debug mode
* @private Internal widget management method * @category Widget Control
*/ */
private UpdateWidgetVisibility() { private UpdateWidgetVisibility(): void {
if (IsValid(this.DebugWidget)) { if (SystemLibrary.IsValid(this.DebugWidget)) {
this.DebugWidget.SetVisibility(this.ShouldShowDebugHUD() ? ESlateVisibility.Visible : ESlateVisibility.Hidden); this.DebugWidget.SetVisibility(
this.ShouldShowDebugHUD()
? ESlateVisibility.Visible
: ESlateVisibility.Hidden
);
} }
} }
// Category: "Widget Communication"
/** /**
* Send current page data to debug widget for display * Send current page data to debug widget for display
* @private Internal widget communication method * @category Widget Communication
*/ */
private UpdateWidgetPage() { private UpdateWidgetPage(): void {
const {Page, IsFound} = this.GetCurrentPage(); if (
this.GetCurrentPage().IsFound &&
SystemLibrary.IsValid(this.DebugWidget)
) {
const currentPage = this.GetCurrentPage().Page;
const visiblePages = this.GetVisiblePages();
if (IsFound) { this.DebugWidget.SetHeaderText(currentPage!.Title);
this.DebugWidget.SetHeaderText(Page.Title); this.DebugWidget.SetContentText(currentPage!.Content);
this.DebugWidget.SetContentText(Page.Content); this.DebugWidget.SetNavigationText(
this.DebugWidget.SetNavigationText(`Page ${this.DebugSettings.CurrentPageIndex + 1}/${this.GetVisiblePages().length} | PageUp/PageDown - Navigate`); `Page ${this.DebugSettings.CurrentPageIndex + 1}/${visiblePages.length} | PageUp/PageDown - Navigate`
);
} }
} }
// Category: "HUD Rendering"
/** /**
* Main update loop for debug HUD system * Main update loop for debug HUD system
* Called every frame from game loop * Called every frame from game loop
* @param CurrentTime - Current game time in seconds * @param CurrentTime - Current game time in seconds
* @param DeltaTime - Time since last frame 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) { public UpdateHUD(CurrentTime: Float, DeltaTime: Float): void {
if (this.IsInitialized && this.ShouldUpdateDebugHUD(CurrentTime)) { 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.FrameCounter++;
this.FPS = DeltaTime > 0.0 ? 1.0 / DeltaTime : 0.0; this.FPS = IsValidDeltaTime(DeltaTime) ? CalculateFPS(DeltaTime) : 0;
this.LastUpdateTime = CurrentTime; this.LastUpdateTime = CurrentTime;
if (this.ShouldShowDebugHUD()) { if (this.ShouldShowDebugHUD()) {
@ -409,223 +421,146 @@ export class AC_DebugHUD {
} }
} }
// Category: "System Setup"
/** /**
* Register default debug pages (Movement, Surface, Performance) * Register default debug pages (Movement, Surface, Performance)
* @private Internal system setup method * @category System Setup
*/ */
private RegisterDefaultPages() { private RegisterDefaultPages(): void {
this.RegisterDebugPage({ DataTableFunctionLibrary.GetDataTableRowNames(this.DebugDataTable).forEach(
PageID: E_DebugPageID.MovementInfo, arrayElement => {
Title: "Movement Constants", this.DebugDataTable.GetDataTableRow(arrayElement, row => {
Content: '', this.RegisterDebugPage(row);
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
}); });
} }
);
}
// Category: "Testing"
/** /**
* Test basic debug system functionality * Get comprehensive test data for debug system validation
* @returns True if all basic tests pass * @returns Object containing initialization status, DataTable reference, and pages array
* @private Internal testing method * @category Testing
*/ */
private TestDebugSystem() { public GetTestData(): {
Print('=== DEBUG SYSTEM TESTS ==='); IsInitialized: boolean;
DebugDataTable: DataTable<S_DebugPage>;
if (this.IsInitialized) { DebugPages: UEArray<S_DebugPage>;
Print('✅ System initialized'); } {
return {
if (this.DebugPages.length === 3) { IsInitialized: this.IsInitialized,
Print('✅ All pages registered'); DebugDataTable: this.DebugDataTable,
DebugPages: this.DebugPages,
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;
}
} }
// 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 * Initialize debug HUD system with movement component reference
* Sets up pages, creates widget, runs tests, and starts display * Sets up pages, creates widget, runs tests, and starts display
* @param MovementComponentRef - Reference to movement component to debug * @param MovementComponentRef - Reference to movement component to debug
* @public Called by BP_MainCharacter during initialization * @param ToastComponentRef - Reference to toast system for notifications
* @example * @example
* // Initialize debug HUD in main character * // Initialize debug HUD in main character
* this.DebugHUDComponent.InitializeDebugHUD(this.MovementComponent); * 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.MovementComponent = MovementComponentRef;
this.ToastComponent = ToastComponentRef;
this.IsInitialized = true; this.IsInitialized = true;
this.FrameCounter = 0; this.FrameCounter = 0;
this.LastUpdateTime = 0; this.LastUpdateTime = 0;
this.FPS = 0; this.FPS = 0;
this.RegisterDefaultPages(); this.RegisterDefaultPages();
this.CreateDebugWidget(); this.CreateDebugWidget();
this.RunAllTests();
this.DebugSettings.CurrentPageIndex = 0; this.DebugSettings.CurrentPageIndex = 0;
this.UpdateWidgetVisibility(); this.UpdateWidgetVisibility();
Print('=== DEBUG HUD INITIALIZED ==='); this.ToastComponent.ShowToast(
'Debug HUD Initialized',
E_MessageType.Success
);
this.UpdateCurrentPage(); 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;
} }

Binary file not shown.

View File

@ -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)

Binary file not shown.

View File

@ -1,7 +1,7 @@
// Content/Debug/Enums/E_DebugPageID.ts // Debug/Enums/E_DebugPageID.ts
export enum E_DebugPageID { export enum E_DebugPageID {
MovementInfo = "MovementInfo", MovementInfo = 'MovementInfo',
SurfaceInfo = "SurfaceInfo", SurfaceInfo = 'SurfaceInfo',
PerformanceInfo = "PerformanceInfo" PerformanceInfo = 'PerformanceInfo',
} }

View File

@ -1,7 +1,7 @@
// Content/Enums/E_DebugUpdateFunction.ts // Debug/Enums/E_DebugUpdateFunction.ts
export enum E_DebugUpdateFunction { export enum E_DebugUpdateFunction {
UpdateMovementPage = "UpdateMovementPage", UpdateMovementPage = 'UpdateMovementPage',
UpdateSurfacePage = "UpdateSurfacePage", UpdateSurfacePage = 'UpdateSurfacePage',
UpdatePerformancePage = "UpdatePerformancePage" UpdatePerformancePage = 'UpdatePerformancePage',
} }

BIN
Content/Debug/Structs/S_DebugColors.uasset (Stored with Git LFS)

Binary file not shown.

View File

@ -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 { E_DebugPageID } from '#root/Debug/Enums/E_DebugPageID.js';
import type {Text} from "../../types.js"; import type { E_DebugUpdateFunction } from '#root/Debug/Enums/E_DebugUpdateFunction.js';
import type {E_DebugUpdateFunction} from "../Enums/E_DebugUpdateFunction.js"; import type { Text } from '#root/UE/Text.ts';
export interface S_DebugPage { export interface S_DebugPage {
PageID: E_DebugPageID; PageID: E_DebugPageID;

View File

@ -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 { ESlateVisibility } from '#root/UE/ESlateVisibility.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_DebugSettings { export interface S_DebugSettings {
CurrentMode: E_DebugMode; CurrentMode: ESlateVisibility;
CurrentPageIndex: Integer; CurrentPageIndex: Integer;
ShowVisualDebug: boolean; ShowVisualDebug: boolean;
UpdateFrequency: Float; UpdateFrequency: Float;

Binary file not shown.

View File

@ -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,
}
)
);

BIN
Content/Debug/Tables/DT_DebugPages.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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();
}

BIN
Content/Debug/Tests/FT_DebugNavigation.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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,
};
}

BIN
Content/Debug/Tests/FT_DebugPageContentGenerator.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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();
}

BIN
Content/Debug/Tests/FT_DebugSystem.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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 type { AC_Movement } from '#root/Movement/Components/AC_Movement.js';
import {TextBox, Widget} from "../../classes.js"; import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
import type {Text} from "../../types.js"; import type { Text } from '#root/UE/Text.ts';
import {IsValid} from "../../functions.js"; import { TextBlock } from '#root/UE/TextBlock.ts';
import { UserWidget } from '#root/UE/UserWidget.ts';
/** /**
* Debug HUD Widget for displaying system information * Debug HUD Widget for displaying system information
* Provides real-time debug information display with multiple pages * Provides real-time debug information display with multiple pages
* Part of the deterministic movement system debug infrastructure * Part of the deterministic movement system debug infrastructure
*/ */
export class WBP_DebugHUD extends Widget{ export class WBP_DebugHUD extends UserWidget {
// Category: "Components" // ════════════════════════════════════════════════════════════════════════════════════════
/** // GRAPHS
* Reference to movement component for accessing debug data // ════════════════════════════════════════════════════════════════════════════════════════
* Set by AC_DebugHUD during initialization
*/
public MovementComponent: AC_Movement | null = null;
// Category: "PageData" // ────────────────────────────────────────────────────────────────────────────────────────
/** // EventGraph
* Current page title text // ────────────────────────────────────────────────────────────────────────────────────────
* Updated by AC_DebugHUD when switching pages
*/
private HeaderText: Text = "Debug HUD";
// Category: "PageData"
/** /**
* Current page content text * Widget construction event - initializes all text displays
* Contains the main debug information for current page * Called automatically when widget is created
*/ */
private ContentText: Text = "Text"; EventConstruct(): void {
this.UpdateHeaderDisplay();
this.UpdateContentDisplay();
this.UpdateNavigationDisplay();
}
// ════════════════════════════════════════════════════════════════════════════════════════
// FUNCTIONS
// ════════════════════════════════════════════════════════════════════════════════════════
// Category: "PageData"
/** /**
* Navigation instructions text * Update header text box with current header text
* Shows current page index and navigation controls * @category Display Updates
*/ */
private NavigationText: Text = "Navigation"; private UpdateHeaderDisplay(): void {
if (SystemLibrary.IsValid(this.HeaderTextBox)) {
// 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)) {
this.HeaderTextBox.SetText(this.HeaderText); this.HeaderTextBox.SetText(this.HeaderText);
} }
} }
// Category: "Display Updates"
/** /**
* Update header text box with current header text * Update content text box with current content text
* Called automatically when header text changes * @category Display Updates
* @private Internal display update method
*/ */
private UpdateContentDisplay() { private UpdateContentDisplay(): void {
if (IsValid(this.ContentTextBox)) { if (SystemLibrary.IsValid(this.ContentTextBox)) {
this.ContentTextBox.SetText(this.ContentText); this.ContentTextBox.SetText(this.ContentText);
} }
} }
// Category: "Display Updates"
/** /**
* Update navigation text box with current navigation text * Update navigation text box with current navigation text
* Called automatically when navigation text changes * @category Display Updates
* @private Internal display update method
*/ */
private UpdateNavigationDisplay() { private UpdateNavigationDisplay(): void {
if (IsValid(this.NavigationTextBox)) { if (SystemLibrary.IsValid(this.NavigationTextBox)) {
this.NavigationTextBox.SetText(this.NavigationText); this.NavigationTextBox.SetText(this.NavigationText);
} }
} }
// Category: "Public Interface"
/** /**
* Set new header text and update display * Set new header text and update display
* @param NewHeaderText - New title text for current debug page * @param NewHeaderText - New title text for current debug page
* @example * @example
* // Set movement page header * // Set movement page header
* SetHeaderText("Movement Constants") * SetHeaderText("Movement Constants")
* @category Public Interface
*/ */
public SetHeaderText(NewHeaderText: string): void { public SetHeaderText(NewHeaderText: Text): void {
this.HeaderText = NewHeaderText; this.HeaderText = NewHeaderText;
this.UpdateHeaderDisplay(); this.UpdateHeaderDisplay();
} }
// Category: "Public Interface"
/** /**
* Set new content text and update display * Set new content text and update display
* @param NewContentText - New debug content (supports multi-line text) * @param NewContentText - New debug content (supports multi-line text)
* @example * @example
* // Set movement constants content * // Set movement constants content
* SetContentText("Max Speed: 600\nAcceleration: 2000\nFriction: 8.0") * 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.ContentText = NewContentText;
this.UpdateContentDisplay(); this.UpdateContentDisplay();
} }
// Category: "Public Interface"
/** /**
* Set new navigation text and update display * Set new navigation text and update display
* @param NewNavigationText - Navigation instructions and page info * @param NewNavigationText - Navigation instructions and page info
* @example * @example
* // Set navigation with page counter * // Set navigation with page counter
* SetNavigationText("Page 1/3 | PageUp/PageDown - Navigate") * SetNavigationText("Page 1/3 | PageUp/PageDown - Navigate")
* @category Public Interface
*/ */
public SetNavigationText(NewNavigationText: string): void { public SetNavigationText(NewNavigationText: Text): void {
this.NavigationText = NewNavigationText; this.NavigationText = NewNavigationText;
this.UpdateNavigationDisplay(); 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)

Binary file not shown.

BIN
Content/Debug/UI/WBP_TestResult.uasset (Stored with Git LFS)

Binary file not shown.

View File

@ -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'));

View File

@ -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'));

View File

@ -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'));

View File

@ -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');

View File

@ -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');

View File

@ -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');

View File

@ -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');

View File

@ -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'
);

View File

@ -0,0 +1,7 @@
// Input/Enums/E_InputDeviceType.ts
export enum E_InputDeviceType {
Unknown = 'Unknown',
Keyboard = 'Keyboard',
Gamepad = 'Gamepad',
}

View File

@ -1,18 +1,24 @@
// Content/Input/IMC_Default.ts // Input/IMC_Default.ts
import type {InputMappingContext} from "../types.js"; import { IA_LeftTrigger } from '#root/Input/Actions/IA_LeftTrigger.ts';
import {IA_LeftTrigger} from "./Actions/IA_LeftTrigger.js"; import { IA_Move } from '#root/Input/Actions/IA_Move.ts';
import {IA_RightTrigger} from "./Actions/IA_RightTrigger.js"; import { IA_NextDebugMode } from '#root/Input/Actions/IA_NextDebugMode.ts';
import {IA_NextDebugMode} from "./Actions/IA_NextDebugMode.js"; import { IA_PrevDebugMode } from '#root/Input/Actions/IA_PrevDebugMode.ts';
import {IA_PrevDebugMode} from "./Actions/IA_PrevDebugMode.js"; import { IA_RightTrigger } from '#root/Input/Actions/IA_RightTrigger.ts';
import {IA_ToggleHUD} from "./Actions/IA_ToggleHUD.js"; import { IA_ToggleHUD } from '#root/Input/Actions/IA_ToggleHUD.ts';
import {IA_ToggleVisualDebug} from "./Actions/IA_ToggleVisualDebug.js"; 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 = [ export const IMC_Default = new InputMappingContext();
IA_LeftTrigger,
IA_RightTrigger, IMC_Default.mapKey(IA_LeftTrigger, 'IA_LeftTrigger' as unknown as Key);
IA_NextDebugMode, IMC_Default.mapKey(IA_RightTrigger, 'IA_RightTrigger' as unknown as Key);
IA_PrevDebugMode, IMC_Default.mapKey(IA_NextDebugMode, 'IA_NextDebugMode' as unknown as Key);
IA_ToggleHUD, 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,
] 'IA_ToggleVisualDebug' as unknown as Key
);
IMC_Default.mapKey(IA_Move, 'IA_Move' as unknown as Key);

View File

@ -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)

Binary file not shown.

View File

@ -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();

BIN
Content/Math/Libraries/BFL_Vectors.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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 { BFL_Vectors } from '#root/Math/Libraries/BFL_Vectors.ts';
import type {S_AngleThresholds} from "../Structs/S_AngleThresholds.js"; import { E_SurfaceType } from '#root/Movement/Enums/E_SurfaceType.ts';
import type {Float, Vector} from "../../types.js"; import { S_AngleThresholds } from '#root/Movement/Structs/S_AngleThresholds.ts';
import {acos, cos, D2R, Dot, Print, sin} from "../../functions.js"; import type { S_MovementConstants } from '#root/Movement/Structs/S_MovementConstants.ts';
import {E_SurfaceType} from "../Enums/E_SurfaceType.js"; import { ActorComponent } from '#root/UE/ActorComponent.ts';
import type {S_SurfaceTestCase} from "../Structs/S_SurfaceTestCase.js"; 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 * Movement System Component
* @param Vector1 - First normalized vector * Core deterministic movement system for 3D platformer
* @param Vector2 - Second normalized vector * Handles surface classification and movement physics calculations
* @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
*/ */
GetAngleBetweenVectors(Vector1: Vector, Vector2: Vector) { export class AC_Movement extends ActorComponent {
return acos(Dot(Vector1, Vector2)); // ════════════════════════════════════════════════════════════════════════════════════════
} // 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 * Classify surface type based on normal vector
* @param SurfaceNormal - Normalized surface normal vector * @param SurfaceNormal - Normalized surface normal vector
@ -96,140 +26,163 @@ export class AC_Movement {
* @returns Surface type classification * @returns Surface type classification
* @example * @example
* // Classify flat ground * // 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 { public ClassifySurface(
const SurfaceAngle = this.GetSurfaceAngle(SurfaceNormal); 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; return E_SurfaceType.Walkable;
} else if (SurfaceAngle <= AngleThresholds.SteepSlope) { } else if (IsSteepSlopeAngle(AngleThresholds.SteepSlope)) {
return E_SurfaceType.SteepSlope; return E_SurfaceType.SteepSlope;
} else if (SurfaceAngle <= AngleThresholds.Wall) { } else if (IsWallAngle(AngleThresholds.Wall)) {
return E_SurfaceType.Wall; return E_SurfaceType.Wall;
} else { } else {
return E_SurfaceType.Ceiling; return E_SurfaceType.Ceiling;
} }
} }
// Category: "Surface Detection"
// Pure: true
/** /**
* Check if surface allows normal walking movement * Check if surface allows normal walking movement
* @param SurfaceType - Surface type to check * @param SurfaceType - Surface type to check
* @returns True if surface is walkable * @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; return SurfaceType === E_SurfaceType.Walkable;
} }
// Category: "Surface Detection"
// Pure: true
/** /**
* Check if surface causes sliding behavior * Check if surface causes sliding behavior
* @param SurfaceType - Surface type to check * @param SurfaceType - Surface type to check
* @returns True if surface is steep slope * @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; return SurfaceType === E_SurfaceType.SteepSlope;
} }
// Category: "Surface Detection"
// Pure: true
/** /**
* Check if surface blocks movement (collision) * Check if surface blocks movement (collision)
* @param SurfaceType - Surface type to check * @param SurfaceType - Surface type to check
* @returns True if surface is a wall * @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; return SurfaceType === E_SurfaceType.Wall;
} }
// Category: "Surface Detection"
// Pure: true
/** /**
* Check if surface is overhead (ceiling) * Check if surface is overhead (ceiling)
* @param SurfaceType - Surface type to check * @param SurfaceType - Surface type to check
* @returns True if surface is ceiling * @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; return SurfaceType === E_SurfaceType.Ceiling;
} }
// Category: "Surface Detection"
// Pure: true
/** /**
* Check if no surface detected (airborne state) * Check if no surface detected (airborne state)
* @param SurfaceType - Surface type to check * @param SurfaceType - Surface type to check
* @returns True if no surface contact * @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; 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 * Initialize movement system with angle conversion
* @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
* Converts degree thresholds to radians for runtime performance * 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.IsInitialized = true;
this.AngleThresholdsRads = { this.AngleThresholdsRads = {
Walkable: D2R(this.AngleThresholdsDegrees.Walkable), Walkable: MathLibrary.DegreesToRadians(
SteepSlope: D2R(this.AngleThresholdsDegrees.SteepSlope), this.AngleThresholdsDegrees.Walkable
Wall: D2R(this.AngleThresholdsDegrees.Wall), ),
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(); * Surface classification angle thresholds in degrees
this.TestSurfaceClassification(); * 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;
} }

Binary file not shown.

View File

@ -1,9 +1,9 @@
// Content/Movement/Enums/E_SurfaceType.ts // Movement/Enums/E_SurfaceType.ts
export enum E_SurfaceType { export enum E_SurfaceType {
None = "None", None = 'None',
Walkable = "Walkable", Walkable = 'Walkable',
SteepSlope = "SteepSlope", SteepSlope = 'SteepSlope',
Wall = "Wall", Wall = 'Wall',
Ceiling = "Ceiling" Ceiling = 'Ceiling',
} }

View File

@ -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 { export interface S_AngleThresholds {
Walkable: Float, Walkable: Float;
SteepSlope: Float, SteepSlope: Float;
Wall: Float, Wall: Float;
} }

View File

@ -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 { export interface S_MovementConstants {
MaxSpeed: Float; MaxSpeed: Float;

View File

@ -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 '#root/Movement/Enums/E_SurfaceType.ts';
import type {E_SurfaceType} from "../Enums/E_SurfaceType.js"; import type { Float } from '#root/UE/Float.ts';
export interface S_SurfaceTestCase { export interface S_SurfaceTestCase {
AngleDegrees: Float; AngleDegrees: Float;

View File

@ -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,
};
}

BIN
Content/Movement/Tests/FT_SurfaceClassification.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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([]);
}

BIN
Content/Toasts/Components/AC_ToastSystem.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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);
}
}

Binary file not shown.

View File

@ -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 { Float } from '#root/UE/Float.ts';
import type {E_MessageType} from "../../UI/Enums/E_MessageType.js"; 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 { export interface S_ToastMessage {
ID: Integer; ID: Integer;

View File

@ -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 { export interface S_ToastSettings {
MaxVisibleToasts: Integer; MaxVisibleToasts: Integer;

View File

@ -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();
}

BIN
Content/Toasts/Tests/FT_ToastLimit.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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;
}

BIN
Content/Toasts/Tests/FT_ToastsDurationHandling.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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 = '';
}

BIN
Content/Toasts/Tests/FT_ToastsEdgeCases.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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();
}

BIN
Content/Toasts/Tests/FT_ToastsSystemInitialization.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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,
]);
}

BIN
Content/Toasts/Tests/FT_ToastsToastCreation.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1,119 +1,113 @@
// Content/Toasts/UI/WBP_Toast.ts // Toasts/UI/WBP_Toast.ts
import {Border, TextBox, Widget} from "../../classes.js"; import { Border } from '#root/UE/Border.ts';
import {E_MessageType} from "../../UI/Enums/E_MessageType.js"; import { MathLibrary } from '#root/UE/MathLibrary.ts';
import type {Color, Text} from "../../types.js"; import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
import {IsValid} from "../../functions.js"; 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; private UpdateBackgroundStyling(): void {
if (SystemLibrary.IsValid(this.BackgroundPanel)) {
// Category: "UI Components" this.BackgroundPanel.SetBrushColor(
/** MathLibrary.ColorToLinearColor(
* Text box widget for displaying toast message BFL_Colors.GetColorByMessageType(this.ToastType, 100)
* 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
} }
} }
// Category: "Display Updates"
/** /**
* Update message text box with current message * Update message text box with current message text
* @private Internal display update method * @category Display Updates
*/ */
private UpdateMessageDisplay(): void { private UpdateMessageDisplay(): void {
if (IsValid(this.MessageTextBox)) { if (SystemLibrary.IsValid(this.MessageTextBox)) {
this.MessageTextBox.SetText(this.MessageText); 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 * Set toast message and update display
* @param Message - New message text * @param Message - New message text
* @example * @example
* // Set success message * // Set success message
* SetMessage("Test completed successfully!") * SetMessage("Test completed successfully!")
* @category Public Interface
*/ */
public SetMessage(Message: Text): void { public SetMessage(Message: Text): void {
this.MessageText = Message; this.MessageText = Message;
this.UpdateMessageDisplay(); this.UpdateMessageDisplay();
} }
// Category: "Public Interface"
/** /**
* Set toast type and update styling * Set toast type and update styling
* @param Type - New toast type * @param Type - New toast type
* @example * @example
* // Set as error toast * // Set as error toast
* SetToastType(E_ToastType.Error) * SetToastType(E_MessageType.Error)
* @category Public Interface
*/ */
public SetToastType(Type: E_MessageType): void { public SetToastType(Type: E_MessageType): void {
this.ToastType = Type; this.ToastType = Type;
this.UpdateBackgroundStyling(); this.UpdateBackgroundStyling();
} }
// Category: "Widget Lifecycle"
/** /**
* Initialize toast widget with message and type * Initialize toast widget with message and type
* Adds widget to viewport for display
* @param Message - Initial message text * @param Message - Initial message text
* @param Type - Initial toast type * @param Type - Initial toast type
* @public Called when creating new toast * @category Widget Lifecycle
*/ */
public InitializeToast(Message: Text, Type: E_MessageType): void { public InitializeToast(Message: Text, Type: E_MessageType): void {
this.SetMessage(Message); this.SetMessage(Message);
this.SetToastType(Type); this.SetToastType(Type);
this.AddToViewport(); 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)

Binary file not shown.

View File

@ -1,61 +1,76 @@
// Content/Toasts/UI/WBP_ToastContainer.ts // Toasts/UI/WBP_ToastContainer.ts
import {VerticalBox, Widget} from "../../classes.js"; import { WBP_Toast } from '#root/Toasts/UI/WBP_Toast.ts';
import {ESlateVisibility} from "../../enums.js"; import { CreateWidget } from '#root/UE/CteateWidget.ts';
import type {Text} from "../../types.js"; import { ESlateVisibility } from '#root/UE/ESlateVisibility.ts';
import type {E_MessageType} from "../../UI/Enums/E_MessageType.js"; import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
import {WBP_Toast} from "./WBP_Toast.js"; import type { Text } from '#root/UE/Text.ts';
import {AddToArray, CreateWidget, IsValid, RemoveItemFromArray} from "../../functions.js"; 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 * Toast Container Widget
* Handles spacing and positioning automatically * 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 * Remove toast from container and hide it
* Sets up vertical box with proper spacing * @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 { public InitializeContainer(): void {
this.SetVisibility(ESlateVisibility.Visible); this.SetVisibility(ESlateVisibility.Visible);
this.AddToViewport(); this.AddToViewport();
} }
// Category: "Toast Management" // ════════════════════════════════════════════════════════════════════════════════════════
/** // VARIABLES
* 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;
}
// Category: "Toast Management"
/** /**
* Remove toast from container * Vertical layout container for automatic toast stacking
* @param Toast - Toast widget to remove * Handles spacing and positioning automatically
* @category Layout
*/ */
public RemoveToast(Toast: WBP_Toast): void { private ToastVerticalBox = new VerticalBox();
if (IsValid(Toast)) {
RemoveItemFromArray(this.ChildToasts, Toast); /**
Toast.SetVisibility(ESlateVisibility.Hidden); * Array of managed child toast widgets
Toast.RemoveFromParent(); * @category Container Management
} */
} private ChildToasts: UEArray<WBP_Toast> = new UEArray([]);
} }

Binary file not shown.

10
Content/UE/Actor.ts Normal file
View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

16
Content/UE/Border.ts Normal file
View File

@ -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);
}
}

3
Content/UE/Byte.ts Normal file
View File

@ -0,0 +1,3 @@
// UE/Byte.ts
export type Byte = number;

5
Content/UE/Cast.ts Normal file
View File

@ -0,0 +1,5 @@
// Content/Cast.ts
export function Cast<T>(obj: unknown): T | null {
return (obj as T) || null;
}

15
Content/UE/Color.ts Normal file
View File

@ -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();
}
}

View File

@ -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);
}
}

15
Content/UE/Controller.ts Normal file
View File

@ -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;
}
}

View File

@ -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();
}

10
Content/UE/DataAsset.ts Normal file
View File

@ -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);
}
}

41
Content/UE/DataTable.ts Normal file
View File

@ -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?.();
}
}
}

View File

@ -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();

View File

@ -0,0 +1,10 @@
// UE/EFunctionalTestResult.ts
export enum EFunctionalTestResult {
'Default' = 'Default',
'Invalid' = 'Invalid',
'Error' = 'Error',
'Running' = 'Running',
'Failed' = 'Failed',
'Succeeded' = 'Succeeded',
}

View File

@ -0,0 +1,9 @@
// UE/ESlateVisibility.ts
export enum ESlateVisibility {
Visible = 'Visible',
Collapsed = 'Collapsed',
Hidden = 'Hidden',
NotHitTestableSelfAndAllChildren = 'NotHitTestableSelfAndAllChildren',
NotHitTestableSelfOnly = 'NotHitTestableSelfOnly',
}

View File

@ -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;
}
}

View File

@ -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);
}
}

3
Content/UE/Float.ts Normal file
View File

@ -0,0 +1,3 @@
// UE/Float.ts
export type Float = number;

View File

@ -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}`
);
}
}

View File

@ -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);
}
}

11
Content/UE/Info.ts Normal file
View File

@ -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);
}
}

11
Content/UE/InputAction.ts Normal file
View File

@ -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);
}
}

View File

@ -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
);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

3
Content/UE/Integer.ts Normal file
View File

@ -0,0 +1,3 @@
// UE/Integer.ts
export type Integer = number;

18
Content/UE/Key.ts Normal file
View File

@ -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);
}
}
}

19
Content/UE/LinearColor.ts Normal file
View File

@ -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;
}
}

View File

@ -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