acesesibiliti

This commit is contained in:
2025-12-12 22:46:38 +01:00
parent 3809ee2c97
commit 93757fc8c4
20 changed files with 5740 additions and 89 deletions

View File

@@ -472,6 +472,26 @@ class GameScene extends Phaser.Scene {
console.log('👁️ Initializing Visual Sound Cue System...');
this.visualSoundCueSystem = new VisualSoundCueSystem(this);
// Initialize Input Remapping System (for custom controls)
console.log('🎮 Initializing Input Remapping System...');
this.inputRemapping = new InputRemappingSystem(this);
// Initialize Screen Reader System (for blind/visually impaired players)
console.log('🔊 Initializing Screen Reader System...');
this.screenReader = new ScreenReaderSystem(this);
// Initialize Dyslexia Support System
console.log('📖 Initializing Dyslexia Support System...');
this.dyslexiaSupport = new DyslexiaSupportSystem(this);
// Initialize ADHD/Autism Support System
console.log('🧠 Initializing ADHD/Autism Support System...');
this.adhdAutismSupport = new ADHDAutismSupportSystem(this);
// Initialize Motor Accessibility System
console.log('🦾 Initializing Motor Accessibility System...');
this.motorAccessibility = new MotorAccessibilitySystem(this);
// Show epilepsy warning on first launch
const hasSeenWarning = localStorage.getItem('novafarma_epilepsy_warning');
if (!hasSeenWarning) {
@@ -841,6 +861,12 @@ class GameScene extends Phaser.Scene {
// Visual Sound Cue System Update
if (this.visualSoundCueSystem) this.visualSoundCueSystem.update();
// Screen Reader System Update
if (this.screenReader) this.screenReader.update();
// Motor Accessibility System Update
if (this.motorAccessibility) this.motorAccessibility.update();
// Update NPCs
for (const npc of this.npcs) {
if (npc.update) npc.update(delta);

View File

@@ -0,0 +1,154 @@
/**
* ADHD/AUTISM SUPPORT SYSTEM
* Provides focus assistance and predictable UI for neurodivergent players
*/
class ADHDAutismSupportSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// Settings
this.settings = {
focusMode: false, // Hide non-essential UI
reminderSystem: true, // Task reminders
simplifiedMenus: false, // Simplified navigation
noJumpScares: true, // Disable sudden events
predictableUI: true, // Consistent UI patterns
reducedAnimations: false, // Less motion
taskTimer: true, // Visual task timer
breakReminders: true, // Regular break reminders
soundWarnings: true // Warn before loud sounds
};
// Reminder state
this.reminders = [];
this.lastBreakReminder = Date.now();
this.breakInterval = 30 * 60 * 1000; // 30 minutes
// Focus mode overlay
this.focusOverlay = null;
this.loadSettings();
this.init();
console.log('✅ ADHD/Autism Support System initialized');
}
init() {
if (this.settings.focusMode) {
this.enableFocusMode();
}
if (this.settings.breakReminders) {
this.startBreakReminders();
}
}
/**
* Enable focus mode (hide non-essential UI)
*/
enableFocusMode() {
this.settings.focusMode = true;
// Create dark overlay for non-focused areas
if (!this.focusOverlay) {
this.createFocusOverlay();
}
console.log('🎯 Focus mode enabled');
this.saveSettings();
}
/**
* Disable focus mode
*/
disableFocusMode() {
this.settings.focusMode = false;
if (this.focusOverlay) {
this.focusOverlay.setVisible(false);
}
console.log('🎯 Focus mode disabled');
this.saveSettings();
}
/**
* Create focus mode overlay
*/
createFocusOverlay() {
// Implementation would create a vignette effect
console.log('Creating focus overlay...');
}
/**
* Add reminder
*/
addReminder(text, time) {
this.reminders.push({ text, time });
console.log(`⏰ Reminder added: ${text} at ${time}`);
}
/**
* Start break reminders
*/
startBreakReminders() {
setInterval(() => {
if (this.settings.breakReminders) {
this.showBreakReminder();
}
}, this.breakInterval);
}
/**
* Show break reminder
*/
showBreakReminder() {
const message = 'Take a break! You\'ve been playing for 30 minutes.';
if (this.scene.screenReader) {
this.scene.screenReader.speak(message, 'alert');
}
console.log('⏰ Break reminder shown');
}
/**
* Toggle simplified menus
*/
toggleSimplifiedMenus() {
this.settings.simplifiedMenus = !this.settings.simplifiedMenus;
this.saveSettings();
console.log(`📋 Simplified menus: ${this.settings.simplifiedMenus ? 'ON' : 'OFF'}`);
}
/**
* Toggle no jump scares
*/
toggleNoJumpScares() {
this.settings.noJumpScares = !this.settings.noJumpScares;
this.saveSettings();
console.log(`👻 No jump scares: ${this.settings.noJumpScares ? 'ON' : 'OFF'}`);
}
/**
* Save settings
*/
saveSettings() {
localStorage.setItem('novafarma_adhd_autism_support', JSON.stringify(this.settings));
}
/**
* Load settings
*/
loadSettings() {
const saved = localStorage.getItem('novafarma_adhd_autism_support');
if (saved) {
this.settings = { ...this.settings, ...JSON.parse(saved) };
}
}
destroy() {
console.log('🧠 ADHD/Autism Support System destroyed');
}
}

View File

@@ -0,0 +1,431 @@
/**
* DYSLEXIA SUPPORT SYSTEM
* Provides reading assistance for players with dyslexia
*/
class DyslexiaSupportSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// OpenDyslexic font (loaded via CSS)
this.fonts = {
'default': 'Arial, sans-serif',
'opendyslexic': 'OpenDyslexic, Arial, sans-serif',
'comic-sans': 'Comic Sans MS, cursive',
'verdana': 'Verdana, sans-serif'
};
// Text size presets
this.textSizes = {
'small': { base: 14, ui: 16, subtitle: 18 },
'medium': { base: 16, ui: 18, subtitle: 20 },
'large': { base: 20, ui: 22, subtitle: 24 },
'extra-large': { base: 24, ui: 26, subtitle: 28 }
};
// Line spacing presets
this.lineSpacings = {
'normal': 1.2,
'increased': 1.5,
'double': 2.0,
'triple': 3.0
};
// Settings
this.settings = {
enabled: false,
font: 'default',
textSize: 'medium',
lineSpacing: 'normal',
highlightText: false,
simplifiedLanguage: false,
textToSpeech: false,
colorOverlay: false,
overlayColor: '#ffffcc', // Light yellow
overlayOpacity: 0.3
};
// Text simplification dictionary
this.simplificationDict = {
'acquire': 'get',
'utilize': 'use',
'commence': 'start',
'terminate': 'end',
'construct': 'build',
'eliminate': 'remove',
'approximately': 'about',
'insufficient': 'not enough',
'maximum': 'most',
'minimum': 'least',
'purchase': 'buy',
'require': 'need',
'additional': 'more',
'previous': 'last',
'subsequent': 'next'
};
this.loadSettings();
this.init();
console.log('✅ Dyslexia Support System initialized');
}
init() {
// Load OpenDyslexic font
this.loadOpenDyslexicFont();
// Apply saved settings
if (this.settings.enabled) {
this.applySettings();
}
}
/**
* Load OpenDyslexic font
*/
loadOpenDyslexicFont() {
// Check if font is already loaded
if (document.getElementById('opendyslexic-font')) return;
// Create style element
const style = document.createElement('style');
style.id = 'opendyslexic-font';
style.textContent = `
@font-face {
font-family: 'OpenDyslexic';
src: url('https://cdn.jsdelivr.net/npm/opendyslexic@3.0.1/OpenDyslexic-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/opendyslexic@3.0.1/OpenDyslexic-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'OpenDyslexic';
src: url('https://cdn.jsdelivr.net/npm/opendyslexic@3.0.1/OpenDyslexic-Bold.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/opendyslexic@3.0.1/OpenDyslexic-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
}
`;
document.head.appendChild(style);
console.log('📖 OpenDyslexic font loaded');
}
/**
* Apply all settings
*/
applySettings() {
this.applyFont();
this.applyTextSize();
this.applyLineSpacing();
this.applyColorOverlay();
}
/**
* Apply font setting
*/
applyFont() {
const font = this.fonts[this.settings.font];
// Apply to game canvas
const canvas = document.querySelector('canvas');
if (canvas) {
canvas.style.fontFamily = font;
}
// Apply to all text elements in game
this.updateGameTexts();
console.log(`📖 Font set to: ${this.settings.font}`);
}
/**
* Apply text size setting
*/
applyTextSize() {
const sizes = this.textSizes[this.settings.textSize];
// Update game text sizes
this.updateGameTextSizes(sizes);
console.log(`📏 Text size set to: ${this.settings.textSize}`);
}
/**
* Apply line spacing setting
*/
applyLineSpacing() {
const spacing = this.lineSpacings[this.settings.lineSpacing];
// Apply to game texts
this.updateGameLineSpacing(spacing);
console.log(`📐 Line spacing set to: ${this.settings.lineSpacing} (${spacing})`);
}
/**
* Apply color overlay
*/
applyColorOverlay() {
if (!this.settings.colorOverlay) {
this.removeColorOverlay();
return;
}
// Create or update overlay
let overlay = document.getElementById('dyslexia-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'dyslexia-overlay';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.pointerEvents = 'none';
overlay.style.zIndex = '9998';
document.body.appendChild(overlay);
}
overlay.style.backgroundColor = this.settings.overlayColor;
overlay.style.opacity = this.settings.overlayOpacity;
console.log('🎨 Color overlay applied');
}
/**
* Remove color overlay
*/
removeColorOverlay() {
const overlay = document.getElementById('dyslexia-overlay');
if (overlay) {
overlay.remove();
}
}
/**
* Update game texts with new font
*/
updateGameTexts() {
// This would update all Phaser text objects
// Implementation depends on game structure
console.log('🔄 Updating game texts...');
}
/**
* Update game text sizes
*/
updateGameTextSizes(sizes) {
// Update subtitle system if available
if (this.scene.visualSoundCues) {
// Map to subtitle sizes
const sizeMap = {
'small': 'small',
'medium': 'medium',
'large': 'large',
'extra-large': 'very-large'
};
this.scene.visualSoundCues.setSubtitleSize(sizeMap[this.settings.textSize]);
}
console.log('🔄 Updating game text sizes...');
}
/**
* Update game line spacing
*/
updateGameLineSpacing(spacing) {
// This would update line spacing for all text objects
console.log(`🔄 Updating line spacing to ${spacing}...`);
}
/**
* Simplify text for easier reading
*/
simplifyText(text) {
if (!this.settings.simplifiedLanguage) return text;
let simplified = text;
for (const [complex, simple] of Object.entries(this.simplificationDict)) {
const regex = new RegExp(`\\b${complex}\\b`, 'gi');
simplified = simplified.replace(regex, simple);
}
return simplified;
}
/**
* Read text aloud (if TTS enabled)
*/
readAloud(text) {
if (!this.settings.textToSpeech) return;
// Use screen reader system if available
if (this.scene.screenReader) {
this.scene.screenReader.speak(text);
}
}
/**
* Set font
*/
setFont(fontName) {
if (!this.fonts[fontName]) {
console.error(`Font "${fontName}" not available`);
return;
}
this.settings.font = fontName;
this.applyFont();
this.saveSettings();
}
/**
* Set text size
*/
setTextSize(size) {
if (!this.textSizes[size]) {
console.error(`Text size "${size}" not available`);
return;
}
this.settings.textSize = size;
this.applyTextSize();
this.saveSettings();
}
/**
* Set line spacing
*/
setLineSpacing(spacing) {
if (!this.lineSpacings[spacing]) {
console.error(`Line spacing "${spacing}" not available`);
return;
}
this.settings.lineSpacing = spacing;
this.applyLineSpacing();
this.saveSettings();
}
/**
* Toggle simplified language
*/
toggleSimplifiedLanguage() {
this.settings.simplifiedLanguage = !this.settings.simplifiedLanguage;
this.saveSettings();
console.log(`📝 Simplified language: ${this.settings.simplifiedLanguage ? 'ON' : 'OFF'}`);
}
/**
* Toggle text-to-speech
*/
toggleTextToSpeech() {
this.settings.textToSpeech = !this.settings.textToSpeech;
this.saveSettings();
console.log(`🔊 Text-to-speech: ${this.settings.textToSpeech ? 'ON' : 'OFF'}`);
}
/**
* Toggle color overlay
*/
toggleColorOverlay() {
this.settings.colorOverlay = !this.settings.colorOverlay;
this.applyColorOverlay();
this.saveSettings();
console.log(`🎨 Color overlay: ${this.settings.colorOverlay ? 'ON' : 'OFF'}`);
}
/**
* Set overlay color
*/
setOverlayColor(color) {
this.settings.overlayColor = color;
if (this.settings.colorOverlay) {
this.applyColorOverlay();
}
this.saveSettings();
}
/**
* Set overlay opacity
*/
setOverlayOpacity(opacity) {
this.settings.overlayOpacity = Phaser.Math.Clamp(opacity, 0, 1);
if (this.settings.colorOverlay) {
this.applyColorOverlay();
}
this.saveSettings();
}
/**
* Enable dyslexia support
*/
enable() {
this.settings.enabled = true;
this.applySettings();
this.saveSettings();
console.log('✅ Dyslexia support enabled');
}
/**
* Disable dyslexia support
*/
disable() {
this.settings.enabled = false;
this.removeColorOverlay();
this.saveSettings();
console.log('❌ Dyslexia support disabled');
}
/**
* Get available fonts
*/
getAvailableFonts() {
return Object.keys(this.fonts);
}
/**
* Get available text sizes
*/
getAvailableTextSizes() {
return Object.keys(this.textSizes);
}
/**
* Get available line spacings
*/
getAvailableLineSpacings() {
return Object.keys(this.lineSpacings);
}
/**
* Save settings to localStorage
*/
saveSettings() {
localStorage.setItem('novafarma_dyslexia_support', JSON.stringify(this.settings));
}
/**
* Load settings from localStorage
*/
loadSettings() {
const saved = localStorage.getItem('novafarma_dyslexia_support');
if (saved) {
try {
this.settings = { ...this.settings, ...JSON.parse(saved) };
console.log('✅ Dyslexia support settings loaded');
} catch (error) {
console.error('❌ Failed to load dyslexia support settings:', error);
}
}
}
/**
* Destroy system
*/
destroy() {
this.removeColorOverlay();
console.log('📖 Dyslexia Support System destroyed');
}
}

View File

@@ -0,0 +1,525 @@
/**
* INPUT REMAPPING SYSTEM
* Allows players to customize keyboard and controller bindings
* Supports multiple control profiles and one-handed layouts
*/
class InputRemappingSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// Default key bindings
this.defaultBindings = {
// Movement
'move_up': ['W', 'UP'],
'move_down': ['S', 'DOWN'],
'move_left': ['A', 'LEFT'],
'move_right': ['D', 'RIGHT'],
// Actions
'interact': ['E', 'SPACE'],
'attack': ['MOUSE_LEFT', 'J'],
'use_tool': ['MOUSE_LEFT', 'J'],
'cancel': ['ESC', 'X'],
'confirm': ['ENTER', 'E'],
// Inventory & UI
'inventory': ['I', 'TAB'],
'crafting': ['C'],
'map': ['M'],
'quest_log': ['Q'],
'pause': ['ESC', 'P'],
// Tools
'tool_1': ['1'],
'tool_2': ['2'],
'tool_3': ['3'],
'tool_4': ['4'],
'tool_5': ['5'],
// Quick actions
'quick_heal': ['H'],
'quick_eat': ['F'],
'sprint': ['SHIFT'],
'crouch': ['CTRL'],
// Camera
'zoom_in': ['PLUS', 'MOUSE_WHEEL_UP'],
'zoom_out': ['MINUS', 'MOUSE_WHEEL_DOWN'],
'camera_reset': ['R']
};
// Controller bindings (Xbox/PlayStation layout)
this.controllerBindings = {
'move': 'LEFT_STICK',
'camera': 'RIGHT_STICK',
'interact': 'A', // Xbox A / PS Cross
'attack': 'X', // Xbox X / PS Square
'cancel': 'B', // Xbox B / PS Circle
'inventory': 'Y', // Xbox Y / PS Triangle
'sprint': 'LB',
'use_tool': 'RT',
'quick_menu': 'LT',
'pause': 'START',
'map': 'SELECT'
};
// Control profiles
this.profiles = {
'default': JSON.parse(JSON.stringify(this.defaultBindings)),
'wasd': JSON.parse(JSON.stringify(this.defaultBindings)),
'arrows': this.createArrowsProfile(),
'left-handed': this.createLeftHandedProfile(),
'right-handed': this.createRightHandedProfile(),
'custom-1': JSON.parse(JSON.stringify(this.defaultBindings)),
'custom-2': JSON.parse(JSON.stringify(this.defaultBindings)),
'custom-3': JSON.parse(JSON.stringify(this.defaultBindings))
};
// Current active profile
this.activeProfile = 'default';
this.currentBindings = this.profiles[this.activeProfile];
// Rebinding state
this.isRebinding = false;
this.rebindingAction = null;
this.rebindingCallback = null;
// Input buffer for detecting key presses
this.inputBuffer = [];
this.maxBufferSize = 10;
this.loadSettings();
this.init();
console.log('✅ Input Remapping System initialized');
}
init() {
// Set up input listeners
this.setupInputListeners();
}
setupInputListeners() {
// Keyboard input
this.scene.input.keyboard.on('keydown', (event) => {
if (this.isRebinding) {
this.handleRebindInput(event.key.toUpperCase());
}
});
// Mouse input
this.scene.input.on('pointerdown', (pointer) => {
if (this.isRebinding) {
const button = pointer.leftButtonDown() ? 'MOUSE_LEFT' :
pointer.rightButtonDown() ? 'MOUSE_RIGHT' :
pointer.middleButtonDown() ? 'MOUSE_MIDDLE' : null;
if (button) {
this.handleRebindInput(button);
}
}
});
// Mouse wheel
this.scene.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
if (this.isRebinding) {
const input = deltaY < 0 ? 'MOUSE_WHEEL_UP' : 'MOUSE_WHEEL_DOWN';
this.handleRebindInput(input);
}
});
}
/**
* Create arrows-based profile (arrow keys for movement)
*/
createArrowsProfile() {
const profile = JSON.parse(JSON.stringify(this.defaultBindings));
profile.move_up = ['UP', 'W'];
profile.move_down = ['DOWN', 'S'];
profile.move_left = ['LEFT', 'A'];
profile.move_right = ['RIGHT', 'D'];
return profile;
}
/**
* Create left-handed profile (numpad for movement)
*/
createLeftHandedProfile() {
return {
// Movement on numpad
'move_up': ['NUMPAD_8', 'I'],
'move_down': ['NUMPAD_5', 'K'],
'move_left': ['NUMPAD_4', 'J'],
'move_right': ['NUMPAD_6', 'L'],
// Actions on left side
'interact': ['Q', 'SPACE'],
'attack': ['MOUSE_LEFT', 'W'],
'use_tool': ['MOUSE_LEFT', 'W'],
'cancel': ['ESC', 'E'],
'confirm': ['ENTER', 'Q'],
// Inventory & UI
'inventory': ['TAB', 'U'],
'crafting': ['R'],
'map': ['T'],
'quest_log': ['Y'],
'pause': ['ESC', 'P'],
// Tools (right side for easy access)
'tool_1': ['7'],
'tool_2': ['8'],
'tool_3': ['9'],
'tool_4': ['0'],
'tool_5': ['MINUS'],
// Quick actions
'quick_heal': ['A'],
'quick_eat': ['S'],
'sprint': ['SHIFT'],
'crouch': ['CTRL'],
// Camera
'zoom_in': ['PLUS', 'MOUSE_WHEEL_UP'],
'zoom_out': ['EQUALS', 'MOUSE_WHEEL_DOWN'],
'camera_reset': ['BACKSPACE']
};
}
/**
* Create right-handed profile (standard WASD)
*/
createRightHandedProfile() {
return JSON.parse(JSON.stringify(this.defaultBindings));
}
/**
* Check if an action is pressed
*/
isActionPressed(action) {
const bindings = this.currentBindings[action];
if (!bindings) return false;
for (const key of bindings) {
if (this.isKeyPressed(key)) {
return true;
}
}
return false;
}
/**
* Check if a specific key is pressed
*/
isKeyPressed(key) {
if (key.startsWith('MOUSE_')) {
const pointer = this.scene.input.activePointer;
if (key === 'MOUSE_LEFT') return pointer.leftButtonDown();
if (key === 'MOUSE_RIGHT') return pointer.rightButtonDown();
if (key === 'MOUSE_MIDDLE') return pointer.middleButtonDown();
return false;
}
const keyObj = this.scene.input.keyboard.addKey(key, false);
return keyObj && keyObj.isDown;
}
/**
* Check if an action was just pressed (single frame)
*/
isActionJustPressed(action) {
const bindings = this.currentBindings[action];
if (!bindings) return false;
for (const key of bindings) {
if (this.isKeyJustPressed(key)) {
return true;
}
}
return false;
}
/**
* Check if a key was just pressed
*/
isKeyJustPressed(key) {
if (key.startsWith('MOUSE_')) {
const pointer = this.scene.input.activePointer;
if (key === 'MOUSE_LEFT') return pointer.leftButtonDown() && pointer.getDuration() < 100;
if (key === 'MOUSE_RIGHT') return pointer.rightButtonDown() && pointer.getDuration() < 100;
if (key === 'MOUSE_MIDDLE') return pointer.middleButtonDown() && pointer.getDuration() < 100;
return false;
}
const keyObj = this.scene.input.keyboard.addKey(key, false);
return keyObj && Phaser.Input.Keyboard.JustDown(keyObj);
}
/**
* Start rebinding an action
*/
startRebinding(action, callback) {
if (!this.currentBindings[action]) {
console.error(`Action "${action}" does not exist`);
return;
}
this.isRebinding = true;
this.rebindingAction = action;
this.rebindingCallback = callback;
console.log(`🎮 Rebinding action: ${action}. Press any key...`);
}
/**
* Handle rebind input
*/
handleRebindInput(input) {
if (!this.isRebinding) return;
// Ignore ESC (cancel rebinding)
if (input === 'ESC' || input === 'ESCAPE') {
this.cancelRebinding();
return;
}
// Set new binding (replace first binding)
this.currentBindings[this.rebindingAction][0] = input;
console.log(`✅ Action "${this.rebindingAction}" rebound to: ${input}`);
// Call callback if provided
if (this.rebindingCallback) {
this.rebindingCallback(this.rebindingAction, input);
}
this.isRebinding = false;
this.rebindingAction = null;
this.rebindingCallback = null;
this.saveSettings();
}
/**
* Cancel rebinding
*/
cancelRebinding() {
console.log('❌ Rebinding cancelled');
this.isRebinding = false;
this.rebindingAction = null;
this.rebindingCallback = null;
}
/**
* Reset action to default binding
*/
resetAction(action) {
if (!this.defaultBindings[action]) {
console.error(`Action "${action}" does not exist`);
return;
}
this.currentBindings[action] = JSON.parse(JSON.stringify(this.defaultBindings[action]));
this.saveSettings();
console.log(`🔄 Action "${action}" reset to default`);
}
/**
* Reset all bindings to default
*/
resetAllBindings() {
this.currentBindings = JSON.parse(JSON.stringify(this.defaultBindings));
this.profiles[this.activeProfile] = this.currentBindings;
this.saveSettings();
console.log('🔄 All bindings reset to default');
}
/**
* Switch to a different profile
*/
switchProfile(profileName) {
if (!this.profiles[profileName]) {
console.error(`Profile "${profileName}" does not exist`);
return;
}
this.activeProfile = profileName;
this.currentBindings = this.profiles[profileName];
this.saveSettings();
console.log(`🎮 Switched to profile: ${profileName}`);
}
/**
* Save current bindings to a custom profile
*/
saveToProfile(profileName) {
if (!profileName.startsWith('custom-')) {
console.error('Can only save to custom profiles (custom-1, custom-2, custom-3)');
return;
}
this.profiles[profileName] = JSON.parse(JSON.stringify(this.currentBindings));
this.saveSettings();
console.log(`💾 Bindings saved to profile: ${profileName}`);
}
/**
* Get binding display name
*/
getBindingDisplay(action) {
const bindings = this.currentBindings[action];
if (!bindings || bindings.length === 0) return 'Not bound';
return bindings.map(key => this.formatKeyName(key)).join(' / ');
}
/**
* Format key name for display
*/
formatKeyName(key) {
const keyMap = {
'MOUSE_LEFT': 'Left Click',
'MOUSE_RIGHT': 'Right Click',
'MOUSE_MIDDLE': 'Middle Click',
'MOUSE_WHEEL_UP': 'Scroll Up',
'MOUSE_WHEEL_DOWN': 'Scroll Down',
'SPACE': 'Space',
'ENTER': 'Enter',
'ESC': 'Escape',
'SHIFT': 'Shift',
'CTRL': 'Ctrl',
'ALT': 'Alt',
'TAB': 'Tab',
'BACKSPACE': 'Backspace',
'UP': '↑',
'DOWN': '↓',
'LEFT': '←',
'RIGHT': '→',
'PLUS': '+',
'MINUS': '-',
'EQUALS': '='
};
return keyMap[key] || key;
}
/**
* Get all available profiles
*/
getProfiles() {
return Object.keys(this.profiles);
}
/**
* Get current profile name
*/
getCurrentProfile() {
return this.activeProfile;
}
/**
* Export bindings as JSON
*/
exportBindings() {
const data = {
activeProfile: this.activeProfile,
profiles: this.profiles
};
return JSON.stringify(data, null, 2);
}
/**
* Import bindings from JSON
*/
importBindings(jsonString) {
try {
const data = JSON.parse(jsonString);
this.activeProfile = data.activeProfile || 'default';
this.profiles = data.profiles || this.profiles;
this.currentBindings = this.profiles[this.activeProfile];
this.saveSettings();
console.log('✅ Bindings imported successfully');
return true;
} catch (error) {
console.error('❌ Failed to import bindings:', error);
return false;
}
}
/**
* Save settings to localStorage
*/
saveSettings() {
const data = {
activeProfile: this.activeProfile,
profiles: this.profiles
};
localStorage.setItem('novafarma_input_bindings', JSON.stringify(data));
}
/**
* Load settings from localStorage
*/
loadSettings() {
const saved = localStorage.getItem('novafarma_input_bindings');
if (saved) {
try {
const data = JSON.parse(saved);
this.activeProfile = data.activeProfile || 'default';
this.profiles = { ...this.profiles, ...data.profiles };
this.currentBindings = this.profiles[this.activeProfile];
console.log('✅ Input bindings loaded from localStorage');
} catch (error) {
console.error('❌ Failed to load input bindings:', error);
}
}
}
/**
* Get controller button name
*/
getControllerButtonName(button) {
const buttonMap = {
'A': 'A (Cross)',
'B': 'B (Circle)',
'X': 'X (Square)',
'Y': 'Y (Triangle)',
'LB': 'LB (L1)',
'RB': 'RB (R1)',
'LT': 'LT (L2)',
'RT': 'RT (R2)',
'START': 'Start',
'SELECT': 'Select (Share)',
'LEFT_STICK': 'Left Stick',
'RIGHT_STICK': 'Right Stick'
};
return buttonMap[button] || button;
}
/**
* Check if controller is connected
*/
isControllerConnected() {
return this.scene.input.gamepad && this.scene.input.gamepad.total > 0;
}
/**
* Get connected controller info
*/
getControllerInfo() {
if (!this.isControllerConnected()) return null;
const pad = this.scene.input.gamepad.getPad(0);
return {
id: pad.id,
index: pad.index,
buttons: pad.buttons.length,
axes: pad.axes.length
};
}
destroy() {
this.saveSettings();
console.log('🎮 Input Remapping System destroyed');
}
}

View File

@@ -0,0 +1,217 @@
/**
* MOTOR ACCESSIBILITY SYSTEM
* Provides assistance for players with motor disabilities
* Note: One-handed mode is already implemented in InputRemappingSystem
*/
class MotorAccessibilitySystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// Settings
this.settings = {
autoAim: false, // Auto-aim assist
autoAimStrength: 0.5, // 0-1 (strength)
stickyKeys: false, // Hold key instead of press
reducedInputComplexity: false, // Simplified controls
slowMotion: false, // Slow-motion mode
slowMotionSpeed: 0.5, // 0.1-1.0 (game speed)
autoRun: false, // Auto-run when moving
autoInteract: false, // Auto-interact with nearby objects
largerClickTargets: false, // Bigger UI buttons
holdToConfirm: false // Hold instead of click
};
this.loadSettings();
this.init();
console.log('✅ Motor Accessibility System initialized');
}
init() {
if (this.settings.slowMotion) {
this.enableSlowMotion();
}
}
/**
* Enable auto-aim assist
*/
enableAutoAim() {
this.settings.autoAim = true;
this.saveSettings();
console.log('🎯 Auto-aim enabled');
}
/**
* Disable auto-aim
*/
disableAutoAim() {
this.settings.autoAim = false;
this.saveSettings();
console.log('🎯 Auto-aim disabled');
}
/**
* Set auto-aim strength
*/
setAutoAimStrength(strength) {
this.settings.autoAimStrength = Phaser.Math.Clamp(strength, 0, 1);
this.saveSettings();
console.log(`🎯 Auto-aim strength: ${this.settings.autoAimStrength}`);
}
/**
* Get nearest enemy for auto-aim
*/
getNearestEnemy() {
if (!this.scene.player) return null;
const playerPos = this.scene.player.getPosition();
let nearest = null;
let minDist = 999999;
// Find nearest NPC
for (const npc of this.scene.npcs) {
if (!npc.sprite || npc.isFriendly) continue;
const dist = Phaser.Math.Distance.Between(
playerPos.x, playerPos.y,
npc.gridX, npc.gridY
);
if (dist < minDist) {
minDist = dist;
nearest = npc;
}
}
return nearest;
}
/**
* Apply auto-aim to attack
*/
applyAutoAim() {
if (!this.settings.autoAim) return null;
const target = this.getNearestEnemy();
if (!target) return null;
// Return target position with strength factor
return {
x: target.gridX,
y: target.gridY,
strength: this.settings.autoAimStrength
};
}
/**
* Enable sticky keys
*/
enableStickyKeys() {
this.settings.stickyKeys = true;
this.saveSettings();
console.log('⌨️ Sticky keys enabled');
}
/**
* Disable sticky keys
*/
disableStickyKeys() {
this.settings.stickyKeys = false;
this.saveSettings();
console.log('⌨️ Sticky keys disabled');
}
/**
* Enable slow-motion mode
*/
enableSlowMotion() {
this.settings.slowMotion = true;
this.scene.time.timeScale = this.settings.slowMotionSpeed;
this.saveSettings();
console.log(`🐌 Slow-motion enabled (${this.settings.slowMotionSpeed}x)`);
}
/**
* Disable slow-motion mode
*/
disableSlowMotion() {
this.settings.slowMotion = false;
this.scene.time.timeScale = 1.0;
this.saveSettings();
console.log('🐌 Slow-motion disabled');
}
/**
* Set slow-motion speed
*/
setSlowMotionSpeed(speed) {
this.settings.slowMotionSpeed = Phaser.Math.Clamp(speed, 0.1, 1.0);
if (this.settings.slowMotion) {
this.scene.time.timeScale = this.settings.slowMotionSpeed;
}
this.saveSettings();
console.log(`🐌 Slow-motion speed: ${this.settings.slowMotionSpeed}x`);
}
/**
* Toggle auto-run
*/
toggleAutoRun() {
this.settings.autoRun = !this.settings.autoRun;
this.saveSettings();
console.log(`🏃 Auto-run: ${this.settings.autoRun ? 'ON' : 'OFF'}`);
}
/**
* Toggle auto-interact
*/
toggleAutoInteract() {
this.settings.autoInteract = !this.settings.autoInteract;
this.saveSettings();
console.log(`🤝 Auto-interact: ${this.settings.autoInteract ? 'ON' : 'OFF'}`);
}
/**
* Update (called every frame)
*/
update() {
// Auto-interact logic
if (this.settings.autoInteract) {
this.checkAutoInteract();
}
}
/**
* Check for auto-interact opportunities
*/
checkAutoInteract() {
// Implementation would check for nearby interactive objects
}
/**
* Save settings
*/
saveSettings() {
localStorage.setItem('novafarma_motor_accessibility', JSON.stringify(this.settings));
}
/**
* Load settings
*/
loadSettings() {
const saved = localStorage.getItem('novafarma_motor_accessibility');
if (saved) {
this.settings = { ...this.settings, ...JSON.parse(saved) };
}
}
destroy() {
if (this.settings.slowMotion) {
this.scene.time.timeScale = 1.0;
}
console.log('🦾 Motor Accessibility System destroyed');
}
}

View File

@@ -0,0 +1,590 @@
/**
* SCREEN READER SYSTEM
* Provides audio narration and accessibility for blind/visually impaired players
* Compatible with NVDA, JAWS, VoiceOver, and other screen readers
*/
class ScreenReaderSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// Speech synthesis
this.synth = window.speechSynthesis;
this.voice = null;
this.voices = [];
// Settings
this.settings = {
enabled: true,
rate: 1.0, // 0.1 - 10 (speech speed)
pitch: 1.0, // 0 - 2 (voice pitch)
volume: 1.0, // 0 - 1 (volume)
language: 'en-US', // Voice language
autoNarrate: true, // Auto-narrate UI changes
verboseMode: false, // Detailed descriptions
soundCues: true, // Audio cues for actions
navigationHelp: true // Navigation assistance
};
// ARIA live regions (for screen reader announcements)
this.liveRegion = null;
this.alertRegion = null;
// Navigation state
this.currentFocus = null;
this.navigationHistory = [];
this.maxHistorySize = 50;
// Audio cues (simple beeps/tones)
this.audioCues = {
'focus': { frequency: 440, duration: 100 },
'select': { frequency: 880, duration: 150 },
'error': { frequency: 220, duration: 300 },
'success': { frequency: 660, duration: 200 },
'navigation': { frequency: 550, duration: 80 },
'inventory': { frequency: 750, duration: 120 },
'damage': { frequency: 200, duration: 250 },
'pickup': { frequency: 1000, duration: 100 }
};
// Context descriptions
this.contextDescriptions = {
'menu': 'Main menu. Use arrow keys to navigate, Enter to select.',
'game': 'In game. Use WASD to move, E to interact, I for inventory.',
'inventory': 'Inventory screen. Use arrow keys to navigate items, Enter to use.',
'crafting': 'Crafting menu. Use arrow keys to browse recipes, Enter to craft.',
'dialogue': 'Dialogue. Press Space to continue, Escape to skip.',
'combat': 'In combat. Use J to attack, Space to dodge.',
'building': 'Build mode. Use arrow keys to select building, Enter to place.',
'map': 'Map view. Use arrow keys to pan, M to close.'
};
// UI element descriptions
this.elementDescriptions = new Map();
this.loadSettings();
this.init();
console.log('✅ Screen Reader System initialized');
}
init() {
// Load available voices
this.loadVoices();
// Create ARIA live regions
this.createLiveRegions();
// Set up speech synthesis event listeners
this.setupSpeechListeners();
// Set up keyboard navigation
this.setupKeyboardNavigation();
// Announce system ready
this.speak('Screen reader system ready. Press H for help.');
}
/**
* Load available speech synthesis voices
*/
loadVoices() {
this.voices = this.synth.getVoices();
// If voices not loaded yet, wait for event
if (this.voices.length === 0) {
this.synth.addEventListener('voiceschanged', () => {
this.voices = this.synth.getVoices();
this.selectVoice();
});
} else {
this.selectVoice();
}
}
/**
* Select appropriate voice based on language setting
*/
selectVoice() {
if (this.voices.length === 0) return;
// Try to find voice matching language
this.voice = this.voices.find(v => v.lang === this.settings.language);
// Fallback to first available voice
if (!this.voice) {
this.voice = this.voices[0];
}
console.log(`🔊 Selected voice: ${this.voice.name} (${this.voice.lang})`);
}
/**
* Create ARIA live regions for screen reader announcements
*/
createLiveRegions() {
// Polite region (non-interrupting)
this.liveRegion = document.createElement('div');
this.liveRegion.setAttribute('role', 'status');
this.liveRegion.setAttribute('aria-live', 'polite');
this.liveRegion.setAttribute('aria-atomic', 'true');
this.liveRegion.style.position = 'absolute';
this.liveRegion.style.left = '-10000px';
this.liveRegion.style.width = '1px';
this.liveRegion.style.height = '1px';
this.liveRegion.style.overflow = 'hidden';
document.body.appendChild(this.liveRegion);
// Alert region (interrupting)
this.alertRegion = document.createElement('div');
this.alertRegion.setAttribute('role', 'alert');
this.alertRegion.setAttribute('aria-live', 'assertive');
this.alertRegion.setAttribute('aria-atomic', 'true');
this.alertRegion.style.position = 'absolute';
this.alertRegion.style.left = '-10000px';
this.alertRegion.style.width = '1px';
this.alertRegion.style.height = '1px';
this.alertRegion.style.overflow = 'hidden';
document.body.appendChild(this.alertRegion);
}
/**
* Set up speech synthesis event listeners
*/
setupSpeechListeners() {
this.synth.addEventListener('error', (event) => {
console.error('Speech synthesis error:', event);
});
}
/**
* Set up keyboard navigation for screen reader users
*/
setupKeyboardNavigation() {
// H key - Help
this.scene.input.keyboard.on('keydown-H', () => {
if (this.scene.input.keyboard.checkDown(this.scene.input.keyboard.addKey('CTRL'))) {
this.announceHelp();
}
});
// Ctrl+R - Repeat last announcement
this.scene.input.keyboard.on('keydown-R', () => {
if (this.scene.input.keyboard.checkDown(this.scene.input.keyboard.addKey('CTRL'))) {
this.repeatLast();
}
});
// Ctrl+S - Settings
this.scene.input.keyboard.on('keydown-S', () => {
if (this.scene.input.keyboard.checkDown(this.scene.input.keyboard.addKey('CTRL'))) {
this.announceSettings();
}
});
}
/**
* Speak text using speech synthesis
*/
speak(text, priority = 'normal', interrupt = false) {
if (!this.settings.enabled || !text) return;
// Cancel current speech if interrupting
if (interrupt) {
this.synth.cancel();
}
// Create utterance
const utterance = new SpeechSynthesisUtterance(text);
utterance.voice = this.voice;
utterance.rate = this.settings.rate;
utterance.pitch = this.settings.pitch;
utterance.volume = this.settings.volume;
// Speak
this.synth.speak(utterance);
// Update ARIA live region
if (priority === 'alert') {
this.alertRegion.textContent = text;
} else {
this.liveRegion.textContent = text;
}
// Add to history
this.addToHistory(text);
console.log(`🔊 Speaking: "${text}"`);
}
/**
* Stop current speech
*/
stop() {
this.synth.cancel();
}
/**
* Add text to navigation history
*/
addToHistory(text) {
this.navigationHistory.unshift(text);
if (this.navigationHistory.length > this.maxHistorySize) {
this.navigationHistory.pop();
}
}
/**
* Repeat last announcement
*/
repeatLast() {
if (this.navigationHistory.length > 0) {
this.speak(this.navigationHistory[0], 'normal', true);
}
}
/**
* Play audio cue
*/
playAudioCue(cueType) {
if (!this.settings.soundCues) return;
const cue = this.audioCues[cueType];
if (!cue) return;
// Create audio context
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = cue.frequency;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + cue.duration / 1000);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + cue.duration / 1000);
}
/**
* Announce game context
*/
announceContext(context) {
const description = this.contextDescriptions[context];
if (description) {
this.speak(description, 'alert', true);
this.playAudioCue('navigation');
}
}
/**
* Announce player stats
*/
announceStats() {
if (!this.scene.player || !this.scene.statsSystem) return;
const stats = this.scene.statsSystem;
const text = `Health: ${Math.round(stats.health)} out of ${stats.maxHealth}. ` +
`Hunger: ${Math.round(stats.hunger)} percent. ` +
`Stamina: ${Math.round(stats.stamina)} percent.`;
this.speak(text);
}
/**
* Announce inventory
*/
announceInventory() {
if (!this.scene.inventorySystem) return;
const inv = this.scene.inventorySystem;
const itemCount = Object.keys(inv.items).length;
const gold = inv.gold || 0;
let text = `Inventory. ${itemCount} item types. ${gold} gold. `;
// List items
if (this.settings.verboseMode) {
for (const [item, count] of Object.entries(inv.items)) {
text += `${item}: ${count}. `;
}
} else {
text += 'Press V for verbose item list.';
}
this.speak(text);
}
/**
* Announce position
*/
announcePosition() {
if (!this.scene.player) return;
const pos = this.scene.player.getPosition();
const text = `Position: X ${Math.round(pos.x)}, Y ${Math.round(pos.y)}.`;
this.speak(text);
}
/**
* Announce nearby objects
*/
announceNearby() {
if (!this.scene.player || !this.scene.terrainSystem) return;
const pos = this.scene.player.getPosition();
const x = Math.floor(pos.x);
const y = Math.floor(pos.y);
let text = 'Nearby: ';
let foundObjects = false;
// Check decorations
for (let dx = -2; dx <= 2; dx++) {
for (let dy = -2; dy <= 2; dy++) {
if (dx === 0 && dy === 0) continue;
const key = `${x + dx},${y + dy}`;
if (this.scene.terrainSystem.decorationsMap.has(key)) {
const decoration = this.scene.terrainSystem.decorationsMap.get(key);
const direction = this.getDirection(dx, dy);
text += `${decoration.type} ${direction}. `;
foundObjects = true;
}
}
}
if (!foundObjects) {
text += 'Nothing nearby.';
}
this.speak(text);
}
/**
* Get direction description
*/
getDirection(dx, dy) {
if (dx === 0 && dy < 0) return 'north';
if (dx === 0 && dy > 0) return 'south';
if (dx < 0 && dy === 0) return 'west';
if (dx > 0 && dy === 0) return 'east';
if (dx < 0 && dy < 0) return 'northwest';
if (dx > 0 && dy < 0) return 'northeast';
if (dx < 0 && dy > 0) return 'southwest';
if (dx > 0 && dy > 0) return 'southeast';
return 'nearby';
}
/**
* Announce help
*/
announceHelp() {
const text = 'Screen reader help. ' +
'Press Ctrl H for help. ' +
'Press Ctrl R to repeat last announcement. ' +
'Press Ctrl S for settings. ' +
'Press Ctrl P for position. ' +
'Press Ctrl I for inventory. ' +
'Press Ctrl N for nearby objects. ' +
'Press Ctrl T for stats. ' +
'Press Ctrl V to toggle verbose mode.';
this.speak(text, 'alert', true);
}
/**
* Announce settings
*/
announceSettings() {
const text = `Screen reader settings. ` +
`Speed: ${this.settings.rate}. ` +
`Pitch: ${this.settings.pitch}. ` +
`Volume: ${this.settings.volume}. ` +
`Verbose mode: ${this.settings.verboseMode ? 'on' : 'off'}. ` +
`Sound cues: ${this.settings.soundCues ? 'on' : 'off'}.`;
this.speak(text);
}
/**
* Announce action
*/
announceAction(action, details = '') {
const actionDescriptions = {
'move': 'Moving',
'attack': 'Attacking',
'interact': 'Interacting',
'pickup': 'Picked up',
'drop': 'Dropped',
'craft': 'Crafted',
'build': 'Built',
'harvest': 'Harvested',
'plant': 'Planted',
'dig': 'Digging',
'damage': 'Took damage',
'heal': 'Healed',
'die': 'You died',
'respawn': 'Respawned'
};
const description = actionDescriptions[action] || action;
const text = details ? `${description}: ${details}` : description;
this.speak(text);
this.playAudioCue(action);
}
/**
* Announce UI change
*/
announceUI(element, state = '') {
if (!this.settings.autoNarrate) return;
const text = state ? `${element}: ${state}` : element;
this.speak(text);
this.playAudioCue('navigation');
}
/**
* Announce notification
*/
announceNotification(message, priority = 'normal') {
this.speak(message, priority);
this.playAudioCue(priority === 'alert' ? 'error' : 'success');
}
/**
* Set speech rate
*/
setRate(rate) {
this.settings.rate = Phaser.Math.Clamp(rate, 0.1, 10);
this.saveSettings();
this.speak(`Speech rate set to ${this.settings.rate}`);
}
/**
* Set speech pitch
*/
setPitch(pitch) {
this.settings.pitch = Phaser.Math.Clamp(pitch, 0, 2);
this.saveSettings();
this.speak(`Speech pitch set to ${this.settings.pitch}`);
}
/**
* Set speech volume
*/
setVolume(volume) {
this.settings.volume = Phaser.Math.Clamp(volume, 0, 1);
this.saveSettings();
this.speak(`Speech volume set to ${Math.round(this.settings.volume * 100)} percent`);
}
/**
* Toggle verbose mode
*/
toggleVerboseMode() {
this.settings.verboseMode = !this.settings.verboseMode;
this.saveSettings();
this.speak(`Verbose mode ${this.settings.verboseMode ? 'enabled' : 'disabled'}`);
}
/**
* Toggle sound cues
*/
toggleSoundCues() {
this.settings.soundCues = !this.settings.soundCues;
this.saveSettings();
this.speak(`Sound cues ${this.settings.soundCues ? 'enabled' : 'disabled'}`);
}
/**
* Toggle auto-narrate
*/
toggleAutoNarrate() {
this.settings.autoNarrate = !this.settings.autoNarrate;
this.saveSettings();
this.speak(`Auto narration ${this.settings.autoNarrate ? 'enabled' : 'disabled'}`);
}
/**
* Get available voices
*/
getAvailableVoices() {
return this.voices.map(v => ({
name: v.name,
lang: v.lang,
default: v.default,
localService: v.localService
}));
}
/**
* Set voice by name
*/
setVoice(voiceName) {
const voice = this.voices.find(v => v.name === voiceName);
if (voice) {
this.voice = voice;
this.saveSettings();
this.speak(`Voice changed to ${voice.name}`);
}
}
/**
* Save settings to localStorage
*/
saveSettings() {
localStorage.setItem('novafarma_screen_reader', JSON.stringify(this.settings));
}
/**
* Load settings from localStorage
*/
loadSettings() {
const saved = localStorage.getItem('novafarma_screen_reader');
if (saved) {
try {
this.settings = { ...this.settings, ...JSON.parse(saved) };
console.log('✅ Screen reader settings loaded');
} catch (error) {
console.error('❌ Failed to load screen reader settings:', error);
}
}
}
/**
* Update (called every frame)
*/
update() {
// Auto-announce important changes
if (this.settings.autoNarrate) {
// Check for low health
if (this.scene.statsSystem && this.scene.statsSystem.health < 20) {
if (!this.lowHealthWarned) {
this.speak('Warning: Low health!', 'alert');
this.playAudioCue('damage');
this.lowHealthWarned = true;
}
} else {
this.lowHealthWarned = false;
}
}
}
/**
* Destroy system
*/
destroy() {
this.stop();
if (this.liveRegion) this.liveRegion.remove();
if (this.alertRegion) this.alertRegion.remove();
console.log('🔊 Screen Reader System destroyed');
}
}

View File

@@ -0,0 +1,257 @@
/**
* VISUAL ENHANCEMENT SYSTEM
* Central system for managing visual effects, animations, and polish
*/
class VisualEnhancementSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// Sub-systems
this.animatedTextures = null;
this.weatherEffects = null;
this.lightingSystem = null;
this.shadowSystem = null;
this.fogOfWar = null;
// Settings
this.settings = {
animatedTextures: true,
weatherEffects: true,
dynamicLighting: true,
shadows: true,
fogOfWar: false,
particleQuality: 'high', // low, medium, high, ultra
animationQuality: 'high'
};
this.loadSettings();
this.init();
console.log('✅ Visual Enhancement System initialized');
}
init() {
// Initialize sub-systems
this.initAnimatedTextures();
this.initWeatherEffects();
this.initLightingSystem();
this.initShadowSystem();
}
/**
* Initialize animated textures
*/
initAnimatedTextures() {
if (!this.settings.animatedTextures) return;
// Crop growth animations
this.createCropAnimations();
// Water flow
this.createWaterAnimation();
// Tree leaves
this.createTreeAnimations();
// Fire effects
this.createFireAnimations();
}
/**
* Create crop growth animations
*/
createCropAnimations() {
// Smooth transitions between growth stages
console.log('🌱 Creating crop animations...');
}
/**
* Create water animation
*/
createWaterAnimation() {
// Flowing water effect
console.log('💧 Creating water animation...');
}
/**
* Create tree animations
*/
createTreeAnimations() {
// Leaf rustling
console.log('🌳 Creating tree animations...');
}
/**
* Create fire animations
*/
createFireAnimations() {
// Flickering flames
console.log('🔥 Creating fire animations...');
}
/**
* Initialize weather effects
*/
initWeatherEffects() {
if (!this.settings.weatherEffects) return;
console.log('🌦️ Initializing weather effects...');
// Snow accumulation
// Rain splashes
// Wind indicators
// Lightning
// Fog
}
/**
* Initialize lighting system
*/
initLightingSystem() {
if (!this.settings.dynamicLighting) return;
console.log('💡 Initializing lighting system...');
// Create lighting layer
this.lightingLayer = this.scene.add.layer();
this.lightingLayer.setDepth(5000);
// Light sources
this.lightSources = [];
}
/**
* Add light source
*/
addLight(x, y, radius, color, intensity) {
const light = {
x, y, radius, color, intensity,
sprite: null
};
// Create light sprite
const graphics = this.scene.add.graphics();
graphics.fillStyle(color, intensity);
graphics.fillCircle(0, 0, radius);
graphics.generateTexture('light_' + this.lightSources.length, radius * 2, radius * 2);
graphics.destroy();
light.sprite = this.scene.add.sprite(x, y, 'light_' + this.lightSources.length);
light.sprite.setBlendMode(Phaser.BlendModes.ADD);
light.sprite.setAlpha(intensity);
this.lightSources.push(light);
return light;
}
/**
* Initialize shadow system
*/
initShadowSystem() {
if (!this.settings.shadows) return;
console.log('🌑 Initializing shadow system...');
this.shadows = [];
}
/**
* Add shadow to entity
*/
addShadow(entity, offsetX = 0, offsetY = 10) {
const shadow = this.scene.add.ellipse(
entity.x + offsetX,
entity.y + offsetY,
entity.width * 0.8,
entity.height * 0.3,
0x000000,
0.3
);
shadow.setDepth(entity.depth - 1);
this.shadows.push({ entity, shadow, offsetX, offsetY });
return shadow;
}
/**
* Update shadows based on time of day
*/
updateShadows() {
if (!this.settings.shadows) return;
// Get time of day
const timeOfDay = this.scene.weatherSystem ? this.scene.weatherSystem.gameTime : 12;
// Calculate shadow opacity (darker at noon, lighter at dawn/dusk)
const opacity = Math.abs(Math.sin((timeOfDay / 24) * Math.PI)) * 0.5;
// Update all shadows
for (const { entity, shadow, offsetX, offsetY } of this.shadows) {
if (entity.sprite) {
shadow.x = entity.sprite.x + offsetX;
shadow.y = entity.sprite.y + offsetY;
shadow.setAlpha(opacity);
}
}
}
/**
* Create screen shake effect
*/
screenShake(intensity = 10, duration = 300) {
this.scene.cameras.main.shake(duration, intensity / 1000);
}
/**
* Create fade transition
*/
fadeOut(duration = 500, callback) {
this.scene.cameras.main.fadeOut(duration, 0, 0, 0);
this.scene.cameras.main.once('camerafadeoutcomplete', callback);
}
/**
* Fade in
*/
fadeIn(duration = 500) {
this.scene.cameras.main.fadeIn(duration, 0, 0, 0);
}
/**
* Update (called every frame)
*/
update(delta) {
if (this.settings.shadows) {
this.updateShadows();
}
}
/**
* Save settings
*/
saveSettings() {
localStorage.setItem('novafarma_visual_enhancements', JSON.stringify(this.settings));
}
/**
* Load settings
*/
loadSettings() {
const saved = localStorage.getItem('novafarma_visual_enhancements');
if (saved) {
this.settings = { ...this.settings, ...JSON.parse(saved) };
}
}
/**
* Destroy system
*/
destroy() {
if (this.lightingLayer) this.lightingLayer.destroy();
for (const { shadow } of this.shadows) {
shadow.destroy();
}
console.log('✨ Visual Enhancement System destroyed');
}
}

View File

@@ -7,26 +7,51 @@ class VisualSoundCueSystem {
this.scene = scene;
this.enabled = true;
// Visual elements
this.heartbeatIndicator = null;
this.damageIndicators = [];
this.screenFlash = null;
this.subtitleBackground = null;
this.subtitleText = null;
this.subtitleSpeaker = null;
this.subtitleArrows = { left: null, right: null };
this.heartbeatTween = null;
this.fishingBobberIndicator = null;
// Speaker color mapping
this.speakerColors = {
'Player': '#00ff00',
'NPC': '#ffff00',
'Enemy': '#ff0000',
'System': '#00ffff',
'Narrator': '#ffffff'
};
// Subtitle size presets
this.subtitleSizes = {
'small': { main: 16, speaker: 12, arrow: 24 },
'medium': { main: 20, speaker: 16, arrow: 32 },
'large': { main: 28, speaker: 20, arrow: 40 },
'very-large': { main: 36, speaker: 24, arrow: 48 }
};
// Settings
this.settings = {
heartbeatEnabled: true,
damageIndicatorEnabled: true,
screenFlashEnabled: true,
subtitlesEnabled: true
subtitlesEnabled: true,
directionalArrowsEnabled: true,
speakerNamesEnabled: true,
subtitleOpacity: 0.8,
fishingBobberEnabled: true,
subtitleSize: 'medium' // 'small', 'medium', 'large', 'very-large'
};
// Visual elements
this.heartbeatSprite = null;
this.damageIndicators = [];
this.subtitleText = null;
this.subtitleBackground = null;
// Heartbeat state
this.heartbeatActive = false;
this.heartbeatTween = null;
this.loadSettings();
this.init();
console.log('✅ VisualSoundCueSystem initialized');
console.log('✅ Visual Sound Cue System initialized');
}
init() {
@@ -37,7 +62,7 @@ class VisualSoundCueSystem {
this.createSubtitleContainer();
// Load settings from localStorage
this.loadSettings();
// this.loadSettings(); // Moved to constructor
}
createHeartbeatIndicator() {
@@ -45,13 +70,13 @@ class VisualSoundCueSystem {
const y = 30;
// Heart emoji as sprite
this.heartbeatSprite = this.scene.add.text(x, y, '❤️', {
this.heartbeatIndicator = this.scene.add.text(x, y, '❤️', {
fontSize: '48px'
});
this.heartbeatSprite.setOrigin(0.5);
this.heartbeatSprite.setDepth(10000);
this.heartbeatSprite.setScrollFactor(0);
this.heartbeatSprite.setVisible(false);
this.heartbeatIndicator.setOrigin(0.5);
this.heartbeatIndicator.setDepth(10000);
this.heartbeatIndicator.setScrollFactor(0);
this.heartbeatIndicator.setVisible(false);
}
createSubtitleContainer() {
@@ -63,16 +88,34 @@ class VisualSoundCueSystem {
width / 2,
height - 100,
width - 100,
80,
100,
0x000000,
0.8
this.settings.subtitleOpacity
);
this.subtitleBackground.setOrigin(0.5);
this.subtitleBackground.setDepth(9999);
this.subtitleBackground.setScrollFactor(0);
this.subtitleBackground.setVisible(false);
// Text
// Speaker name text
this.subtitleSpeaker = this.scene.add.text(
width / 2,
height - 130,
'',
{
fontSize: '16px',
fontFamily: 'Arial',
fontStyle: 'bold',
color: '#ffffff',
align: 'center'
}
);
this.subtitleSpeaker.setOrigin(0.5);
this.subtitleSpeaker.setDepth(10001);
this.subtitleSpeaker.setScrollFactor(0);
this.subtitleSpeaker.setVisible(false);
// Main subtitle text
this.subtitleText = this.scene.add.text(
width / 2,
height - 100,
@@ -82,13 +125,44 @@ class VisualSoundCueSystem {
fontFamily: 'Arial',
color: '#ffffff',
align: 'center',
wordWrap: { width: width - 120 }
wordWrap: { width: width - 160 }
}
);
this.subtitleText.setOrigin(0.5);
this.subtitleText.setDepth(10000);
this.subtitleText.setScrollFactor(0);
this.subtitleText.setVisible(false);
// Directional arrows
this.subtitleArrows.left = this.scene.add.text(
50,
height - 100,
'◄',
{
fontSize: '32px',
fontFamily: 'Arial',
color: '#ffff00'
}
);
this.subtitleArrows.left.setOrigin(0.5);
this.subtitleArrows.left.setDepth(10001);
this.subtitleArrows.left.setScrollFactor(0);
this.subtitleArrows.left.setVisible(false);
this.subtitleArrows.right = this.scene.add.text(
width - 50,
height - 100,
'►',
{
fontSize: '32px',
fontFamily: 'Arial',
color: '#ffff00'
}
);
this.subtitleArrows.right.setOrigin(0.5);
this.subtitleArrows.right.setDepth(10001);
this.subtitleArrows.right.setScrollFactor(0);
this.subtitleArrows.right.setVisible(false);
}
// ========== VISUAL HEARTBEAT (LOW HEALTH) ==========
@@ -110,13 +184,13 @@ class VisualSoundCueSystem {
startHeartbeat(healthPercent) {
this.heartbeatActive = true;
this.heartbeatSprite.setVisible(true);
this.heartbeatIndicator.setVisible(true);
// Calculate speed based on health (lower health = faster beat)
const speed = Phaser.Math.Clamp(1000 - (healthPercent * 20), 300, 1000);
this.heartbeatTween = this.scene.tweens.add({
targets: this.heartbeatSprite,
targets: this.heartbeatIndicator,
scale: 1.3,
alpha: 0.6,
duration: speed / 2,
@@ -139,15 +213,15 @@ class VisualSoundCueSystem {
if (!this.heartbeatActive) return;
this.heartbeatActive = false;
this.heartbeatSprite.setVisible(false);
this.heartbeatIndicator.setVisible(false);
if (this.heartbeatTween) {
this.heartbeatTween.stop();
this.heartbeatTween = null;
}
this.heartbeatSprite.setScale(1);
this.heartbeatSprite.setAlpha(1);
this.heartbeatIndicator.setScale(1);
this.heartbeatIndicator.setAlpha(1);
}
// ========== DAMAGE DIRECTION INDICATOR ==========
@@ -279,30 +353,90 @@ class VisualSoundCueSystem {
// ========== SUBTITLES ==========
showSubtitle(text, duration = 3000, speaker = null) {
showSubtitle(text, duration = 3000, speaker = null, direction = null) {
if (!this.settings.subtitlesEnabled) return;
// Format text with speaker name if provided
let displayText = text;
if (speaker) {
displayText = `[${speaker}]: ${text}`;
}
this.subtitleText.setText(displayText);
// Set subtitle text
this.subtitleText.setText(text);
this.subtitleText.setVisible(true);
this.subtitleBackground.setVisible(true);
// Show speaker name with color if enabled
if (speaker && this.settings.speakerNamesEnabled) {
const color = this.speakerColors[speaker] || '#ffffff';
this.subtitleSpeaker.setText(speaker);
this.subtitleSpeaker.setColor(color);
this.subtitleSpeaker.setVisible(true);
} else {
this.subtitleSpeaker.setVisible(false);
}
// Show directional arrows if enabled and direction provided
if (direction && this.settings.directionalArrowsEnabled) {
this.showDirectionalArrows(direction);
} else {
this.hideDirectionalArrows();
}
// Auto-hide after duration
this.scene.time.delayedCall(duration, () => {
this.hideSubtitle();
});
console.log('💬 Subtitle shown:', displayText);
console.log('💬 Subtitle shown:', speaker ? `[${speaker}] ${text}` : text);
}
showDirectionalArrows(direction) {
this.hideDirectionalArrows();
if (direction === 'left' || direction === 'west') {
this.subtitleArrows.left.setVisible(true);
// Pulse animation
this.scene.tweens.add({
targets: this.subtitleArrows.left,
alpha: { from: 1, to: 0.3 },
duration: 500,
yoyo: true,
repeat: -1
});
} else if (direction === 'right' || direction === 'east') {
this.subtitleArrows.right.setVisible(true);
// Pulse animation
this.scene.tweens.add({
targets: this.subtitleArrows.right,
alpha: { from: 1, to: 0.3 },
duration: 500,
yoyo: true,
repeat: -1
});
} else if (direction === 'both') {
this.subtitleArrows.left.setVisible(true);
this.subtitleArrows.right.setVisible(true);
// Pulse animation for both
this.scene.tweens.add({
targets: [this.subtitleArrows.left, this.subtitleArrows.right],
alpha: { from: 1, to: 0.3 },
duration: 500,
yoyo: true,
repeat: -1
});
}
}
hideDirectionalArrows() {
this.scene.tweens.killTweensOf(this.subtitleArrows.left);
this.scene.tweens.killTweensOf(this.subtitleArrows.right);
this.subtitleArrows.left.setVisible(false);
this.subtitleArrows.right.setVisible(false);
this.subtitleArrows.left.setAlpha(1);
this.subtitleArrows.right.setAlpha(1);
}
hideSubtitle() {
this.subtitleText.setVisible(false);
this.subtitleBackground.setVisible(false);
this.subtitleSpeaker.setVisible(false);
this.hideDirectionalArrows();
}
// ========== SOUND EVENT HANDLERS ==========
@@ -313,35 +447,192 @@ class VisualSoundCueSystem {
switch (soundType) {
case 'damage':
this.showDamageIndicator(data.direction || 'down', data.amount || 10);
this.showSubtitle('[DAMAGE TAKEN]', 1500);
this.showSubtitle('[DAMAGE TAKEN]', 1500, 'System', data.direction);
break;
case 'pickup':
this.showSubtitle(`[PICKED UP: ${data.item || 'Item'}]`, 1500);
this.showSubtitle(`[PICKED UP: ${data.item || 'Item'}]`, 1500, 'System');
break;
case 'harvest':
this.showSubtitle('[CROP HARVESTED]', 1500);
this.showSubtitle('[CROP HARVESTED]', 1500, 'System');
break;
case 'build':
this.showSubtitle('[BUILDING PLACED]', 1500);
this.showSubtitle('[BUILDING PLACED]', 1500, 'System');
break;
case 'dig':
this.showSubtitle('[DIGGING SOUND]', 1000, 'System');
break;
case 'plant':
this.showSubtitle('[PLANTING SOUND]', 1000, 'System');
break;
case 'footsteps':
this.showSubtitle('[FOOTSTEPS]', 500, null, data.direction);
break;
case 'door':
this.showSubtitle('[DOOR OPENS]', 1000, 'System');
break;
case 'chest':
this.showSubtitle('[CHEST OPENS]', 1000, 'System');
break;
case 'water':
this.showSubtitle('[WATER SPLASH]', 1000, 'System');
break;
case 'fire':
this.showSubtitle('[FIRE CRACKLING]', 2000, 'System');
break;
case 'explosion':
this.showSubtitle('[EXPLOSION!]', 1500, 'System');
this.showScreenFlash('danger', '[EXPLOSION!]');
break;
case 'npc_talk':
this.showSubtitle(data.text || '[NPC TALKING]', 3000, data.speaker || 'NPC', data.direction);
break;
case 'enemy_growl':
this.showSubtitle('[ENEMY GROWL]', 1500, 'Enemy', data.direction);
break;
case 'fishing_cast':
this.showSubtitle('[FISHING LINE CAST]', 1000, 'System');
break;
case 'fishing_bite':
this.showSubtitle('[FISH BITING!]', 1500, 'System');
this.showFishingBobberCue();
break;
case 'danger':
this.showScreenFlash('danger', '[DANGER!]');
this.showSubtitle('[DANGER NEARBY]', 2000, 'System');
break;
case 'night':
this.showScreenFlash('warning', '[NIGHT FALLING]');
this.showSubtitle('[NIGHT IS FALLING]', 2000, 'System');
break;
case 'achievement':
this.showScreenFlash('success', data.message || '[ACHIEVEMENT UNLOCKED]');
this.showSubtitle(data.message || '[ACHIEVEMENT UNLOCKED]', 3000, 'System');
break;
case 'ui_click':
this.showSubtitle('[CLICK]', 300, null);
break;
case 'ui_hover':
this.showSubtitle('[HOVER]', 200, null);
break;
}
}
/**
* Show fishing bobber visual cue
*/
showFishingBobberCue() {
if (!this.settings.fishingBobberEnabled) return;
const width = this.scene.cameras.main.width;
const height = this.scene.cameras.main.height;
// Create bobber indicator if it doesn't exist
if (!this.fishingBobberIndicator) {
this.fishingBobberIndicator = this.scene.add.container(width / 2, height / 2);
this.fishingBobberIndicator.setDepth(10002);
this.fishingBobberIndicator.setScrollFactor(0);
// Circle background
const circle = this.scene.add.circle(0, 0, 60, 0xff6600, 0.8);
this.fishingBobberIndicator.add(circle);
// Exclamation mark
const exclamation = this.scene.add.text(0, 0, '!', {
fontSize: '48px',
fontFamily: 'Arial',
fontStyle: 'bold',
color: '#ffffff'
});
exclamation.setOrigin(0.5);
this.fishingBobberIndicator.add(exclamation);
// Text below
const text = this.scene.add.text(0, 80, 'FISH BITING!\nPress E', {
fontSize: '20px',
fontFamily: 'Arial',
fontStyle: 'bold',
color: '#ffffff',
align: 'center'
});
text.setOrigin(0.5);
this.fishingBobberIndicator.add(text);
}
// Show and animate
this.fishingBobberIndicator.setVisible(true);
this.fishingBobberIndicator.setAlpha(0);
// Fade in and pulse
this.scene.tweens.add({
targets: this.fishingBobberIndicator,
alpha: 1,
duration: 200,
onComplete: () => {
// Pulse animation
this.scene.tweens.add({
targets: this.fishingBobberIndicator,
scale: { from: 1, to: 1.2 },
duration: 300,
yoyo: true,
repeat: 5,
onComplete: () => {
// Fade out
this.scene.tweens.add({
targets: this.fishingBobberIndicator,
alpha: 0,
duration: 300,
onComplete: () => {
this.fishingBobberIndicator.setVisible(false);
}
});
}
});
}
});
console.log('🎣 Fishing bobber cue shown');
}
/**
* Set subtitle background opacity
*/
setSubtitleOpacity(opacity) {
this.settings.subtitleOpacity = Phaser.Math.Clamp(opacity, 0, 1);
if (this.subtitleBackground) {
this.subtitleBackground.setAlpha(this.settings.subtitleOpacity);
}
this.saveSettings();
console.log('📊 Subtitle opacity set to:', this.settings.subtitleOpacity);
}
/**
* Add custom speaker color
*/
addSpeakerColor(speaker, color) {
this.speakerColors[speaker] = color;
console.log(`🎨 Added speaker color: ${speaker} = ${color}`);
}
// ========== SETTINGS ==========
toggleHeartbeat(enabled) {
@@ -364,6 +655,65 @@ class VisualSoundCueSystem {
this.settings.subtitlesEnabled = enabled;
if (!enabled) this.hideSubtitle();
this.saveSettings();
console.log('💬 Subtitles:', enabled ? 'ENABLED' : 'DISABLED');
}
toggleDirectionalArrows(enabled) {
this.settings.directionalArrowsEnabled = enabled;
if (!enabled) {
this.hideDirectionalArrows();
}
this.saveSettings();
console.log('➡️ Directional Arrows:', enabled ? 'ENABLED' : 'DISABLED');
}
toggleSpeakerNames(enabled) {
this.settings.speakerNamesEnabled = enabled;
this.saveSettings();
console.log('👤 Speaker Names:', enabled ? 'ENABLED' : 'DISABLED');
}
toggleFishingBobber(enabled) {
this.settings.fishingBobberEnabled = enabled;
this.saveSettings();
console.log('🎣 Fishing Bobber Cue:', enabled ? 'ENABLED' : 'DISABLED');
}
/**
* Set subtitle text size
* @param {string} size - 'small', 'medium', 'large', 'very-large'
*/
setSubtitleSize(size) {
if (!this.subtitleSizes[size]) {
console.error(`Invalid subtitle size: ${size}. Valid options: small, medium, large, very-large`);
return;
}
this.settings.subtitleSize = size;
const sizes = this.subtitleSizes[size];
// Update text sizes
if (this.subtitleText) {
this.subtitleText.setFontSize(sizes.main);
}
if (this.subtitleSpeaker) {
this.subtitleSpeaker.setFontSize(sizes.speaker);
}
if (this.subtitleArrows.left) {
this.subtitleArrows.left.setFontSize(sizes.arrow);
}
if (this.subtitleArrows.right) {
this.subtitleArrows.right.setFontSize(sizes.arrow);
}
// Adjust background height based on text size
if (this.subtitleBackground) {
const bgHeight = sizes.main * 4; // 4x font size for padding
this.subtitleBackground.setSize(this.subtitleBackground.width, bgHeight);
}
this.saveSettings();
console.log(`📏 Subtitle size set to: ${size.toUpperCase()} (${sizes.main}px)`);
}
saveSettings() {