Add some sound functionality

This commit is contained in:
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.

BIN
app_web/assets/cave1.wav Normal file

Binary file not shown.

BIN
app_web/assets/scroll.wav Normal file

Binary file not shown.

BIN
app_web/assets/windy1.wav Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
assets/Greek7EchoHall.wav Normal file

Binary file not shown.

BIN
assets/cave1.wav Normal file

Binary file not shown.

BIN
assets/scroll.wav Normal file

Binary file not shown.

BIN
assets/windy1.wav Normal file

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);
}
}

18
src/engine/sound.js Normal 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);
}
}

10
src/game/items/cup.js Normal file
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`);
})

12
src/game/items/torch.js Normal file
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();

13
src/game/rooms/tunnel2.js Normal file
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',