This commit is contained in:
parent
68a91aa31a
commit
34fa93ad44
18 changed files with 354 additions and 215 deletions
|
@ -3,7 +3,7 @@ import type { SceneDefinitionBase } from "../../shared/script/types"
|
||||||
import type { SessionId } from "../session"
|
import type { SessionId } from "../session"
|
||||||
|
|
||||||
export interface SceneType<DefinitionT extends SceneDefinitionBase, StateT extends SceneState> {
|
export interface SceneType<DefinitionT extends SceneDefinitionBase, StateT extends SceneState> {
|
||||||
id: DefinitionT["id"]
|
id: DefinitionT["type"]
|
||||||
createState(game: Game, definition: DefinitionT): StateT
|
createState(game: Game, definition: DefinitionT): StateT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { playerRouter } from "./player"
|
||||||
import { crewRouter } from "./crew"
|
import { crewRouter } from "./crew"
|
||||||
import { game } from "../game"
|
import { game } from "../game"
|
||||||
import { on } from "node:events"
|
import { on } from "node:events"
|
||||||
|
import type { PlayerBroadcast } from "../../shared/broadcast"
|
||||||
|
|
||||||
export const appRouter = t.router({
|
export const appRouter = t.router({
|
||||||
player: playerRouter,
|
player: playerRouter,
|
||||||
|
@ -21,7 +22,7 @@ export const appRouter = t.router({
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const broadcast of iterable) {
|
for await (const broadcast of iterable) {
|
||||||
yield broadcast
|
yield broadcast as unknown as PlayerBroadcast
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
|
@ -157,7 +157,7 @@
|
||||||
onStarted: () => {
|
onStarted: () => {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
},
|
},
|
||||||
onData: game.handleGameEvent,
|
onData: game.handlePlayerBroadcast,
|
||||||
onError: error => {
|
onError: error => {
|
||||||
console.error("🔴", error)
|
console.error("🔴", error)
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,99 +1,16 @@
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { computed, reactive, ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import { script } from "../shared/script"
|
import { script } from "../shared/script"
|
||||||
import type { SceneObjectDefinition, Interaction, InteractionQueueItem } from "../shared/script/types"
|
|
||||||
import { trpcClient } from "./trpc"
|
|
||||||
import type { PlayerBroadcast } from "../shared/broadcast"
|
import type { PlayerBroadcast } from "../shared/broadcast"
|
||||||
import { getInteractionQueueItemId } from "../shared/util"
|
|
||||||
|
|
||||||
export const useGame = defineStore("gameState", () => {
|
export const useGame = defineStore("gameState", () => {
|
||||||
const currentRoom = computed(() => script.roomsById.get(currentRoomId.value)!)
|
const currentSceneId = ref(script.scenesById.values().next().value!.id)
|
||||||
const currentRoomId = ref(script.roomsById.values().next().value!.id)
|
const currentScene = computed(() => script.scenesById.get(currentSceneId.value)!)
|
||||||
|
|
||||||
const currentInteraction = ref<null | Interaction>(null)
|
|
||||||
const currentInteractionId = computed(() =>
|
|
||||||
currentInteraction.value === null ? null : getInteractionQueueItemId(currentInteraction.value))
|
|
||||||
|
|
||||||
const interactionQueue = reactive(new Map<string, InteractionQueueItem>())
|
|
||||||
const sortedInteractionQueue = computed(() =>
|
|
||||||
[...interactionQueue.values()].sort((a, b) => b.votes - a.votes))
|
|
||||||
|
|
||||||
const visibleObjectIds = reactive(new Set<string>())
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentRoomId,
|
currentScene,
|
||||||
currentRoom,
|
handlePlayerBroadcast(broadcast: PlayerBroadcast) {
|
||||||
interactionQueue,
|
console.log(broadcast)
|
||||||
visibleObjectIds,
|
|
||||||
sortedInteractionQueue,
|
|
||||||
allObjectsById: computed(() => {
|
|
||||||
const map = new Map<string, SceneObjectDefinition>()
|
|
||||||
currentRoom.value.initialObjects.forEach(o => map.set(o.id, o))
|
|
||||||
currentRoom.value.hiddenObjects.forEach(o => map.set(o.id, o))
|
|
||||||
return map
|
|
||||||
}),
|
|
||||||
visibleObjectsById: computed(() => {
|
|
||||||
const map = new Map<string, SceneObjectDefinition>()
|
|
||||||
currentRoom.value.initialObjects.values().filter(o => visibleObjectIds.has(o.id)).forEach(o => map.set(o.id, o))
|
|
||||||
currentRoom.value.hiddenObjects.values().filter(o => visibleObjectIds.has(o.id)).forEach(o => map.set(o.id, o))
|
|
||||||
return map
|
|
||||||
}),
|
|
||||||
currentInteraction,
|
|
||||||
currentInteractionId,
|
|
||||||
voteForInteraction(interaction: Interaction) {
|
|
||||||
if (currentInteractionId.value === getInteractionQueueItemId(interaction)) return
|
|
||||||
if (currentInteractionId.value !== null) trpcClient.player.removeInteractionVote.mutate({ queueItemId: currentInteractionId.value })
|
|
||||||
currentInteraction.value = interaction
|
|
||||||
trpcClient.player.voteForInteraction.mutate({ interaction })
|
|
||||||
},
|
|
||||||
revokeCurrentInteractionVote() {
|
|
||||||
if (currentInteractionId.value === null) return
|
|
||||||
trpcClient.player.removeInteractionVote.mutate({ queueItemId: currentInteractionId.value })
|
|
||||||
currentInteraction.value = null
|
|
||||||
},
|
|
||||||
switchRoom(roomId: string) {
|
|
||||||
trpcClient.director.switchRoom.mutate({ roomId })
|
|
||||||
},
|
|
||||||
activateInteractionQueueItem(id: string) {
|
|
||||||
trpcClient.director.activateInteractionQueueItem.mutate({ id })
|
|
||||||
},
|
|
||||||
removeInteractionQueueItem(id: string) {
|
|
||||||
trpcClient.director.removeInteractionQueueItem.mutate({ id })
|
|
||||||
},
|
|
||||||
setObjectVisibility(id: string, isVisible: boolean) {
|
|
||||||
trpcClient.director.setObjectVisibility.mutate({ id, isVisible })
|
|
||||||
},
|
|
||||||
handleGameEvent(event: PlayerBroadcast) {
|
|
||||||
console.log(event)
|
|
||||||
switch (event.type) {
|
|
||||||
case "room-changed":
|
|
||||||
currentInteraction.value = null
|
|
||||||
currentRoomId.value = event.roomId
|
|
||||||
interactionQueue.clear()
|
|
||||||
|
|
||||||
visibleObjectIds.clear()
|
|
||||||
currentRoom.value.initialObjects.forEach(o => visibleObjectIds.add(o.id))
|
|
||||||
break
|
|
||||||
|
|
||||||
case "interaction-queued":
|
|
||||||
interactionQueue.set(event.item.id, event.item)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "interaction-votes-changed":
|
|
||||||
if (event.votes <= 0) {
|
|
||||||
interactionQueue.delete(event.id)
|
|
||||||
if (currentInteractionId.value === event.id) currentInteraction.value = null
|
|
||||||
} else {
|
|
||||||
interactionQueue.get(event.id)!.votes = event.votes
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
case "object-visibility-changed":
|
|
||||||
if (event.isVisible) visibleObjectIds.add(event.id)
|
|
||||||
else visibleObjectIds.delete(event.id)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
12
frontend/scene-types/base.ts
Normal file
12
frontend/scene-types/base.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import type { SceneDefinitionBase } from "../../shared/script/types"
|
||||||
|
import type { Component } from "vue"
|
||||||
|
|
||||||
|
export interface SceneType<DefinitionT extends SceneDefinitionBase, ControllerT extends SceneController> {
|
||||||
|
id: DefinitionT["type"]
|
||||||
|
playerView: Component<{ controller: ControllerT }>
|
||||||
|
createController(definition: DefinitionT): ControllerT
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SceneController<EventT = any> {
|
||||||
|
handleEvent(event: EventT): void
|
||||||
|
}
|
5
frontend/scene-types/index.ts
Normal file
5
frontend/scene-types/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { TextSceneType } from "./text/text"
|
||||||
|
|
||||||
|
export const sceneTypesById = {
|
||||||
|
"text": TextSceneType,
|
||||||
|
} as const
|
15
frontend/scene-types/text/PlayerView.vue
Normal file
15
frontend/scene-types/text/PlayerView.vue
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { TextSceneController } from "./text"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
controller: TextSceneController
|
||||||
|
}>()
|
||||||
|
</script>
|
20
frontend/scene-types/text/text.ts
Normal file
20
frontend/scene-types/text/text.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import type { SceneController, SceneType } from "../base"
|
||||||
|
import type { TextSceneDefinition } from "../../../shared/script/types"
|
||||||
|
import type { TextSceneEvent } from "../../../shared/scene-types/text"
|
||||||
|
import PlayerView from "./PlayerView.vue"
|
||||||
|
|
||||||
|
export const TextSceneType: SceneType<TextSceneDefinition, TextSceneController> = {
|
||||||
|
id: "text",
|
||||||
|
playerView: PlayerView,
|
||||||
|
createController(definition: TextSceneDefinition): TextSceneController {
|
||||||
|
return {
|
||||||
|
handleEvent(event: TextSceneEvent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextSceneController extends SceneController {
|
||||||
|
|
||||||
|
}
|
|
@ -1,12 +1,18 @@
|
||||||
import type { Script } from "./types"
|
import type { Script } from "./types"
|
||||||
import { roomHauseingang } from "./rooms/hauseingang"
|
import { scenePreStart } from "./scenes/pre-start"
|
||||||
import { roomKueche } from "./rooms/küche"
|
import { sceneTutorialKettensaege } from "./scenes/tutorial-kettensaege"
|
||||||
|
import { sceneTutorialMuesli } from "./scenes/tutorial-muesli"
|
||||||
|
import { sceneTutorialVorhang } from "./scenes/tutorial-vorhang"
|
||||||
|
import { sceneChoiceTest } from "./scenes/choice-test"
|
||||||
|
|
||||||
const script: Script = {
|
const script: Script = {
|
||||||
scenesById: new Map()
|
scenesById: new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
script.scenesById.set(roomHauseingang.id, roomHauseingang)
|
script.scenesById.set(scenePreStart.id, scenePreStart)
|
||||||
script.scenesById.set(roomKueche.id, roomKueche)
|
script.scenesById.set(sceneTutorialKettensaege.id, sceneTutorialKettensaege)
|
||||||
|
script.scenesById.set(sceneTutorialMuesli.id, sceneTutorialMuesli)
|
||||||
|
script.scenesById.set(sceneTutorialVorhang.id, sceneTutorialVorhang)
|
||||||
|
script.scenesById.set(sceneChoiceTest.id, sceneChoiceTest)
|
||||||
|
|
||||||
export { script }
|
export { script }
|
|
@ -1,41 +0,0 @@
|
||||||
import type { SceneDefinition } from "../types"
|
|
||||||
import { cSet } from "../../util"
|
|
||||||
|
|
||||||
export const roomHauseingang: SceneDefinition = {
|
|
||||||
id: "hauseingang",
|
|
||||||
label: "Hauseingang",
|
|
||||||
initialObjects: cSet(
|
|
||||||
{
|
|
||||||
id: "schlüssel",
|
|
||||||
label: "Schlüssel",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "haustür",
|
|
||||||
label: "Haustür"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
hiddenObjects: cSet(
|
|
||||||
{
|
|
||||||
id: "offene-haustür",
|
|
||||||
label: "Offene Haustür"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
combinations: cSet(
|
|
||||||
{
|
|
||||||
id: "open-door",
|
|
||||||
inputs: cSet(
|
|
||||||
{
|
|
||||||
objectId: "schlüssel",
|
|
||||||
isConsumed: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
objectId: "haustür",
|
|
||||||
isConsumed: true
|
|
||||||
}
|
|
||||||
),
|
|
||||||
outputIds: cSet(
|
|
||||||
"offene-haustür"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
import type { SceneDefinition } from "../types"
|
|
||||||
import { cSet } from "../../util"
|
|
||||||
|
|
||||||
export const roomKueche: SceneDefinition = {
|
|
||||||
id: "küche",
|
|
||||||
label: "Küche",
|
|
||||||
initialObjects: cSet(
|
|
||||||
{
|
|
||||||
id: "schüssel",
|
|
||||||
label: "Schüssel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "kühlschrank",
|
|
||||||
label: "Kühlschrank"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "kakaopulver",
|
|
||||||
label: "Kakaopulver"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
hiddenObjects: cSet(
|
|
||||||
{
|
|
||||||
id: "milch",
|
|
||||||
label: "Milch"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "kakao",
|
|
||||||
label: "Kakao"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
combinations: cSet(
|
|
||||||
{
|
|
||||||
id: "kakao",
|
|
||||||
inputs: cSet(
|
|
||||||
{
|
|
||||||
objectId: "milch",
|
|
||||||
isConsumed: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
objectId: "kakaopulver",
|
|
||||||
isConsumed: false
|
|
||||||
}
|
|
||||||
),
|
|
||||||
outputIds: cSet(
|
|
||||||
"kakao"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
19
shared/script/scenes/choice-test.ts
Normal file
19
shared/script/scenes/choice-test.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Temporal } from "temporal-polyfill"
|
||||||
|
import type { SceneDefinition } from "../types"
|
||||||
|
|
||||||
|
export const sceneChoiceTest: SceneDefinition = {
|
||||||
|
id: "choice-test",
|
||||||
|
type: "choice",
|
||||||
|
label: "Auswahl-Test",
|
||||||
|
plannedDuration: Temporal.Duration.from({ seconds: 30 }),
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "a",
|
||||||
|
label: "Option A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "b",
|
||||||
|
label: "Option B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
9
shared/script/scenes/pre-start.ts
Normal file
9
shared/script/scenes/pre-start.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import type { SceneDefinition } from "../types"
|
||||||
|
|
||||||
|
export const scenePreStart: SceneDefinition = {
|
||||||
|
id: "pre-start",
|
||||||
|
type: "text",
|
||||||
|
label: "Vor Beginn",
|
||||||
|
plannedDuration: null,
|
||||||
|
text: "Die Vorstellung hat noch nicht begonnen."
|
||||||
|
}
|
37
shared/script/scenes/tutorial-kettensaege.ts
Normal file
37
shared/script/scenes/tutorial-kettensaege.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { Temporal } from "temporal-polyfill"
|
||||||
|
import { type SceneDefinition } from "../types"
|
||||||
|
import { defineInteractionScene } from "../types"
|
||||||
|
|
||||||
|
export const sceneTutorialKettensaege: SceneDefinition = defineInteractionScene({
|
||||||
|
id: "tutorial-kettensaege",
|
||||||
|
type: "interaction",
|
||||||
|
label: "Tutorial: Kettensäge",
|
||||||
|
plannedDuration: Temporal.Duration.from({ minutes: 8 }),
|
||||||
|
objects: {
|
||||||
|
kettensaege: {
|
||||||
|
label: "Kettensäge",
|
||||||
|
reveal: true,
|
||||||
|
},
|
||||||
|
redner: {
|
||||||
|
label: "Redner",
|
||||||
|
reveal: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interactions: [
|
||||||
|
{
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: {
|
||||||
|
"kettensaege": { consume: false },
|
||||||
|
"redner": { consume: true },
|
||||||
|
},
|
||||||
|
outputObjectIds: [],
|
||||||
|
note: "Bei den ersten zwei Versuchen unterbricht der Redner Faba, bevor er ihn verjagen kann. Beim dritten Mal flüchtet der Redner dann."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "use",
|
||||||
|
objectId: "kettensaege",
|
||||||
|
consume: false,
|
||||||
|
revealedObjectIds: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
129
shared/script/scenes/tutorial-muesli.ts
Normal file
129
shared/script/scenes/tutorial-muesli.ts
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
import { Temporal } from "temporal-polyfill"
|
||||||
|
import { defineInteractionScene, type SceneDefinition } from "../types"
|
||||||
|
|
||||||
|
export const sceneTutorialMuesli: SceneDefinition = defineInteractionScene({
|
||||||
|
id: "tutorial-muesli",
|
||||||
|
type: "interaction",
|
||||||
|
label: "Tutorial: Müsli",
|
||||||
|
plannedDuration: Temporal.Duration.from({ minutes: 8 }),
|
||||||
|
objects: {
|
||||||
|
"escobar": {
|
||||||
|
label: "Escobar",
|
||||||
|
reveal: true,
|
||||||
|
},
|
||||||
|
"kuehlschrank": {
|
||||||
|
label: "Kühlschrank",
|
||||||
|
reveal: true,
|
||||||
|
},
|
||||||
|
"peruecke": {
|
||||||
|
label: "Perücke",
|
||||||
|
reveal: true,
|
||||||
|
},
|
||||||
|
"thunfisch": {
|
||||||
|
label: "Thunfisch",
|
||||||
|
reveal: false,
|
||||||
|
},
|
||||||
|
"haferflocken": {
|
||||||
|
label: "Haferflocken",
|
||||||
|
reveal: false,
|
||||||
|
},
|
||||||
|
"milch": {
|
||||||
|
label: "Milch",
|
||||||
|
reveal: false,
|
||||||
|
},
|
||||||
|
"h-milch": {
|
||||||
|
label: "H-Milch",
|
||||||
|
reveal: false,
|
||||||
|
},
|
||||||
|
"kaffeebohnen": {
|
||||||
|
label: "Kaffeebohnen",
|
||||||
|
reveal: false,
|
||||||
|
},
|
||||||
|
"muesli-unfertig": {
|
||||||
|
label: "Muesli (unfertig)",
|
||||||
|
reveal: true,
|
||||||
|
completion: {
|
||||||
|
replaceWith: "muesli",
|
||||||
|
steps: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"muesli": {
|
||||||
|
label: "Müsli",
|
||||||
|
reveal: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interactions: [
|
||||||
|
{
|
||||||
|
type: "use",
|
||||||
|
objectId: "kuehlschrank",
|
||||||
|
consume: false,
|
||||||
|
revealedObjectIds: ["milch", "thunfisch", "haferflocken", "kaffeebohnen"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: {
|
||||||
|
"escobar": { consume: false },
|
||||||
|
"kuehlschrank": { consume: false },
|
||||||
|
},
|
||||||
|
outputObjectIds: [],
|
||||||
|
note: "»Was sagst du Escobar, dir ist es hier zu warm?«"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: {
|
||||||
|
"kaffeebohnen": { consume: true },
|
||||||
|
"muesli-unfertig": { consume: false }
|
||||||
|
},
|
||||||
|
outputObjectIds: ["muesli-unfertig"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: {
|
||||||
|
"thunfisch": { consume: true },
|
||||||
|
"muesli-unfertig": { consume: false }
|
||||||
|
},
|
||||||
|
outputObjectIds: ["muesli-unfertig"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: {
|
||||||
|
"haferflocken": { consume: true },
|
||||||
|
"muesli-unfertig": { consume: false }
|
||||||
|
},
|
||||||
|
outputObjectIds: ["muesli-unfertig"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: {
|
||||||
|
"milch": { consume: false },
|
||||||
|
"muesli-unfertig": { consume: false }
|
||||||
|
},
|
||||||
|
outputObjectIds: [],
|
||||||
|
note: "Leider ist die Milch schon abgelaufen. → Duo: »Hätten wir nur H-Milch besorgt.«"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "use",
|
||||||
|
objectId: "milch",
|
||||||
|
consume: false,
|
||||||
|
revealedObjectIds: [],
|
||||||
|
note: "Leider ist die Milch schon abgelaufen. → Duo: »Hätten wir nur H-Milch besorgt.«"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: {
|
||||||
|
"milch": { consume: true },
|
||||||
|
"peruecke": { consume: true }
|
||||||
|
},
|
||||||
|
outputObjectIds: ["h-milch"],
|
||||||
|
note: "Ein Haar der Perücke in der Milch → H-Milch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: {
|
||||||
|
"h-milch": { consume: true },
|
||||||
|
"muesli-unfertig": { consume: false }
|
||||||
|
},
|
||||||
|
outputObjectIds: ["muesli-unfertig"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
29
shared/script/scenes/tutorial-vorhang.ts
Normal file
29
shared/script/scenes/tutorial-vorhang.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { Temporal } from "temporal-polyfill"
|
||||||
|
import { defineInteractionScene, type SceneDefinition } from "../types"
|
||||||
|
|
||||||
|
export const sceneTutorialVorhang: SceneDefinition = defineInteractionScene({
|
||||||
|
id: "tutorial-vorhang",
|
||||||
|
type: "interaction",
|
||||||
|
label: "Tutorial: Vorhang",
|
||||||
|
plannedDuration: Temporal.Duration.from({ seconds: 30 }),
|
||||||
|
objects: {
|
||||||
|
"buehnenbildner": {
|
||||||
|
label: "Bühnenbildner",
|
||||||
|
reveal: true
|
||||||
|
},
|
||||||
|
"vorhang": {
|
||||||
|
label: "Vorhang",
|
||||||
|
reveal: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interactions: [
|
||||||
|
{
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: {
|
||||||
|
"buehnenbildner": { consume: true },
|
||||||
|
"vorhang": { consume: true },
|
||||||
|
},
|
||||||
|
outputObjectIds: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
|
@ -1,7 +1,6 @@
|
||||||
import { Temporal } from "temporal-polyfill"
|
import { Temporal } from "temporal-polyfill"
|
||||||
import type { Exact } from "type-fest"
|
|
||||||
import { getSuggestedInteractionFromDefinition, getSuggestedInteractionId } from "../scene-types/interaction"
|
import { getSuggestedInteractionFromDefinition, getSuggestedInteractionId } from "../scene-types/interaction"
|
||||||
import type { IdMap } from "../util"
|
import { cMap, cSet } from "../util"
|
||||||
|
|
||||||
export interface Script {
|
export interface Script {
|
||||||
scenesById: Map<string, SceneDefinition>
|
scenesById: Map<string, SceneDefinition>
|
||||||
|
@ -9,45 +8,88 @@ export interface Script {
|
||||||
|
|
||||||
export interface SceneDefinitionBase {
|
export interface SceneDefinitionBase {
|
||||||
id: string
|
id: string
|
||||||
|
type: string
|
||||||
label: string
|
label: string
|
||||||
plannedDuration: Temporal.Duration
|
plannedDuration: Temporal.Duration | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defineInteractionScene = <ObjectIds extends string>(d: SceneDefinitionBase & {
|
export const defineInteractionScene = <ObjectsT extends Record<string, SceneObjectDefinition<keyof ObjectsT & string>>>(d: SceneDefinitionBase & {
|
||||||
type: "interaction"
|
type: "interaction"
|
||||||
objects: IdMap<SceneObjectDefinition & { id: ObjectIds }>
|
objects: ObjectsT
|
||||||
interactions: Array<UseInteractionDefinition<ObjectIds> | CombineInteractionDefinition<ObjectIds>>
|
interactions: Array<RawUseInteractionDefinition<keyof ObjectsT & string> | RawCombineInteractionDefinition<keyof ObjectsT & string>>
|
||||||
}) => ({
|
}): InteractionSceneDefinition<keyof ObjectsT & string> => ({
|
||||||
...d,
|
...d,
|
||||||
interactionsById: new Map(d.interactions.map(i => [getSuggestedInteractionId(getSuggestedInteractionFromDefinition(i)), i]))
|
objectsById: cMap(d.objects),
|
||||||
|
interactionsById: new Map(d.interactions.values().map(raw => {
|
||||||
|
let i: UseInteractionDefinition<keyof ObjectsT & string> | CombineInteractionDefinition<keyof ObjectsT & string>
|
||||||
|
switch (raw.type) {
|
||||||
|
case "use":
|
||||||
|
i = {
|
||||||
|
type: "use",
|
||||||
|
objectId: raw.objectId,
|
||||||
|
consume: raw.consume,
|
||||||
|
revealedObjectIds: cSet(...raw.revealedObjectIds),
|
||||||
|
note: raw.note
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case "combine":
|
||||||
|
i = {
|
||||||
|
type: "combine",
|
||||||
|
inputObjects: cMap(raw.inputObjects),
|
||||||
|
outputObjectIds: cSet(...raw.outputObjectIds),
|
||||||
|
note: raw.note
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [getSuggestedInteractionId(getSuggestedInteractionFromDefinition(i)), i]
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
export interface InteractionSceneDefinition<ObjectIds extends string = string> extends SceneDefinitionBase {
|
export interface InteractionSceneDefinition<ObjectIds extends string = string> extends SceneDefinitionBase {
|
||||||
type: "interaction"
|
type: "interaction"
|
||||||
objects: IdMap<SceneObjectDefinition & { id: ObjectIds }>
|
objectsById: Map<ObjectIds, SceneObjectDefinition<ObjectIds>>
|
||||||
interactionsById: Map<string, UseInteractionDefinition<ObjectIds> | CombineInteractionDefinition<ObjectIds>>
|
interactionsById: Map<string, UseInteractionDefinition<ObjectIds> | CombineInteractionDefinition<ObjectIds>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SceneObjectDefinition {
|
export interface SceneObjectDefinition<AvailableObjectIds extends string = string> {
|
||||||
id: string
|
|
||||||
label: string
|
label: string
|
||||||
reveal: boolean
|
reveal: boolean
|
||||||
relevant: boolean
|
completion?: SceneObjectDefinitionCompletionOptions<AvailableObjectIds>
|
||||||
completionSteps?: number
|
}
|
||||||
|
|
||||||
|
export interface SceneObjectDefinitionCompletionOptions<AvailableObjectIds extends string = string> {
|
||||||
|
steps: number
|
||||||
|
replaceWith: AvailableObjectIds
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RawUseInteractionDefinition<AvailableObjectIds extends string> {
|
||||||
|
type: "use"
|
||||||
|
objectId: AvailableObjectIds
|
||||||
|
consume: boolean
|
||||||
|
revealedObjectIds: Array<AvailableObjectIds>
|
||||||
|
note?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseInteractionDefinition<AvailableObjectIds extends string> {
|
export interface UseInteractionDefinition<AvailableObjectIds extends string> {
|
||||||
type: "use"
|
type: "use"
|
||||||
objectId: AvailableObjectIds
|
objectId: AvailableObjectIds
|
||||||
consume: boolean
|
consume: boolean
|
||||||
note: string
|
revealedObjectIds: Set<AvailableObjectIds>
|
||||||
|
note?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RawCombineInteractionDefinition<AvailableObjectIds extends string> {
|
||||||
|
type: "combine"
|
||||||
|
inputObjects: Partial<Record<AvailableObjectIds, CombinationInteractionDefinitionInputOptions>>
|
||||||
|
outputObjectIds: Array<AvailableObjectIds>
|
||||||
|
note?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CombineInteractionDefinition<AvailableObjectIds extends string> {
|
export interface CombineInteractionDefinition<AvailableObjectIds extends string> {
|
||||||
type: "combine"
|
type: "combine"
|
||||||
inputObjects: Map<AvailableObjectIds, CombinationInteractionDefinitionInputOptions>
|
inputObjects: Map<AvailableObjectIds, CombinationInteractionDefinitionInputOptions>
|
||||||
outputObjectIds: Set<AvailableObjectIds>
|
outputObjectIds: Set<AvailableObjectIds>
|
||||||
note: string
|
note?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CombinationInteractionDefinitionInputOptions {
|
export interface CombinationInteractionDefinitionInputOptions {
|
||||||
|
|
|
@ -1,19 +1,7 @@
|
||||||
export type IdMap<T extends { id: string }> = Map<T["id"], T>
|
|
||||||
|
|
||||||
export const cSet = <T>(...values: T[]) => new Set(values)
|
export const cSet = <T>(...values: T[]) => new Set(values)
|
||||||
|
|
||||||
export const cMap = <K extends string, V>(object: Record<K, V>): Map<K, V> => {
|
export const cMap = <K extends string, V>(object: Partial<Record<K, V>>): Map<K, V> => {
|
||||||
const result = new Map<K, V>();
|
const result = new Map<K, V>();
|
||||||
for (const key in object) {
|
Object.entries(object).forEach(([k, v]) => result.set(k as K, v as V))
|
||||||
result.set(key, object[key])
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export const cIdMap = <K extends string, V extends { id: K }>(...values: V[]) => {
|
|
||||||
const result = new Map<K, V>();
|
|
||||||
for (const value of values) {
|
|
||||||
result.set(value.id, value)
|
|
||||||
}
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue