Add some sound functionality

master
Talon 2021-11-05 14:28:01 +01:00
parent dd066f0aa3
commit c5a4167846
32 changed files with 187 additions and 30 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

BIN
assets/cave1.wav 100644

Binary file not shown.

BIN
assets/scroll.wav 100644

Binary file not shown.

BIN
assets/windy1.wav 100644

Binary file not shown.

View File

@ -40,6 +40,11 @@ export default class ItemBuilder {
return this;
}
withDropCallback(callback) {
this.item.addDropCallback(callback);
return this;
}
withTickCallback(callback) {
this.item.addTickCallback(callback);
return this;

View File

@ -3,6 +3,6 @@ export default function EchoCommand(args, context) {
context.print(`Usage: echo <on/off>`);
} else {
context.setInputEcho(args[1] == "on" ? true : false);
context.print(`Command echo is now ${args[1]}`);
context.print(`Command echo is now ${args[1]}.`);
}
}

View File

@ -12,7 +12,7 @@ export default function LookCommand(args, context) {
}
}
if (!item) {
context.output.say(`I could not find a ${args[1]}`);
context.output.say(`I could not find a ${args[1]}.`);
} else {
context.output.say(item.name);
context.output.say(item.description);

View File

@ -9,14 +9,14 @@ export default function TakeCommand(args, context) {
}
}
if (!item) {
context.print(`You can't find any ${args[1]}`);
context.print(`You can't find any ${args[1]}.`);
} else {
if (!item.takeable) {
context.print(`You can't take ${item.name}`);
context.print(`You can't take ${item.name}.`);
} else {
room.removeItem(item.id);
context.player.addItem(item.id);
context.print(`You take ${item.name}`);
context.print(`You take ${item.name}.`);
item.onTake();
}
}

View File

@ -9,7 +9,16 @@ export default async function UseCommand(args, context) {
}
}
if (!item) {
context.output.say(`I could not find a ${args[1]}`);
const items = context.player.getInventory();
for (let i of items) {
if (i.name.includes(args[1])) {
item = i;
break;
}
}
}
if (!item) {
context.output.say(`I could not find a ${args[1]}.`);
} else {
await item.onUse();
}

View File

