Added Conversations
This commit is contained in:
parent
153ec819b9
commit
6a4d42f8b7
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -5,6 +5,12 @@
|
|||
"spriteIndex": 0,
|
||||
"name": "Cy-Bro",
|
||||
"bio": "AI Bro sent from the future to show the superiority of the future.",
|
||||
"voice": "Legion",
|
||||
"convo": [
|
||||
"nadia",
|
||||
"fireball",
|
||||
"nicole"
|
||||
],
|
||||
"speech": {
|
||||
"intro": [
|
||||
"cybro-intro-01",
|
||||
|
|
@ -34,6 +40,12 @@
|
|||
"spriteIndex": 1,
|
||||
"name": "Brad",
|
||||
"bio": "Came for the honey, stayed for the salmon!",
|
||||
"voice": "Jewel Weaver - Bear",
|
||||
"convo": [
|
||||
"croc",
|
||||
"maurice",
|
||||
"smasher"
|
||||
],
|
||||
"speech": {
|
||||
"intro": [
|
||||
"brad-intro-01",
|
||||
|
|
@ -63,6 +75,12 @@
|
|||
"spriteIndex": 2,
|
||||
"name": "Victor",
|
||||
"bio": "Patient and calculating. Moves are made using ancient technology.",
|
||||
"voice": "Starfield - Vasco",
|
||||
"convo": [
|
||||
"klaxon",
|
||||
"beth",
|
||||
"juliet"
|
||||
],
|
||||
"speech": {
|
||||
"intro": [
|
||||
"victor-intro-01",
|
||||
|
|
@ -92,6 +110,12 @@
|
|||
"spriteIndex": 3,
|
||||
"name": "Croc",
|
||||
"bio": "Wassssuppp everybody! I'm here for the party!!",
|
||||
"voice": "Batman - Killer Croc",
|
||||
"convo": [
|
||||
"jeff",
|
||||
"aiko",
|
||||
"terry"
|
||||
],
|
||||
"speech": {
|
||||
"intro": [
|
||||
"croc-intro-01",
|
||||
|
|
@ -143,6 +167,12 @@
|
|||
"spriteIndex": 5,
|
||||
"name": "Ethel",
|
||||
"bio": "Thank you for visiting with me.",
|
||||
"voice": "Betty White",
|
||||
"convo": [
|
||||
"jerry",
|
||||
"zanthor",
|
||||
"brad"
|
||||
],
|
||||
"speech": {
|
||||
"intro": [
|
||||
"ethel-intro-01",
|
||||
|
|
@ -172,6 +202,11 @@
|
|||
"spriteIndex": 6,
|
||||
"name": "Jeff",
|
||||
"bio": "I play slow, I win fast!",
|
||||
"voice": "SlowAltered",
|
||||
"convo": [
|
||||
"kona",
|
||||
"michael"
|
||||
],
|
||||
"speech": {
|
||||
"intro": [
|
||||
"jeff-intro-01",
|
||||
|
|
@ -230,6 +265,7 @@
|
|||
"spriteIndex": 8,
|
||||
"name": "Fireball",
|
||||
"bio": "Come on let's play! I promise not to use my x-ray eyes.",
|
||||
"voice": "Zero - Borderlands 3",
|
||||
"speech": {
|
||||
"intro": [
|
||||
"fireball-intro-01",
|
||||
|
|
@ -281,6 +317,7 @@
|
|||
"spriteIndex": 10,
|
||||
"name": "Jerry",
|
||||
"bio": "Well now yall know I want to play some them games!",
|
||||
"voice": "Life is Strange - Jed Lucan",
|
||||
"speech": {
|
||||
"intro": [
|
||||
"jerry-intro-01",
|
||||
|
|
@ -390,6 +427,10 @@
|
|||
"spriteIndex": 15,
|
||||
"name": "Michael",
|
||||
"bio": "Hey mon! Yous be knowin' I been wantin' ta play on da Fertig Games for a while now mon!",
|
||||
"voice": "Djed Spence",
|
||||
"convo": [
|
||||
"michael"
|
||||
],
|
||||
"speech": {
|
||||
"intro": [
|
||||
"michael-intro-01",
|
||||
|
|
@ -419,6 +460,7 @@
|
|||
"spriteIndex": 13,
|
||||
"name": "Juliet",
|
||||
"bio": "Let's take a break on a warm summer day to spend time playing games!",
|
||||
"voice": "Street Fighter 6 - Crimson Viper",
|
||||
"speech": {
|
||||
"intro": [
|
||||
"juliet-intro-01",
|
||||
|
|
@ -535,6 +577,7 @@
|
|||
"spriteIndex": 19,
|
||||
"name": "Blackwind",
|
||||
"bio": "Aaaaaaargh! Me be playin' these games matey!",
|
||||
"voice": "pirate",
|
||||
"speech": {
|
||||
"intro": [
|
||||
"blackwind-intro-01",
|
||||
|
|
@ -564,6 +607,7 @@
|
|||
"spriteIndex": 20,
|
||||
"name": "Nadia",
|
||||
"bio": "She's here from the future to learn our games and study our culture.",
|
||||
"voice": "Invincible VS - Atom Eve",
|
||||
"speech": {
|
||||
"intro": [
|
||||
"nadia-intro-01",
|
||||
|
|
@ -651,6 +695,7 @@
|
|||
"spriteIndex": 23,
|
||||
"name": "Nicole",
|
||||
"bio": "I've disabled the firewall protections on this website. Now to hack the core!",
|
||||
"voice": "Life is Strange - Rachael Amber",
|
||||
"speech": {
|
||||
"intro": [
|
||||
"nicole-intro-01",
|
||||
|
|
@ -738,6 +783,12 @@
|
|||
"spriteIndex": 26,
|
||||
"name": "Schooner",
|
||||
"bio": "Ahoy you land lovers!",
|
||||
"voice": "Defiant Calder",
|
||||
"convo": [
|
||||
"blackwind",
|
||||
"ethel",
|
||||
"bernie"
|
||||
],
|
||||
"speech": {
|
||||
"intro": [
|
||||
"schooner-intro-01",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
import { enqueue as enqueueSpeech } from './SpeechQueue.js';
|
||||
|
||||
// ── Opponent conversation coordinator ─────────────────────────────────────────
|
||||
// Global, per-scene system: when two opponents that list each other (via the
|
||||
// `convo` array in opponents.json) are both present in the same game, they can
|
||||
// have a recorded conversation. Each eligible directed pair (owner→match) gets
|
||||
// its own independent random 1–5 min one-shot timer and plays once per match.
|
||||
// Conversations always queue (bypassing the speech-queue cap) and play
|
||||
// sequentially. While playing, both portraits show an orange ring + speech
|
||||
// visualizer for the clip's duration.
|
||||
//
|
||||
// This rides on the shared Portrait factory, so it applies to every game with
|
||||
// no per-game wiring.
|
||||
|
||||
const DEBUG_CONVOS = false;
|
||||
|
||||
const MIN_MS = 60_000;
|
||||
const MAX_MS = 300_000;
|
||||
const ORANGE_RGB = [255, 122, 0];
|
||||
|
||||
function rand(min, max) {
|
||||
return min + Math.random() * (max - min);
|
||||
}
|
||||
|
||||
class ConvoManager {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.entries = [];
|
||||
this.armed = false;
|
||||
this._armTimer = null;
|
||||
}
|
||||
|
||||
register(entry) {
|
||||
this.entries.push(entry);
|
||||
// Portraits register synchronously during scene `create`; debounce arming
|
||||
// so we only schedule timers once every portrait has registered.
|
||||
if (!this.armed) {
|
||||
this._armTimer?.remove();
|
||||
this._armTimer = this.scene.time.delayedCall(0, () => this._armAll());
|
||||
}
|
||||
return () => this._unregister(entry);
|
||||
}
|
||||
|
||||
_unregister(entry) {
|
||||
const i = this.entries.indexOf(entry);
|
||||
if (i >= 0) this.entries.splice(i, 1);
|
||||
}
|
||||
|
||||
_armAll() {
|
||||
if (this.armed) return;
|
||||
this.armed = true;
|
||||
this._armTimer = null;
|
||||
|
||||
const seen = new Set();
|
||||
const debugMatches = [];
|
||||
for (const a of this.entries) {
|
||||
const convo = a.opponent?.convo;
|
||||
if (!Array.isArray(convo)) continue;
|
||||
for (const matchId of convo) {
|
||||
const b = this.entries.find(e => e !== a && e.opponent?.id === matchId);
|
||||
if (!b) continue;
|
||||
const key = `${a.opponent.id}->${b.opponent.id}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
const delay = rand(MIN_MS, MAX_MS);
|
||||
if (DEBUG_CONVOS) debugMatches.push({ pair: key, file: `convo/${a.opponent.id}-${b.opponent.id}.mp3`, delayMs: delay, playsAt: `${(delay / 1000).toFixed(1)}s` });
|
||||
this.scene.time.delayedCall(delay, () => this._playConvo(a, b));
|
||||
}
|
||||
}
|
||||
if (DEBUG_CONVOS) {
|
||||
if (debugMatches.length) {
|
||||
console.log('[ConvoManager] Scheduled conversations:', debugMatches);
|
||||
} else {
|
||||
console.log('[ConvoManager] No conversation matches found among present opponents:', this.entries.map(e => e.opponent?.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_playConvo(a, b) {
|
||||
if (!a.isAlive() || !b.isAlive()) return;
|
||||
enqueueSpeech(`convo/${a.opponent.id}-${b.opponent.id}`, {
|
||||
onStart: () => {
|
||||
a.startSpeaking(ORANGE_RGB); b.startSpeaking(ORANGE_RGB);
|
||||
a.showRing(); b.showRing();
|
||||
},
|
||||
onEnd: () => {
|
||||
a.stopSpeaking(); b.stopSpeaking();
|
||||
a.hideRing(); b.hideRing();
|
||||
},
|
||||
}, { force: true });
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._armTimer?.remove();
|
||||
this._armTimer = null;
|
||||
this.entries = [];
|
||||
}
|
||||
}
|
||||
|
||||
export function registerConvoPortrait(scene, entry) {
|
||||
let mgr = scene.__convoManager;
|
||||
if (!mgr) {
|
||||
mgr = new ConvoManager(scene);
|
||||
scene.__convoManager = mgr;
|
||||
scene.events.once('shutdown', () => {
|
||||
mgr.destroy();
|
||||
if (scene.__convoManager === mgr) scene.__convoManager = null;
|
||||
});
|
||||
}
|
||||
return mgr.register(entry);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import { COLORS } from '../config.js';
|
|||
import { auth } from '../services/auth.js';
|
||||
import { api } from '../services/api.js';
|
||||
import { enqueue as enqueueSpeech, resetQueue as resetSpeechQueue } from './SpeechQueue.js';
|
||||
import { registerConvoPortrait } from './ConvoManager.js';
|
||||
|
||||
// ── Shared portrait utility ───────────────────────────────────────────────────
|
||||
// Both factories add elements directly to the scene at world coordinates so
|
||||
|
|
@ -126,8 +127,9 @@ export function createOpponentPortrait(scene, opponent, worldX, worldY, radius,
|
|||
rafId = requestAnimationFrame(_vizFrame);
|
||||
}
|
||||
|
||||
function startVisualizer(emotion) {
|
||||
if (emotion === 'happy') vizColor = [0, 210, 60];
|
||||
function startVisualizer(emotion, color) {
|
||||
if (color) vizColor = color;
|
||||
else if (emotion === 'happy') vizColor = [0, 210, 60];
|
||||
else if (emotion === 'upset') vizColor = [210, 60, 60];
|
||||
else vizColor = [0, 200, 255];
|
||||
vizActive = true;
|
||||
|
|
@ -147,6 +149,18 @@ export function createOpponentPortrait(scene, opponent, worldX, worldY, radius,
|
|||
retargetTimer = null;
|
||||
}
|
||||
|
||||
// Orange conversation ring — drawn around the portrait while a convo plays
|
||||
let ringG = null;
|
||||
function showRing() {
|
||||
if (ringG) return;
|
||||
ringG = scene.add.graphics().setDepth(depth + 4);
|
||||
ringG.lineStyle(6, 0xff7a00, 1);
|
||||
ringG.strokeCircle(worldX, worldY, radius + 5);
|
||||
}
|
||||
function hideRing() {
|
||||
if (ringG) { ringG.destroy(); ringG = null; }
|
||||
}
|
||||
|
||||
let emotionPlaying = false;
|
||||
|
||||
function playEmotion(emotion) {
|
||||
|
|
@ -208,9 +222,26 @@ export function createOpponentPortrait(scene, opponent, worldX, worldY, radius,
|
|||
}
|
||||
|
||||
let destroyed = false;
|
||||
|
||||
// Register with the per-scene conversation coordinator so any two opponents
|
||||
// that list each other in their `convo` arrays can talk during the match.
|
||||
let unregisterConvo = null;
|
||||
if (opponent?.id) {
|
||||
unregisterConvo = registerConvoPortrait(scene, {
|
||||
opponent,
|
||||
startSpeaking: (rgb) => startVisualizer('convo', rgb),
|
||||
stopSpeaking: stopVisualizer,
|
||||
showRing,
|
||||
hideRing,
|
||||
isAlive: () => !destroyed,
|
||||
});
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
if (destroyed) return;
|
||||
destroyed = true;
|
||||
unregisterConvo?.();
|
||||
hideRing();
|
||||
scene.events.off('shutdown', destroy);
|
||||
videoEl.pause();
|
||||
videoEl.src = '';
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ let _currentAudio = null;
|
|||
let _currentCbs = null;
|
||||
const MAX = 4;
|
||||
|
||||
export function enqueue(filename, callbacks) {
|
||||
if (_queue.length + (_playing ? 1 : 0) >= MAX) return;
|
||||
export function enqueue(filename, callbacks, opts = {}) {
|
||||
if (!opts.force && _queue.length + (_playing ? 1 : 0) >= MAX) return;
|
||||
_queue.push({ filename, onStart: callbacks?.onStart, onEnd: callbacks?.onEnd });
|
||||
_playNext();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue