Refactor code for consistency and readability
- Updated import statements to use consistent formatting across files. - Adjusted method definitions and class constructors for uniform spacing and style. - Simplified promise handling and error messages in state handlers. - Enhanced state transition logic in various state handlers. - Improved quirk animation handling in WaitStateHandler. - Streamlined animation loading and caching mechanisms in AnimationLoader. - Updated Vite configuration for aliasing.
This commit is contained in:
@ -3,8 +3,8 @@
|
||||
* @module states
|
||||
*/
|
||||
|
||||
import { StateHandler } from './StateHandler.js';
|
||||
import { States, Emotions } from '../constants.js';
|
||||
import { StateHandler } from './StateHandler.js'
|
||||
import { States, Emotions } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Handler for the React state
|
||||
@ -12,148 +12,148 @@ import { States, Emotions } from '../constants.js';
|
||||
* @extends StateHandler
|
||||
*/
|
||||
export class ReactStateHandler extends StateHandler {
|
||||
/**
|
||||
/**
|
||||
* Create a react state handler
|
||||
* @param {OwenAnimationContext} context - The animation context
|
||||
*/
|
||||
constructor(context) {
|
||||
super(States.REACT, context);
|
||||
constructor (context) {
|
||||
super(States.REACT, context)
|
||||
|
||||
/**
|
||||
/**
|
||||
* Current emotional state
|
||||
* @type {string}
|
||||
*/
|
||||
this.emotion = Emotions.NEUTRAL;
|
||||
}
|
||||
this.emotion = Emotions.NEUTRAL
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Enter the react state
|
||||
* @param {string|null} [_fromState=null] - The previous state (unused)
|
||||
* @param {string} [emotion=Emotions.NEUTRAL] - The emotion to enter with
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async enter(_fromState = null, emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering REACT state with emotion: ${emotion}`);
|
||||
this.emotion = emotion;
|
||||
async enter (_fromState = null, emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering REACT state with emotion: ${emotion}`)
|
||||
this.emotion = emotion
|
||||
|
||||
// Play appropriate reaction
|
||||
const reactionClip = this.context.getClip('react_idle_L');
|
||||
if (reactionClip) {
|
||||
await reactionClip.play();
|
||||
this.currentClip = reactionClip;
|
||||
}
|
||||
// Play appropriate reaction
|
||||
const reactionClip = this.context.getClip('react_idle_L')
|
||||
if (reactionClip) {
|
||||
await reactionClip.play()
|
||||
this.currentClip = reactionClip
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Exit the react state
|
||||
* @param {string|null} [toState=null] - The next state
|
||||
* @param {string} [emotion=Emotions.NEUTRAL] - The emotion to exit with
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async exit(toState = null, emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting REACT state to ${toState} with emotion: ${emotion}`);
|
||||
async exit (toState = null, emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting REACT state to ${toState} with emotion: ${emotion}`)
|
||||
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip();
|
||||
}
|
||||
|
||||
// Play emotional transition if available
|
||||
let transitionName;
|
||||
if (emotion !== Emotions.NEUTRAL) {
|
||||
transitionName = `react_${this.emotion}2${toState}_${emotion}_T`;
|
||||
} else {
|
||||
transitionName = `react_2${toState}_T`;
|
||||
}
|
||||
|
||||
const transition = this.context.getClip(transitionName);
|
||||
if (transition) {
|
||||
await transition.play();
|
||||
await this.waitForClipEnd(transition);
|
||||
}
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip()
|
||||
}
|
||||
|
||||
/**
|
||||
// Play emotional transition if available
|
||||
let transitionName
|
||||
if (emotion !== Emotions.NEUTRAL) {
|
||||
transitionName = `react_${this.emotion}2${toState}_${emotion}_T`
|
||||
} else {
|
||||
transitionName = `react_2${toState}_T`
|
||||
}
|
||||
|
||||
const transition = this.context.getClip(transitionName)
|
||||
if (transition) {
|
||||
await transition.play()
|
||||
await this.waitForClipEnd(transition)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a user message in react state
|
||||
* @param {string} message - The user message
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async handleMessage(message) {
|
||||
// Analyze message sentiment to determine emotion
|
||||
const emotion = this.analyzeMessageEmotion(message);
|
||||
this.emotion = emotion;
|
||||
async handleMessage (message) {
|
||||
// Analyze message sentiment to determine emotion
|
||||
const emotion = this.analyzeMessageEmotion(message)
|
||||
this.emotion = emotion
|
||||
|
||||
// Play emotional reaction if needed
|
||||
if (emotion !== Emotions.NEUTRAL) {
|
||||
const emotionalReaction = this.context.getClip(`react_${emotion}_Q`);
|
||||
if (emotionalReaction) {
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip(0.2);
|
||||
}
|
||||
await emotionalReaction.play();
|
||||
await this.waitForClipEnd(emotionalReaction);
|
||||
}
|
||||
// Play emotional reaction if needed
|
||||
if (emotion !== Emotions.NEUTRAL) {
|
||||
const emotionalReaction = this.context.getClip(`react_${emotion}_Q`)
|
||||
if (emotionalReaction) {
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip(0.2)
|
||||
}
|
||||
await emotionalReaction.play()
|
||||
await this.waitForClipEnd(emotionalReaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Analyze message to determine emotional response
|
||||
* @private
|
||||
* @param {string} message - The message to analyze
|
||||
* @returns {string} The determined emotion
|
||||
*/
|
||||
analyzeMessageEmotion(message) {
|
||||
const text = message.toLowerCase();
|
||||
analyzeMessageEmotion (message) {
|
||||
const text = message.toLowerCase()
|
||||
|
||||
// Check for urgent/angry indicators
|
||||
if (
|
||||
text.includes('!') ||
|
||||
// Check for urgent/angry indicators
|
||||
if (
|
||||
text.includes('!') ||
|
||||
text.includes('urgent') ||
|
||||
text.includes('asap') ||
|
||||
text.includes('hurry')
|
||||
) {
|
||||
return Emotions.ANGRY;
|
||||
}
|
||||
) {
|
||||
return Emotions.ANGRY
|
||||
}
|
||||
|
||||
// Check for error/shocked indicators
|
||||
if (
|
||||
text.includes('error') ||
|
||||
// Check for error/shocked indicators
|
||||
if (
|
||||
text.includes('error') ||
|
||||
text.includes('problem') ||
|
||||
text.includes('issue') ||
|
||||
text.includes('bug') ||
|
||||
text.includes('broken')
|
||||
) {
|
||||
return Emotions.SHOCKED;
|
||||
}
|
||||
) {
|
||||
return Emotions.SHOCKED
|
||||
}
|
||||
|
||||
// Check for positive/happy indicators
|
||||
if (
|
||||
text.includes('great') ||
|
||||
// Check for positive/happy indicators
|
||||
if (
|
||||
text.includes('great') ||
|
||||
text.includes('awesome') ||
|
||||
text.includes('good') ||
|
||||
text.includes('excellent') ||
|
||||
text.includes('perfect')
|
||||
) {
|
||||
return Emotions.HAPPY;
|
||||
}
|
||||
) {
|
||||
return Emotions.HAPPY
|
||||
}
|
||||
|
||||
// Check for sad indicators
|
||||
if (
|
||||
text.includes('sad') ||
|
||||
// Check for sad indicators
|
||||
if (
|
||||
text.includes('sad') ||
|
||||
text.includes('disappointed') ||
|
||||
text.includes('failed') ||
|
||||
text.includes('wrong')
|
||||
) {
|
||||
return Emotions.SAD;
|
||||
}
|
||||
|
||||
return Emotions.NEUTRAL;
|
||||
) {
|
||||
return Emotions.SAD
|
||||
}
|
||||
|
||||
/**
|
||||
return Emotions.NEUTRAL
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available transitions from react state
|
||||
* @returns {string[]} Array of available state transitions
|
||||
*/
|
||||
getAvailableTransitions() {
|
||||
return [ States.TYPE, States.WAIT ];
|
||||
}
|
||||
getAvailableTransitions () {
|
||||
return [States.TYPE, States.WAIT]
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
* @module states
|
||||
*/
|
||||
|
||||
import { StateHandler } from './StateHandler.js';
|
||||
import { States, Emotions } from '../constants.js';
|
||||
import { StateHandler } from './StateHandler.js'
|
||||
import { States, Emotions } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Handler for the Sleep state
|
||||
@ -16,20 +16,20 @@ export class SleepStateHandler extends StateHandler {
|
||||
* Create a sleep state handler
|
||||
* @param {OwenAnimationContext} context - The animation context
|
||||
*/
|
||||
constructor(context) {
|
||||
super(States.SLEEP, context);
|
||||
constructor (context) {
|
||||
super(States.SLEEP, context)
|
||||
|
||||
/**
|
||||
* Sleep animation clip
|
||||
* @type {AnimationClip|null}
|
||||
*/
|
||||
this.sleepClip = null;
|
||||
this.sleepClip = null
|
||||
|
||||
/**
|
||||
* Whether the character is in deep sleep
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isDeepSleep = false;
|
||||
this.isDeepSleep = false
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,24 +38,24 @@ export class SleepStateHandler extends StateHandler {
|
||||
* @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to enter with (unused)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async enter(fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering SLEEP state from ${fromState}`);
|
||||
async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering SLEEP state from ${fromState}`)
|
||||
|
||||
// Play sleep transition if available
|
||||
const sleepTransition = this.context.getClip('wait_2sleep_T');
|
||||
const sleepTransition = this.context.getClip('wait_2sleep_T')
|
||||
if (sleepTransition) {
|
||||
await sleepTransition.play();
|
||||
await this.waitForClipEnd(sleepTransition);
|
||||
await sleepTransition.play()
|
||||
await this.waitForClipEnd(sleepTransition)
|
||||
}
|
||||
|
||||
// Start sleep loop
|
||||
this.sleepClip = this.context.getClip('sleep_idle_L');
|
||||
this.sleepClip = this.context.getClip('sleep_idle_L')
|
||||
if (this.sleepClip) {
|
||||
await this.sleepClip.play();
|
||||
this.currentClip = this.sleepClip;
|
||||
await this.sleepClip.play()
|
||||
this.currentClip = this.sleepClip
|
||||
}
|
||||
|
||||
this.isDeepSleep = true;
|
||||
this.isDeepSleep = true
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,27 +64,27 @@ export class SleepStateHandler extends StateHandler {
|
||||
* @param {string} [emotion=Emotions.NEUTRAL] - The emotion to exit with
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async exit(toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting SLEEP state to ${toState}`);
|
||||
this.isDeepSleep = false;
|
||||
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting SLEEP state to ${toState}`)
|
||||
this.isDeepSleep = false
|
||||
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip();
|
||||
await this.stopCurrentClip()
|
||||
}
|
||||
|
||||
// Play wake up animation
|
||||
const wakeUpClip = this.context.getClip('sleep_wakeup_T');
|
||||
const wakeUpClip = this.context.getClip('sleep_wakeup_T')
|
||||
if (wakeUpClip) {
|
||||
await wakeUpClip.play();
|
||||
await this.waitForClipEnd(wakeUpClip);
|
||||
await wakeUpClip.play()
|
||||
await this.waitForClipEnd(wakeUpClip)
|
||||
}
|
||||
|
||||
// Play transition to next state if available
|
||||
const transitionName = `sleep_2${toState}_T`;
|
||||
const transition = this.context.getClip(transitionName);
|
||||
const transitionName = `sleep_2${toState}_T`
|
||||
const transition = this.context.getClip(transitionName)
|
||||
if (transition) {
|
||||
await transition.play();
|
||||
await this.waitForClipEnd(transition);
|
||||
await transition.play()
|
||||
await this.waitForClipEnd(transition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ export class SleepStateHandler extends StateHandler {
|
||||
* @param {number} _deltaTime - Time elapsed since last update (ms, unused)
|
||||
* @returns {void}
|
||||
*/
|
||||
update(_deltaTime) {
|
||||
update (_deltaTime) {
|
||||
// Sleep state doesn't need regular updates
|
||||
// Character remains asleep until external stimulus
|
||||
}
|
||||
@ -103,12 +103,12 @@ export class SleepStateHandler extends StateHandler {
|
||||
* @param {string} _message - The user message (unused, just triggers wake up)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async handleMessage(_message) {
|
||||
async handleMessage (_message) {
|
||||
// Any message should wake up the character
|
||||
if (this.isDeepSleep) {
|
||||
console.log('Waking up due to user message');
|
||||
console.log('Waking up due to user message')
|
||||
// This will trigger a state transition to REACT
|
||||
await this.context.transitionTo(States.REACT);
|
||||
await this.context.transitionTo(States.REACT)
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,25 +116,25 @@ export class SleepStateHandler extends StateHandler {
|
||||
* Get available transitions from sleep state
|
||||
* @returns {string[]} Array of available state transitions
|
||||
*/
|
||||
getAvailableTransitions() {
|
||||
return [ States.WAIT, States.REACT ];
|
||||
getAvailableTransitions () {
|
||||
return [States.WAIT, States.REACT]
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if in deep sleep
|
||||
* @returns {boolean} True if in deep sleep, false otherwise
|
||||
*/
|
||||
isInDeepSleep() {
|
||||
return this.isDeepSleep;
|
||||
isInDeepSleep () {
|
||||
return this.isDeepSleep
|
||||
}
|
||||
|
||||
/**
|
||||
* Force wake up from sleep
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async wakeUp() {
|
||||
async wakeUp () {
|
||||
if (this.isDeepSleep) {
|
||||
await this.context.transitionTo(States.WAIT);
|
||||
await this.context.transitionTo(States.WAIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
* @module states
|
||||
*/
|
||||
|
||||
import { WaitStateHandler } from './WaitStateHandler.js';
|
||||
import { ReactStateHandler } from './ReactStateHandler.js';
|
||||
import { TypeStateHandler } from './TypeStateHandler.js';
|
||||
import { SleepStateHandler } from './SleepStateHandler.js';
|
||||
import { States } from '../constants.js';
|
||||
import { WaitStateHandler } from './WaitStateHandler.js'
|
||||
import { ReactStateHandler } from './ReactStateHandler.js'
|
||||
import { TypeStateHandler } from './TypeStateHandler.js'
|
||||
import { SleepStateHandler } from './SleepStateHandler.js'
|
||||
import { States } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Factory for creating state handlers using dependency injection
|
||||
@ -17,19 +17,19 @@ export class StateFactory {
|
||||
/**
|
||||
* Create a state factory
|
||||
*/
|
||||
constructor() {
|
||||
constructor () {
|
||||
/**
|
||||
* Registry of state handler classes
|
||||
* @type {Map<string, Function>}
|
||||
* @private
|
||||
*/
|
||||
this.stateHandlers = new Map();
|
||||
this.stateHandlers = new Map()
|
||||
|
||||
// Register default state handlers
|
||||
this.registerStateHandler(States.WAIT, WaitStateHandler);
|
||||
this.registerStateHandler(States.REACT, ReactStateHandler);
|
||||
this.registerStateHandler(States.TYPE, TypeStateHandler);
|
||||
this.registerStateHandler(States.SLEEP, SleepStateHandler);
|
||||
this.registerStateHandler(States.WAIT, WaitStateHandler)
|
||||
this.registerStateHandler(States.REACT, ReactStateHandler)
|
||||
this.registerStateHandler(States.TYPE, TypeStateHandler)
|
||||
this.registerStateHandler(States.SLEEP, SleepStateHandler)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,8 +38,8 @@ export class StateFactory {
|
||||
* @param {Function} handlerClass - The handler class constructor
|
||||
* @returns {void}
|
||||
*/
|
||||
registerStateHandler(stateName, handlerClass) {
|
||||
this.stateHandlers.set(stateName, handlerClass);
|
||||
registerStateHandler (stateName, handlerClass) {
|
||||
this.stateHandlers.set(stateName, handlerClass)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,21 +49,21 @@ export class StateFactory {
|
||||
* @returns {StateHandler} The created state handler
|
||||
* @throws {Error} If state handler is not registered
|
||||
*/
|
||||
createStateHandler(stateName, context) {
|
||||
const HandlerClass = this.stateHandlers.get(stateName);
|
||||
createStateHandler (stateName, context) {
|
||||
const HandlerClass = this.stateHandlers.get(stateName)
|
||||
if (!HandlerClass) {
|
||||
throw new Error(`No handler registered for state: ${stateName}`);
|
||||
throw new Error(`No handler registered for state: ${stateName}`)
|
||||
}
|
||||
|
||||
return new HandlerClass(context);
|
||||
return new HandlerClass(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available state names
|
||||
* @returns {string[]} Array of registered state names
|
||||
*/
|
||||
getAvailableStates() {
|
||||
return Array.from(this.stateHandlers.keys());
|
||||
getAvailableStates () {
|
||||
return Array.from(this.stateHandlers.keys())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,8 +71,8 @@ export class StateFactory {
|
||||
* @param {string} stateName - The state name to check
|
||||
* @returns {boolean} True if registered, false otherwise
|
||||
*/
|
||||
isStateRegistered(stateName) {
|
||||
return this.stateHandlers.has(stateName);
|
||||
isStateRegistered (stateName) {
|
||||
return this.stateHandlers.has(stateName)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,7 +80,7 @@ export class StateFactory {
|
||||
* @param {string} stateName - The state name to unregister
|
||||
* @returns {boolean} True if removed, false if not found
|
||||
*/
|
||||
unregisterStateHandler(stateName) {
|
||||
return this.stateHandlers.delete(stateName);
|
||||
unregisterStateHandler (stateName) {
|
||||
return this.stateHandlers.delete(stateName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* @module StateHandler
|
||||
*/
|
||||
|
||||
import { Emotions, Config } from '../constants.js';
|
||||
import { Emotions, Config } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Abstract base class for state handlers
|
||||
@ -16,30 +16,30 @@ export class StateHandler {
|
||||
* @param {string} stateName - The name of the state
|
||||
* @param {OwenAnimationContext} context - The animation context
|
||||
*/
|
||||
constructor(stateName, context) {
|
||||
constructor (stateName, context) {
|
||||
/**
|
||||
* The name of this state
|
||||
* @type {string}
|
||||
*/
|
||||
this.stateName = stateName;
|
||||
this.stateName = stateName
|
||||
|
||||
/**
|
||||
* The animation context
|
||||
* @type {OwenAnimationContext}
|
||||
*/
|
||||
this.context = context;
|
||||
this.context = context
|
||||
|
||||
/**
|
||||
* Currently playing animation clip
|
||||
* @type {AnimationClip|null}
|
||||
*/
|
||||
this.currentClip = null;
|
||||
this.currentClip = null
|
||||
|
||||
/**
|
||||
* Nested state information
|
||||
* @type {Object|null}
|
||||
*/
|
||||
this.nestedState = null;
|
||||
this.nestedState = null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,8 +50,8 @@ export class StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} Must be implemented by subclasses
|
||||
*/
|
||||
async enter(_fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||
throw new Error('enter method must be implemented by subclasses');
|
||||
async enter (_fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||
throw new Error('enter method must be implemented by subclasses')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,8 +62,8 @@ export class StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} Must be implemented by subclasses
|
||||
*/
|
||||
async exit(_toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
throw new Error('exit method must be implemented by subclasses');
|
||||
async exit (_toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
throw new Error('exit method must be implemented by subclasses')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,7 +71,7 @@ export class StateHandler {
|
||||
* @param {number} _deltaTime - Time elapsed since last update (ms, unused in base class)
|
||||
* @returns {void}
|
||||
*/
|
||||
update(_deltaTime) {
|
||||
update (_deltaTime) {
|
||||
// Override in subclasses if needed
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ export class StateHandler {
|
||||
* @param {string} _message - The user message (unused in base class)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async handleMessage(_message) {
|
||||
async handleMessage (_message) {
|
||||
// Override in subclasses if needed
|
||||
}
|
||||
|
||||
@ -88,8 +88,8 @@ export class StateHandler {
|
||||
* Get available transitions from this state
|
||||
* @returns {string[]} Array of state names that can be transitioned to
|
||||
*/
|
||||
getAvailableTransitions() {
|
||||
return [];
|
||||
getAvailableTransitions () {
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,17 +98,17 @@ export class StateHandler {
|
||||
* @param {AnimationClip} clip - The animation clip to wait for
|
||||
* @returns {Promise<void>} Promise that resolves when the clip finishes
|
||||
*/
|
||||
async waitForClipEnd(clip) {
|
||||
async waitForClipEnd (clip) {
|
||||
return new Promise((resolve) => {
|
||||
const checkFinished = () => {
|
||||
if (!clip.isPlaying()) {
|
||||
resolve();
|
||||
resolve()
|
||||
} else {
|
||||
requestAnimationFrame(checkFinished);
|
||||
requestAnimationFrame(checkFinished)
|
||||
}
|
||||
};
|
||||
checkFinished();
|
||||
});
|
||||
}
|
||||
checkFinished()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,10 +117,10 @@ export class StateHandler {
|
||||
* @param {number} [fadeOutDuration] - Fade out duration
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async stopCurrentClip(fadeOutDuration = Config.DEFAULT_FADE_OUT) {
|
||||
async stopCurrentClip (fadeOutDuration = Config.DEFAULT_FADE_OUT) {
|
||||
if (this.currentClip) {
|
||||
await this.currentClip.stop(fadeOutDuration);
|
||||
this.currentClip = null;
|
||||
await this.currentClip.stop(fadeOutDuration)
|
||||
this.currentClip = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
* @module states
|
||||
*/
|
||||
|
||||
import { StateHandler } from './StateHandler.js';
|
||||
import { States, Emotions } from '../constants.js';
|
||||
import { StateHandler } from './StateHandler.js'
|
||||
import { States, Emotions } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Handler for the Type state
|
||||
@ -16,20 +16,20 @@ export class TypeStateHandler extends StateHandler {
|
||||
* Create a type state handler
|
||||
* @param {OwenAnimationContext} context - The animation context
|
||||
*/
|
||||
constructor(context) {
|
||||
super(States.TYPE, context);
|
||||
constructor (context) {
|
||||
super(States.TYPE, context)
|
||||
|
||||
/**
|
||||
* Current emotional state
|
||||
* @type {string}
|
||||
*/
|
||||
this.emotion = Emotions.NEUTRAL;
|
||||
this.emotion = Emotions.NEUTRAL
|
||||
|
||||
/**
|
||||
* Whether currently typing
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isTyping = false;
|
||||
this.isTyping = false
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,21 +38,21 @@ export class TypeStateHandler extends StateHandler {
|
||||
* @param {string} [emotion=Emotions.NEUTRAL] - The emotion to enter with
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async enter(_fromState = null, emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering TYPE state with emotion: ${emotion}`);
|
||||
this.emotion = emotion;
|
||||
this.isTyping = true;
|
||||
async enter (_fromState = null, emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering TYPE state with emotion: ${emotion}`)
|
||||
this.emotion = emotion
|
||||
this.isTyping = true
|
||||
|
||||
// Play appropriate typing animation
|
||||
let typingClipName = 'type_idle_L';
|
||||
let typingClipName = 'type_idle_L'
|
||||
if (emotion !== Emotions.NEUTRAL) {
|
||||
typingClipName = `type_${emotion}_L`;
|
||||
typingClipName = `type_${emotion}_L`
|
||||
}
|
||||
|
||||
const typingClip = this.context.getClip(typingClipName);
|
||||
const typingClip = this.context.getClip(typingClipName)
|
||||
if (typingClip) {
|
||||
await typingClip.play();
|
||||
this.currentClip = typingClip;
|
||||
await typingClip.play()
|
||||
this.currentClip = typingClip
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,24 +62,24 @@ export class TypeStateHandler extends StateHandler {
|
||||
* @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to exit with (unused)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async exit(toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting TYPE state to ${toState}`);
|
||||
this.isTyping = false;
|
||||
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting TYPE state to ${toState}`)
|
||||
this.isTyping = false
|
||||
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip();
|
||||
await this.stopCurrentClip()
|
||||
}
|
||||
|
||||
// Play transition if available
|
||||
let transitionName = `type_2${toState}_T`;
|
||||
let transitionName = `type_2${toState}_T`
|
||||
if (this.emotion !== Emotions.NEUTRAL) {
|
||||
transitionName = `type_${this.emotion}2${toState}_T`;
|
||||
transitionName = `type_${this.emotion}2${toState}_T`
|
||||
}
|
||||
|
||||
const transition = this.context.getClip(transitionName);
|
||||
const transition = this.context.getClip(transitionName)
|
||||
if (transition) {
|
||||
await transition.play();
|
||||
await this.waitForClipEnd(transition);
|
||||
await transition.play()
|
||||
await this.waitForClipEnd(transition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,34 +87,34 @@ export class TypeStateHandler extends StateHandler {
|
||||
* Finish typing and prepare to transition
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async finishTyping() {
|
||||
if (!this.isTyping) return;
|
||||
async finishTyping () {
|
||||
if (!this.isTyping) return
|
||||
|
||||
// Play typing finish animation if available
|
||||
const finishClip = this.context.getClip('type_finish_Q');
|
||||
const finishClip = this.context.getClip('type_finish_Q')
|
||||
if (finishClip && this.currentClip) {
|
||||
await this.stopCurrentClip(0.2);
|
||||
await finishClip.play();
|
||||
await this.waitForClipEnd(finishClip);
|
||||
await this.stopCurrentClip(0.2)
|
||||
await finishClip.play()
|
||||
await this.waitForClipEnd(finishClip)
|
||||
}
|
||||
|
||||
this.isTyping = false;
|
||||
this.isTyping = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available transitions from type state
|
||||
* @returns {string[]} Array of available state transitions
|
||||
*/
|
||||
getAvailableTransitions() {
|
||||
return [ States.WAIT, States.REACT ];
|
||||
getAvailableTransitions () {
|
||||
return [States.WAIT, States.REACT]
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently typing
|
||||
* @returns {boolean} True if typing, false otherwise
|
||||
*/
|
||||
getIsTyping() {
|
||||
return this.isTyping;
|
||||
getIsTyping () {
|
||||
return this.isTyping
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,7 +122,7 @@ export class TypeStateHandler extends StateHandler {
|
||||
* @param {boolean} typing - Whether currently typing
|
||||
* @returns {void}
|
||||
*/
|
||||
setTyping(typing) {
|
||||
this.isTyping = typing;
|
||||
setTyping (typing) {
|
||||
this.isTyping = typing
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
* @module states
|
||||
*/
|
||||
|
||||
import { StateHandler } from './StateHandler.js';
|
||||
import { States, Emotions, Config } from '../constants.js';
|
||||
import { StateHandler } from './StateHandler.js'
|
||||
import { States, Emotions, Config } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Handler for the Wait/Idle state
|
||||
@ -12,127 +12,127 @@ import { States, Emotions, Config } from '../constants.js';
|
||||
* @extends StateHandler
|
||||
*/
|
||||
export class WaitStateHandler extends StateHandler {
|
||||
/**
|
||||
/**
|
||||
* Create a wait state handler
|
||||
* @param {OwenAnimationContext} context - The animation context
|
||||
*/
|
||||
constructor(context) {
|
||||
super(States.WAIT, context);
|
||||
constructor (context) {
|
||||
super(States.WAIT, context)
|
||||
|
||||
/**
|
||||
/**
|
||||
* The main idle animation clip
|
||||
* @type {AnimationClip|null}
|
||||
*/
|
||||
this.idleClip = null;
|
||||
this.idleClip = null
|
||||
|
||||
/**
|
||||
/**
|
||||
* Available quirk animations
|
||||
* @type {AnimationClip[]}
|
||||
*/
|
||||
this.quirks = [];
|
||||
this.quirks = []
|
||||
|
||||
/**
|
||||
/**
|
||||
* Timer for quirk animations
|
||||
* @type {number}
|
||||
*/
|
||||
this.quirkTimer = 0;
|
||||
this.quirkTimer = 0
|
||||
|
||||
/**
|
||||
/**
|
||||
* Interval between quirk attempts (ms)
|
||||
* @type {number}
|
||||
*/
|
||||
this.quirkInterval = Config.QUIRK_INTERVAL;
|
||||
}
|
||||
this.quirkInterval = Config.QUIRK_INTERVAL
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Enter the wait state
|
||||
* @param {string|null} [fromState=null] - The previous state
|
||||
* @param {string} [emotion=Emotions.NEUTRAL] - The emotion to enter with
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async enter(fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering WAIT state from ${fromState}`);
|
||||
async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering WAIT state from ${fromState}`)
|
||||
|
||||
// Play idle loop
|
||||
this.idleClip = this.context.getClip('wait_idle_L');
|
||||
if (this.idleClip) {
|
||||
await this.idleClip.play();
|
||||
this.currentClip = this.idleClip;
|
||||
}
|
||||
|
||||
// Collect available quirks
|
||||
this.quirks = this.context.getClipsByPattern('wait_*_Q');
|
||||
this.quirkTimer = 0;
|
||||
// Play idle loop
|
||||
this.idleClip = this.context.getClip('wait_idle_L')
|
||||
if (this.idleClip) {
|
||||
await this.idleClip.play()
|
||||
this.currentClip = this.idleClip
|
||||
}
|
||||
|
||||
/**
|
||||
// Collect available quirks
|
||||
this.quirks = this.context.getClipsByPattern('wait_*_Q')
|
||||
this.quirkTimer = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the wait state
|
||||
* @param {string|null} [toState=null] - The next state
|
||||
* @param {string} [emotion=Emotions.NEUTRAL] - The emotion to exit with
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async exit(toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting WAIT state to ${toState}`);
|
||||
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting WAIT state to ${toState}`)
|
||||
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip();
|
||||
}
|
||||
|
||||
// Play transition if available
|
||||
const transitionName = `wait_2${toState}_T`;
|
||||
const transition = this.context.getClip(transitionName);
|
||||
if (transition) {
|
||||
await transition.play();
|
||||
await this.waitForClipEnd(transition);
|
||||
}
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip()
|
||||
}
|
||||
|
||||
/**
|
||||
// Play transition if available
|
||||
const transitionName = `wait_2${toState}_T`
|
||||
const transition = this.context.getClip(transitionName)
|
||||
if (transition) {
|
||||
await transition.play()
|
||||
await this.waitForClipEnd(transition)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the wait state
|
||||
* @param {number} deltaTime - Time elapsed since last update (ms)
|
||||
* @returns {void}
|
||||
*/
|
||||
update(deltaTime) {
|
||||
this.quirkTimer += deltaTime;
|
||||
update (deltaTime) {
|
||||
this.quirkTimer += deltaTime
|
||||
|
||||
// Randomly play quirks
|
||||
if (this.quirkTimer > this.quirkInterval && Math.random() < Config.QUIRK_PROBABILITY) {
|
||||
this.playRandomQuirk();
|
||||
this.quirkTimer = 0;
|
||||
}
|
||||
// Randomly play quirks
|
||||
if (this.quirkTimer > this.quirkInterval && Math.random() < Config.QUIRK_PROBABILITY) {
|
||||
this.playRandomQuirk()
|
||||
this.quirkTimer = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Play a random quirk animation
|
||||
* @private
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async playRandomQuirk() {
|
||||
if (this.quirks.length === 0) return;
|
||||
async playRandomQuirk () {
|
||||
if (this.quirks.length === 0) return
|
||||
|
||||
const quirk = this.quirks[ Math.floor(Math.random() * this.quirks.length) ];
|
||||
const quirk = this.quirks[Math.floor(Math.random() * this.quirks.length)]
|
||||
|
||||
// Fade out idle
|
||||
if (this.idleClip) {
|
||||
await this.idleClip.stop(0.2);
|
||||
}
|
||||
|
||||
// Play quirk
|
||||
await quirk.play();
|
||||
await this.waitForClipEnd(quirk);
|
||||
|
||||
// Return to idle
|
||||
if (this.idleClip) {
|
||||
await this.idleClip.play();
|
||||
this.currentClip = this.idleClip;
|
||||
}
|
||||
// Fade out idle
|
||||
if (this.idleClip) {
|
||||
await this.idleClip.stop(0.2)
|
||||
}
|
||||
|
||||
/**
|
||||
// Play quirk
|
||||
await quirk.play()
|
||||
await this.waitForClipEnd(quirk)
|
||||
|
||||
// Return to idle
|
||||
if (this.idleClip) {
|
||||
await this.idleClip.play()
|
||||
this.currentClip = this.idleClip
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available transitions from wait state
|
||||
* @returns {string[]} Array of available state transitions
|
||||
*/
|
||||
getAvailableTransitions() {
|
||||
return [ States.REACT, States.SLEEP ];
|
||||
}
|
||||
getAvailableTransitions () {
|
||||
return [States.REACT, States.SLEEP]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user