@ -24,7 +24,6 @@ export default class Game {
}
init(data) {
console.log(data);
this.rooms = data.rooms.map((room) => {
room.context = this;
return room;
@ -33,7 +32,7 @@ export default class Game {
item.context = this;
return item;
});
this.state = data.state;
this.state = data.state || State;
this.commandHandler.addCommands(data.commands);
this.player = new Player();
this.player.context = this;
@ -49,7 +48,7 @@ export default class Game {
start() {
this.interval = setInterval(() => this.advanceTick(), 1000);
}
stop() {
clearInterval(this.interval);
this.interval = null;
@ -70,16 +69,38 @@ export default class Game {
examineItems() {
const room = this.getRoom(this.player.currentRoom);
const items = room.getItems();
items.forEach((item) => this.output.say(item.name));
if (items.length < 1) return;
let itemDescription = `You see `;
items.forEach((item, index) => {
if (index < items.length - 2) {
itemDescription += `${item.name}, `;
} else if (index < items.length - 1) {
itemDescription += `${item.name} and `;
} else {
itemDescription += item.name
}
});
this.output.say(itemDescription + ".");
}
examineExits() {
const room = this.getRoom(this.player.currentRoom);
let exits = [];
let exitDescription = "You can go ";
for (let exit of room.exits.keys()) {
exitDescription += " " + exit;
const exitKeys = room.exits.keys();
for (let exit of exitKeys) {
exits.push(exit);
}
this.output.say(exitDescription);
exits.forEach((item, index) => {
if (index < exits.length - 2) {
exitDescription += `${item}, `;
} else if (index < exits.length - 1) {
exitDescription += `${item} and `;
} else {
exitDescription += item
}
});
this.output.say(exitDescription + ".");
}
getRoom(id) {

View File

@ -17,9 +17,12 @@ export default class Item {
}
async onTake() {
if (this.takeCallback) return this.takeCallback();
if (this.takeCallback) return this.takeCallback(this.context);
}
async onDrop() {
if (this.dropCallback) return this.dropCallback(this.context);
}
async onTick() {
if (this.tickCallback) return this.tickCallback(this.context);
}

View File

@ -1,13 +1,16 @@
import { TTS } from '../framework/tts';
import { AriaOutput } from '../framework/tts/outputs/aria';
import Sound from './sound';
export default class Output {
constructor() {
this.tts = new TTS(new AriaOutput());
this.history = document.getElementById("output-area");
this.sound = new Sound();
}
say(string) {
this.sound.play(`assets/scroll.wav`);
const node = document.createElement("p");
string.split("\n").forEach((line) => {
node.appendChild(document.createTextNode(line));
@ -16,4 +19,8 @@ export default class Output {
this.history.appendChild(node);
// this.tts.speak(string);
}
play(file) {
this.sound.play(file);
}
}

View File

@ -0,0 +1,18 @@
import Resonator from '../framework/resonator';
export default class Sound {
constructor() {
this.res = new Resonator();
this.res.setEnvironmentImpulse(`assets/Greek7EchoHall.wav`);
this.ambience = null;
this.music = null;
this.previousAmbience = null;
this.previousMusic = null;
}
play(file) {
const sound = this.res.loadImmediate(file);
sound.play();
}
}

View File

@ -4,6 +4,9 @@ class State {
}
get(key) {
if (!this.states.has(key)) {
return null;
}
return this.states.get(key);
}

View File

@ -32,4 +32,6 @@ export default class AudioSource implements BaseSource {
stop(): void;
destroy(): void;
loop(value: boolean): void;
fadeOut(time: number): void;
fadeIn(time: number): void;
}

View File

@ -129,4 +129,18 @@ export default class AudioSource {
this.node.loop = value;
}
}
fadeOut(time) {
if (!this.node) {
return;
}
this.gain.gain.exponentialRampToValueAtTime(0.0001, this.context.getContext().currentTime + time);
setTimeout(() => this.stop(), time * 1000);
}
fadeIn(time) {
if (!this.node) {
this.play();
}
this.gain.gain.setValueAtTime(0, this.context.getContext().currentTime);
this.gain.gain.exponentialRampToValueAtTime(this.volume, this.context.getContext().currentTime + time);
}
}

View File

@ -6,5 +6,7 @@ export interface BaseSource {
setVolume(value: number): void;
getVolume(): number;
loop(value: boolean): void;
fadeOut(time: number): void;
fadeIn(time: number): void;
destroy(): void;
}

View File

@ -14,6 +14,7 @@ export declare class StreamingSource implements BaseSource {
private node;
private canPlay;
private sceneNode;
private gain;
private position;
constructor(graph: AudioGraph, scene: ResonatorScene, context: ResonatorAudioContext, element: HTMLAudioElement, type?: SourceType);
private init;
@ -27,4 +28,6 @@ export declare class StreamingSource implements BaseSource {
setPosition(x: number, y: number, z: number): void;
destroy(): void;
loop(value: boolean): void;
fadeIn(time: number): void;
fadeOut(time: number): void;
}

View File

@ -15,6 +15,7 @@ export class StreamingSource {
}
init() {
this.node = this.context.createMediaElementSource(this.element);
this.gain = this.context.createGain();
this.createConnections();
this.element.addEventListener('canplay', (event) => {
this.canPlay = true;
@ -50,7 +51,8 @@ export class StreamingSource {
if (!this.sceneNode) {
this.sceneNode = this.scene.createSource();
}
this.node.connect(this.sceneNode);
this.node.connect(this.gain);
this.gain.connect(this.sceneNode);
break;
default:
this.graph.connectToMaster(this.node);
@ -78,4 +80,18 @@ export class StreamingSource {
loop(value) {
this.element.loop = true;
}
fadeIn(time) {
if (!this.node) {
this.play();
}
this.gain.gain.setValueAtTime(0, this.context.getContext().currentTime);
this.gain.gain.exponentialRampToValueAtTime(this.getVolume(), this.context.getContext().currentTime + time);
}
fadeOut(time) {
if (!this.node) {
return;
}
this.gain.gain.exponentialRampToValueAtTime(0.0001, this.context.getContext().currentTime + time);
setTimeout(() => this.stop(), time * 1000);
}
}

View File

@ -0,0 +1,10 @@
import ItemBuilder from "../../engine/builders/item";
import Item from "../../engine/item";
export default new ItemBuilder()
.withID("cup")
.withName("a cup")
.withDescription("A standard coffee cup")
.isTakeable(true)
.isUsable(false)
.create();

View File

@ -1,5 +1,9 @@
import Stone from './stone';
import Torch from './torch';
import Cup from './cup';
export default [
Stone
Stone,
Torch,
Cup
]

View File

@ -2,13 +2,16 @@ import ItemBuilder from "../../engine/builders/item";
export default new ItemBuilder()
.withID("stone")
.withName("A dull stone")
.withName("a dull stone")
.withDescription("There is nothing remarkable about this rough, bland stone.")
.isTakeable(true)
.isUsable(true)
.withTakeCallback(async function(context) {
context.print(`The ${this.id} feels heavy in your hands.`);
})
.withDropCallback(async function(context) {
context.print(`It bounces back and forth a little.`)
})
.withUseCallback(async function(context) {
context.print(`You can't really figure out what to do with ${this.name} yet`);
})

View File

@ -0,0 +1,12 @@
import ItemBuilder from "../../engine/builders/item";
export default new ItemBuilder()
.withID("torch")
.withName("a torch")
.withDescription("A standard torch that provides light.")
.isUsable(true)
.isTakeable(true)
.withUseCallback(async function(context) {
context.print(`You try to light the torch but fail.`)
})
.create();

View File

@ -1,7 +1,9 @@
import Start from './start';
import Tunnel1 from './tunnel1';
import tunnel2 from './tunnel2';
export default [
Start,
Tunnel1
Tunnel1,
tunnel2
];

View File

@ -3,15 +3,17 @@ import RoomBuilder from '../../engine/builders/room';
export default new RoomBuilder()
.withID("start")
.withTitle("The starting room")
.withFirstDescription(`
You set foot in your very first ever room.
.withFirstDescription(
`You set foot in your very first ever room.
You're not quite sure what you were supposed to expect, but it definitely wasn't this.
I mean who would expect a boring old room like this one? Ugh.
Just... make it stop. Please.
`)
Just... make it stop. Please.`
)
.withDescription("The first room. Nothing special about it.")
.withExit("north", "tunnel_1")
.withExit("northwest", "tunnel_2")
.withEnterCallback(async function(context) {
if (context.state.get("start.awoken")) return;
const { output, wait } = context;
context.enableCommandInput(false);
output.say("You slowly wake up");
@ -20,6 +22,9 @@ Just... make it stop. Please.
await wait(5000);
output.say("Yet here we are.");
context.enableCommandInput(true);
context.state.set("start.awoken", true);
})
.withItem("stone")
.withItem("cup")
.withItem("torch")
.create();

View File

@ -0,0 +1,13 @@
import RoomBuilder from "../../engine/builders/room";
export default new RoomBuilder()
.withID("tunnel_2")
.withTitle("A long, winding tunnel")
.withFirstDescription(
`You step out from your hidy hole into a thin, winding tunnel. The walls and ceiling appear to get thinner and thinner, space slowly dwindling away to nothing.`
)
.withDescription(
`A tunnel that ends in a bowl shape.`
)
.withExit("southeast", "start")
.create();

View File

@ -8,16 +8,21 @@ module.exports = {
entry: './src/game/index.js',
devtool: 'inline-source-map',
performance: {
hints: false
hints: false
},
optimization: {
minimizer: [new TerserWebpackPlugin()]
},
"plugins": [
new HTMLWebpackPlugin({
title: Package.name,
template: path.join(__dirname, "src/game/index.html")
}),
new HTMLWebpackPlugin({
title: Package.name,
template: path.join(__dirname, "src/game/index.html")
}),
new CopyWebpackPlugin({
patterns: [
{ from: 'assets', to: 'assets' },
]
})
],
module: {
rules: [
@ -28,7 +33,7 @@ module.exports = {
]
},
resolve: {
extensions: [ '.ts', '.js' ]
extensions: ['.ts', '.js']
},
output: {
filename: 'game.js',