feat: add dynamic voice setting
This commit is contained in:
parent
21eb0411d1
commit
0d77ea7f84
4 changed files with 187 additions and 173 deletions
|
@ -1,8 +1,7 @@
|
|||
import type CameraControls from 'camera-controls'
|
||||
import { writable } from 'svelte/store'
|
||||
import Hornet from '../../models/Hornet.svelte'
|
||||
import type { Mesh, Object3DEventMap } from 'three'
|
||||
import type { Group } from 'three/examples/jsm/libs/tween.module.js'
|
||||
import type CameraControls from "camera-controls";
|
||||
import { writable } from "svelte/store";
|
||||
import type { Mesh, Object3DEventMap } from "three";
|
||||
import type { Group } from "three/examples/jsm/libs/tween.module.js";
|
||||
|
||||
export const cameraControls = writable<CameraControls>()
|
||||
export const mesh = writable<Mesh>()
|
||||
export const cameraControls = writable<CameraControls>();
|
||||
export const mesh = writable<Mesh>();
|
||||
|
|
|
@ -14,47 +14,44 @@ Sequences should be either event-driven or periodic. In the case of periodic
|
|||
sequences, invoke them in the periodicSequence function
|
||||
*/
|
||||
|
||||
import { Notifications } from '../Notifications/notifications'
|
||||
import { sequenceStore } from '../stores/sequenceStore'
|
||||
import { settingsStore } from '../stores/settingsStore'
|
||||
import { get } from 'svelte/store'
|
||||
import getVoicePath from '../utils/getVoicePath'
|
||||
import { tick } from 'svelte'
|
||||
import { Notifications } from "../Notifications/notifications";
|
||||
import { sequenceStore } from "../stores/sequenceStore";
|
||||
import { settingsStore } from "../stores/settingsStore";
|
||||
import { get } from "svelte/store";
|
||||
import getVoicePath from "../utils/getVoicePath";
|
||||
import { tick } from "svelte";
|
||||
|
||||
// await a "tick" (a svelte update frame) at the start of every sequence so that
|
||||
// state is synced and no weird side effects occur
|
||||
|
||||
export const initializationSequence = async () => {
|
||||
await tick()
|
||||
Notifications.info('Jankboard initialized!', {
|
||||
await tick();
|
||||
Notifications.info("Jankboard initialized!", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('jankboard-initialized', 'en'),
|
||||
})
|
||||
src: getVoicePath("jankboard-initialized"),
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (get(settingsStore).goWoke) return
|
||||
Notifications.success('LittenOS is online', {
|
||||
if (get(settingsStore).goWoke) return;
|
||||
Notifications.success("LittenOS is online", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('littenos-is-online', 'en'),
|
||||
})
|
||||
src: getVoicePath("littenos-is-online"),
|
||||
});
|
||||
setTimeout(() => {
|
||||
Notifications.warn('Breaching Monte Vista codebase', {
|
||||
Notifications.warn("Breaching Monte Vista codebase", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('breaching-monte-vista', 'en'),
|
||||
})
|
||||
src: getVoicePath("breaching-monte-vista"),
|
||||
});
|
||||
setTimeout(() => {
|
||||
Notifications.playAudio(
|
||||
getVoicePath('hello-virtual-assistant', 'en'),
|
||||
() => {
|
||||
sequenceStore.update('initializationComplete', true)
|
||||
periodicSequence()
|
||||
}
|
||||
)
|
||||
}, 3000)
|
||||
}, 3000)
|
||||
}, 3000)
|
||||
}
|
||||
Notifications.playAudio(getVoicePath("hello-virtual-assistant"), () => {
|
||||
sequenceStore.update("initializationComplete", true);
|
||||
periodicSequence();
|
||||
});
|
||||
}, 3000);
|
||||
}, 3000);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
let counter = 1
|
||||
let counter = 1;
|
||||
/**
|
||||
* Special sequence that plays invokes itself periodically, started automatically
|
||||
* at the end of the initializationSequence
|
||||
|
@ -64,7 +61,7 @@ let counter = 1
|
|||
* @return void
|
||||
*/
|
||||
const periodicSequence = async () => {
|
||||
await tick()
|
||||
await tick();
|
||||
|
||||
/**
|
||||
* Returns either true or false based on the provided probability
|
||||
|
@ -74,11 +71,11 @@ const periodicSequence = async () => {
|
|||
*/
|
||||
const chance = (probability: number) => {
|
||||
if (probability < 0 || probability > 1) {
|
||||
throw new Error('Probability must be between 0 and 1')
|
||||
throw new Error("Probability must be between 0 and 1");
|
||||
}
|
||||
|
||||
return Math.random() < probability * get(settingsStore).randomWeight
|
||||
}
|
||||
return Math.random() < probability * get(settingsStore).randomWeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls a callback function at regular intervals.
|
||||
|
@ -87,140 +84,140 @@ const periodicSequence = async () => {
|
|||
* @param callback - the function to call
|
||||
*/
|
||||
const every = (seconds: number, callback: () => void) => {
|
||||
if (counter % seconds === 0) callback()
|
||||
}
|
||||
if (counter % seconds === 0) callback();
|
||||
};
|
||||
|
||||
// add your periodic sequences here
|
||||
every(15, () => {
|
||||
if (chance(0.2)) breaching1323Sequence()
|
||||
else if (chance(0.2)) breaching254Sequence()
|
||||
})
|
||||
if (chance(0.2)) breaching1323Sequence();
|
||||
else if (chance(0.2)) breaching254Sequence();
|
||||
});
|
||||
every(25, () => {
|
||||
if (chance(0.05)) bullyingRohanSequence()
|
||||
else if (chance(0.1)) bypassCoprocessorRestrictionsSequence()
|
||||
})
|
||||
if (chance(0.05)) bullyingRohanSequence();
|
||||
else if (chance(0.1)) bypassCoprocessorRestrictionsSequence();
|
||||
});
|
||||
|
||||
// Dont touch
|
||||
counter++
|
||||
setTimeout(periodicSequence, 1000)
|
||||
}
|
||||
counter++;
|
||||
setTimeout(periodicSequence, 1000);
|
||||
};
|
||||
export const criticalFailureIminentSequence = async () => {
|
||||
await tick()
|
||||
Notifications.error('Critical robot failure imminent', {
|
||||
await tick();
|
||||
Notifications.error("Critical robot failure imminent", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('critical-robot-failure', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("critical-robot-failure"),
|
||||
});
|
||||
};
|
||||
|
||||
export const collisionDetectedSequence = async () => {
|
||||
await tick()
|
||||
Notifications.error('Collision detected', {
|
||||
await tick();
|
||||
Notifications.error("Collision detected", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('collision-detected', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("collision-detected"),
|
||||
});
|
||||
};
|
||||
|
||||
export const collisionImminentSequence = async () => {
|
||||
await tick()
|
||||
Notifications.error('Collision imminent', {
|
||||
await tick();
|
||||
Notifications.error("Collision imminent", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('collision-imminent', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("collision-imminent"),
|
||||
});
|
||||
};
|
||||
|
||||
export const cruiseControlEngagedSequence = async () => {
|
||||
if (get(settingsStore).disableAnnoyances) return
|
||||
await tick()
|
||||
Notifications.success('Cruise control engaged', {
|
||||
if (get(settingsStore).disableAnnoyances) return;
|
||||
await tick();
|
||||
Notifications.success("Cruise control engaged", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('cruise-control-engaged', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("cruise-control-engaged"),
|
||||
});
|
||||
};
|
||||
|
||||
export const retardSequence = async () => {
|
||||
if (get(settingsStore).goWoke) return
|
||||
await tick()
|
||||
Notifications.warn('Retard', {
|
||||
if (get(settingsStore).goWoke) return;
|
||||
await tick();
|
||||
Notifications.warn("Retard", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('retard', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("retard"),
|
||||
});
|
||||
};
|
||||
|
||||
const breaching254Sequence = async () => {
|
||||
if (get(settingsStore).disableAnnoyances) return
|
||||
await tick()
|
||||
Notifications.warn('Breaching 254 mainframe', {
|
||||
if (get(settingsStore).disableAnnoyances) return;
|
||||
await tick();
|
||||
Notifications.warn("Breaching 254 mainframe", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('breaching-254-mainframe', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("breaching-254-mainframe"),
|
||||
});
|
||||
};
|
||||
|
||||
const breaching1323Sequence = async () => {
|
||||
if (get(settingsStore).disableAnnoyances) return
|
||||
await tick()
|
||||
Notifications.warn('Breaching 1323 mainframe', {
|
||||
if (get(settingsStore).disableAnnoyances) return;
|
||||
await tick();
|
||||
Notifications.warn("Breaching 1323 mainframe", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('breaching-1323-mainframe', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("breaching-1323-mainframe"),
|
||||
});
|
||||
};
|
||||
|
||||
const bullyingRohanSequence = async () => {
|
||||
if (get(settingsStore).disableAnnoyances) return
|
||||
await tick()
|
||||
Notifications.info('Bullying Rohan', {
|
||||
if (get(settingsStore).disableAnnoyances) return;
|
||||
await tick();
|
||||
Notifications.info("Bullying Rohan", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('bullying-rohan', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("bullying-rohan"),
|
||||
});
|
||||
};
|
||||
|
||||
export const userErrorDetectedSequence = async () => {
|
||||
await tick()
|
||||
Notifications.error('User error detected', {
|
||||
await tick();
|
||||
Notifications.error("User error detected", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('user-error-detected', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("user-error-detected"),
|
||||
});
|
||||
};
|
||||
|
||||
// hacky way to prevent duplicate infotainment bootups
|
||||
let infotainmentStarted = false
|
||||
let infotainmentStarted = false;
|
||||
export const infotainmentBootupSequence = async () => {
|
||||
if (
|
||||
get(sequenceStore).infotainmentStartedFirstTime ||
|
||||
get(settingsStore).disableAnnoyances ||
|
||||
infotainmentStarted
|
||||
)
|
||||
return
|
||||
return;
|
||||
|
||||
infotainmentStarted = true
|
||||
await tick()
|
||||
infotainmentStarted = true;
|
||||
await tick();
|
||||
|
||||
const sequence = () => {
|
||||
Notifications.info('Infotainment system buffering', {
|
||||
Notifications.info("Infotainment system buffering", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('infotainment-system-buffering', 'en'),
|
||||
})
|
||||
src: getVoicePath("infotainment-system-buffering"),
|
||||
});
|
||||
setTimeout(() => {
|
||||
Notifications.success('Infotainment system online', {
|
||||
Notifications.success("Infotainment system online", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('infotainment-system-online', 'en'),
|
||||
src: getVoicePath("infotainment-system-online"),
|
||||
onComplete: () => {
|
||||
sequenceStore.update('infotainmentStartedFirstTime', true)
|
||||
sequenceStore.update("infotainmentStartedFirstTime", true);
|
||||
},
|
||||
})
|
||||
}, 3000)
|
||||
}
|
||||
});
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
if (!get(sequenceStore).initializationComplete) {
|
||||
const unsubscribe = sequenceStore.subscribe(data => {
|
||||
const unsubscribe = sequenceStore.subscribe((data) => {
|
||||
if (data.initializationComplete) {
|
||||
sequence()
|
||||
unsubscribe()
|
||||
sequence();
|
||||
unsubscribe();
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
sequence()
|
||||
sequence();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for the infotainment system to boot up before executing the given sequence.
|
||||
|
@ -231,77 +228,77 @@ export const infotainmentBootupSequence = async () => {
|
|||
*/
|
||||
const waitForInfotainmentBootup = (sequence: () => void) => {
|
||||
if (!get(sequenceStore).infotainmentStartedFirstTime) {
|
||||
const unsubscribe = sequenceStore.subscribe(data => {
|
||||
const unsubscribe = sequenceStore.subscribe((data) => {
|
||||
if (data.infotainmentStartedFirstTime) {
|
||||
sequence()
|
||||
unsubscribe()
|
||||
sequence();
|
||||
unsubscribe();
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
sequence()
|
||||
sequence();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const musicPlayerBootupSequence = async () => {
|
||||
if (
|
||||
get(sequenceStore).musicStartedFirstTime ||
|
||||
get(settingsStore).disableAnnoyances
|
||||
)
|
||||
return
|
||||
return;
|
||||
|
||||
await tick()
|
||||
await tick();
|
||||
|
||||
sequenceStore.update('musicStartedFirstTime', true)
|
||||
sequenceStore.update("musicStartedFirstTime", true);
|
||||
|
||||
waitForInfotainmentBootup(() => {
|
||||
Notifications.info('Downloading copyrighted music...', {
|
||||
Notifications.info("Downloading copyrighted music...", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('downloading-copyrighted-music', 'en'),
|
||||
})
|
||||
})
|
||||
}
|
||||
src: getVoicePath("downloading-copyrighted-music"),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const gbaEmulatorBootupSequence = async () => {
|
||||
if (
|
||||
get(sequenceStore).gbaEmulatorStartedFirstTime ||
|
||||
get(settingsStore).disableAnnoyances
|
||||
)
|
||||
return
|
||||
return;
|
||||
|
||||
await tick()
|
||||
sequenceStore.update('gbaEmulatorStartedFirstTime', true)
|
||||
await tick();
|
||||
sequenceStore.update("gbaEmulatorStartedFirstTime", true);
|
||||
|
||||
waitForInfotainmentBootup(() => {
|
||||
Notifications.info('Loading pirated Nintendo ROMs', {
|
||||
Notifications.info("Loading pirated Nintendo ROMs", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('loading-pirated-nintendo', 'en'),
|
||||
})
|
||||
})
|
||||
}
|
||||
src: getVoicePath("loading-pirated-nintendo"),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const doomBootupSequence = async () => {
|
||||
if (
|
||||
get(sequenceStore).doomStartedFirstTime ||
|
||||
get(settingsStore).disableAnnoyances
|
||||
)
|
||||
return
|
||||
return;
|
||||
|
||||
await tick()
|
||||
sequenceStore.update('doomStartedFirstTime', true)
|
||||
await tick();
|
||||
sequenceStore.update("doomStartedFirstTime", true);
|
||||
|
||||
waitForInfotainmentBootup(() => {
|
||||
Notifications.success('Doom Engaged', {
|
||||
Notifications.success("Doom Engaged", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('doom-engaged', 'en'),
|
||||
})
|
||||
})
|
||||
}
|
||||
src: getVoicePath("doom-engaged"),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const bypassCoprocessorRestrictionsSequence = async () => {
|
||||
if (get(settingsStore).disableAnnoyances) return
|
||||
await tick()
|
||||
Notifications.warn('Bypassing coprocessor restrictions', {
|
||||
if (get(settingsStore).disableAnnoyances) return;
|
||||
await tick();
|
||||
Notifications.warn("Bypassing coprocessor restrictions", {
|
||||
withAudio: true,
|
||||
src: getVoicePath('bypassing-coprocessor-restrictions', 'en'),
|
||||
})
|
||||
}
|
||||
src: getVoicePath("bypassing-coprocessor-restrictions"),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
/* stores global app wide settings */
|
||||
|
||||
import { writable } from 'svelte/store'
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
type SupportedLanguage = "en" | "rus";
|
||||
|
||||
export interface SettingsStoreData {
|
||||
disableAnnoyances: boolean
|
||||
goWoke: boolean
|
||||
fastStartup: boolean
|
||||
randomWeight: number
|
||||
disableAnnoyances: boolean;
|
||||
goWoke: boolean;
|
||||
fastStartup: boolean;
|
||||
randomWeight: number;
|
||||
voiceLang: SupportedLanguage;
|
||||
}
|
||||
|
||||
export const defaults: SettingsStoreData = {
|
||||
|
@ -14,25 +17,26 @@ export const defaults: SettingsStoreData = {
|
|||
goWoke: false, // go woke (for showing parents or other officials where DEI has taken over), disables "offensive" sequences
|
||||
fastStartup: false, // skip the loading splash screen (for development purposes. Setting this from within the app has no effect.)
|
||||
randomWeight: 1, // the weight of random events (multiplied by the original probability)
|
||||
}
|
||||
voiceLang: "en",
|
||||
};
|
||||
|
||||
const createSequenceStore = () => {
|
||||
const { subscribe, set, update } = writable<SettingsStoreData>(defaults)
|
||||
const { subscribe, set, update } = writable<SettingsStoreData>(defaults);
|
||||
return {
|
||||
subscribe,
|
||||
update: (
|
||||
data: keyof SettingsStoreData,
|
||||
newValue: SettingsStoreData[typeof data]
|
||||
) => {
|
||||
update(store => {
|
||||
update((store) => {
|
||||
// @ts-expect-error
|
||||
store[data] = newValue
|
||||
return store
|
||||
})
|
||||
store[data] = newValue;
|
||||
return store;
|
||||
});
|
||||
},
|
||||
reset: () => set(defaults),
|
||||
set: (data: SettingsStoreData) => set(data),
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const settingsStore = createSequenceStore()
|
||||
export const settingsStore = createSequenceStore();
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { get } from "svelte/store";
|
||||
import { settingsStore } from "../stores/settingsStore";
|
||||
|
||||
/**
|
||||
* Retrieves the voice audio path for the given audio file.
|
||||
*
|
||||
|
@ -5,7 +8,18 @@
|
|||
* @param lang - the language of the audio
|
||||
* @return the path of the audio file
|
||||
*/
|
||||
type SupportedLanguage = 'en' | 'rus'
|
||||
export default function getVoicePath(audio: string, lang: SupportedLanguage) {
|
||||
return `/static/voices/${lang}/${audio}.wav`
|
||||
type SupportedLanguage = "en" | "rus";
|
||||
|
||||
let currentLang = "en";
|
||||
|
||||
settingsStore.subscribe((data) => {
|
||||
currentLang = data.voiceLang;
|
||||
});
|
||||
export default function getVoicePath(audio: string, lang?: SupportedLanguage) {
|
||||
console.log(get(settingsStore).voiceLang);
|
||||
if (!lang) {
|
||||
return `/static/voices/${get(settingsStore).voiceLang}/${audio}.wav`;
|
||||
}
|
||||
|
||||
return `/static/voices/${lang}/${audio}.wav`;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue