WIP: Fix and improve interaction scenes
All checks were successful
Build / build (push) Successful in 1m8s

This commit is contained in:
Moritz Ruth 2025-04-14 15:42:37 +02:00
parent 97e70432f0
commit 63516d0267
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
16 changed files with 260 additions and 66 deletions

View file

@ -21,6 +21,7 @@ const stop = () => {
console.log("Received stop signal") console.log("Received stop signal")
server.closeAllConnections() server.closeAllConnections()
server.close() server.close()
server.unref()
} }
process.on("SIGTERM", stop) process.on("SIGTERM", stop)

View file

@ -17,6 +17,7 @@ export class InteractionSceneState implements SceneState<InteractionSceneEvent>
private suggestedInteractionIdBySessionId: Map<SessionId, string> = new Map() private suggestedInteractionIdBySessionId: Map<SessionId, string> = new Map()
private ongoingInteractionExecution: SuggestedInteraction | null = null private ongoingInteractionExecution: SuggestedInteraction | null = null
private objectVisibilityById = new Map<string, boolean>() private objectVisibilityById = new Map<string, boolean>()
private objectCompletionStepById = new Map<string, number>()
constructor(private game: Game, private definition: InteractionSceneDefinition) { constructor(private game: Game, private definition: InteractionSceneDefinition) {
definition.objectsById.entries().forEach(([id, o]) => this.setObjectVisibility(id, o.reveal)) definition.objectsById.entries().forEach(([id, o]) => this.setObjectVisibility(id, o.reveal))
@ -27,6 +28,7 @@ export class InteractionSceneState implements SceneState<InteractionSceneEvent>
events.push(this.getVotesChangedEvent()) events.push(this.getVotesChangedEvent())
this.objectVisibilityById.entries().forEach(([id, isVisible]) => events.push({ type: "object-visibility-changed", objectId: id, isVisible })) this.objectVisibilityById.entries().forEach(([id, isVisible]) => events.push({ type: "object-visibility-changed", objectId: id, isVisible }))
this.objectCompletionStepById.entries().forEach(([id, step]) => events.push({ type: "object-completion-step-changed", objectId: id, step }))
if (this.ongoingInteractionExecution !== null) events.push({ type: "interaction-execution-started", interaction: this.ongoingInteractionExecution }) if (this.ongoingInteractionExecution !== null) events.push({ type: "interaction-execution-started", interaction: this.ongoingInteractionExecution })
@ -95,19 +97,19 @@ export class InteractionSceneState implements SceneState<InteractionSceneEvent>
this.emit({ type: "interaction-execution-started", interaction }) this.emit({ type: "interaction-execution-started", interaction })
} }
finishInteractionExecution(id: string, onlyIfOngoing: boolean = true) { finishInteractionExecution(interactionId: string, onlyIfOngoing: boolean = true) {
if (onlyIfOngoing && (this.ongoingInteractionExecution === null || id !== getSuggestedInteractionId(this.ongoingInteractionExecution))) return if (onlyIfOngoing && (this.ongoingInteractionExecution === null || interactionId !== getSuggestedInteractionId(this.ongoingInteractionExecution))) return
this.ongoingInteractionExecution = null this.ongoingInteractionExecution = null
this.emit({ type: "interaction-execution-finished" }) this.emit({ type: "interaction-execution-finished" })
this.removeInteractionFromQueue(id) this.removeInteractionFromQueue(interactionId)
const interaction = this.definition.interactionsById.get(id) const interaction = this.definition.interactionsById.get(interactionId)
if (interaction === undefined) return if (interaction === undefined) return
switch (interaction.type) { switch (interaction.type) {
case "use": case "use":
if (interaction.consume) { if (interaction.consume) {
this.emit({ type: "object-visibility-changed", objectId: interaction.objectId, isVisible: false }) this.setObjectVisibility(interaction.objectId, false)
} }
break break
@ -117,14 +119,34 @@ export class InteractionSceneState implements SceneState<InteractionSceneEvent>
if (consume) this.setObjectVisibility(id, false) if (consume) this.setObjectVisibility(id, false)
}) })
interaction.outputObjectIds.forEach(id => this.setObjectVisibility(id, true)) interaction.outputObjectIds.forEach(id => {
const object = this.definition.objectsById.get(id)!
this.setObjectVisibility(id, true)
if (object.completion !== undefined) {
let step = (this.objectCompletionStepById.get(id) ?? -1)
if (interaction.inputObjects.get(id)?.consume === true) {
step = 0
} else {
step = Math.min(step + 1, object.completion.steps)
}
this.objectCompletionStepById.set(id, step)
this.emit({ type: "object-completion-step-changed", objectId: id, step })
if (step === object.completion.steps) {
this.objectVisibilityById.set(id, false)
this.objectVisibilityById.set(object.completion.replaceWith, true)
}
}
})
break break
} }
} }
cancelInteractionExecution(id: string, onlyIfOngoing: boolean = true) { cancelInteractionExecution(interactionId: string, onlyIfOngoing: boolean = true) {
if (onlyIfOngoing && (this.ongoingInteractionExecution === null || id !== getSuggestedInteractionId(this.ongoingInteractionExecution))) return if (onlyIfOngoing && (this.ongoingInteractionExecution === null || interactionId !== getSuggestedInteractionId(this.ongoingInteractionExecution))) return
this.ongoingInteractionExecution = null this.ongoingInteractionExecution = null
this.emit({ type: "interaction-execution-cancelled" }) this.emit({ type: "interaction-execution-cancelled" })
} }

View file

@ -4,7 +4,7 @@ import type { Component } from "vue"
export interface SceneType<DefinitionT extends SceneDefinitionBase, ControllerT extends SceneController> { export interface SceneType<DefinitionT extends SceneDefinitionBase, ControllerT extends SceneController> {
id: DefinitionT["type"] id: DefinitionT["type"]
playerView: Component<{ controller: ControllerT, definition: DefinitionT }>, playerView: Component<{ controller: ControllerT, definition: DefinitionT }>,
duoView: Component<{ controller: ControllerT, definition: DefinitionT }> crewView: Component<{ controller: ControllerT, definition: DefinitionT }>
createController(definition: DefinitionT): ControllerT createController(definition: DefinitionT): ControllerT
} }

View file

@ -2,12 +2,12 @@ import type { SceneController, SceneType } from "../base"
import type { ChoiceSceneDefinition } from "../../../shared/script/types" import type { ChoiceSceneDefinition } from "../../../shared/script/types"
import PlayerView from "./PlayerView.vue" import PlayerView from "./PlayerView.vue"
import type { ChoiceSceneEvent } from "../../../shared/scene-types/choice" import type { ChoiceSceneEvent } from "../../../shared/scene-types/choice"
import DuoView from "./DuoView.vue" import CrewView from "./CrewView.vue"
export const ChoiceSceneType: SceneType<ChoiceSceneDefinition, ChoiceSceneController> = { export const ChoiceSceneType: SceneType<ChoiceSceneDefinition, ChoiceSceneController> = {
id: "choice", id: "choice",
playerView: PlayerView, playerView: PlayerView,
duoView: DuoView, crewView: CrewView,
createController(definition: ChoiceSceneDefinition): ChoiceSceneController { createController(definition: ChoiceSceneDefinition): ChoiceSceneController {
return { return {
handleEvent(event: ChoiceSceneEvent) { handleEvent(event: ChoiceSceneEvent) {

View file

@ -0,0 +1,57 @@
<template>
<div class="bg-dark-600 rounded-lg flex flex-shrink-0 overflow-hidden">
<div class="flex-grow flex items-center justify-center gap-2 px-3 py-4">
<template v-if="interaction.type === 'use'">
<HandPointingIcon class="text-4xl mb-6"/>
<div class="flex flex-col items-center gap-2">
<ObjectPicture :object-id="interaction.objectId"/>
<div class="text-sm text-gray-200 text-center">
{{ definition.objectsById.get(interaction.objectId)!.label }}
</div>
</div>
</template>
<template v-else v-for="(objectId, index) in interaction.objectIds" :key="objectId">
<div class="flex flex-col items-center gap-2">
<ObjectPicture :object-id="objectId"/>
<div class="text-sm text-gray-200 text-center">
{{ definition.objectsById.get(objectId)!.label }}
</div>
</div>
<PlusIcon v-if="index < interaction.objectIds.size - 1" class="text-3xl mb-6"/>
</template>
</div>
<div class="flex flex-col justify-between items-center bg-gray-800 w-17">
<div class="flex flex-col justify-center items-center pt-3 pb-2">
<div class="text-2xl">{{ votes }}</div>
<div class="text-sm text-center">
Vote{{votes === 1 ? "" : "s" }}
</div>
</div>
</div>
</div>
</template>
<style module lang="scss">
</style>
<script setup lang="ts">
import PlusIcon from "virtual:icons/ph/plus-bold"
import TrashIcon from "virtual:icons/ph/trash-bold"
import CheckIcon from "virtual:icons/ph/check-bold"
import HandPointingIcon from "virtual:icons/ph/hand-pointing-duotone"
import ObjectPicture from "./ObjectPicture.vue"
import { useGame } from "../../game"
import type { SuggestedInteraction } from "../../../shared/mutations"
import type { InteractionSceneController } from "./index"
import { getSuggestedInteractionId } from "../../../shared/scene-types/interaction"
import type { InteractionSceneDefinition } from "../../../shared/script/types"
import { computed } from "vue"
const props = defineProps<{
interaction: SuggestedInteraction
controller: InteractionSceneController
definition: InteractionSceneDefinition
votes: number
}>()
</script>

View file

@ -0,0 +1,42 @@
<template>
<div class="flex flex-col items-center gap-2 bg-dark-600 rounded-lg px-4 py-3">
<span class="text-sm text-gray-200 text-center font-bold">
{{ object.label }}
</span>
<div class="flex gap-3 w-full">
<ObjectPicture class="w-10" :object-id="objectId"/>
<div class="flex flex-col gap-1">
<button
class="flex items-center gap-1 rounded-full border border-solid border-green-600 px-2 py-1 transition-colors text-xs"
:class="isVisible ? 'bg-green-800 text-gray-100' : 'bg-transparent text-gray-300'"
@click="emit('set-visibility', !isVisible)"
>
<EyeIcon v-if="isVisible"/>
<EyeClosedIcon v-else/>
<span>{{ isVisible ? "Sichtbar" : "Versteckt" }}</span>
</button>
</div>
</div>
</div>
</template>
<style module lang="scss">
</style>
<script setup lang="ts">
import ObjectPicture from "./ObjectPicture.vue"
import EyeIcon from "virtual:icons/ph/eye-duotone"
import EyeClosedIcon from "virtual:icons/ph/eye-closed-duotone"
import type { SceneObjectDefinition } from "../../../shared/script/types"
const props = defineProps<{
objectId: string
object: SceneObjectDefinition
isVisible: boolean
}>()
const emit = defineEmits<{
"set-visibility": [boolean]
}>()
</script>

View file

@ -1,33 +1,32 @@
<template> <template>
<section class="flex-grow"> <section class="flex-grow">
<div class="font-bold text-2xl pb-2">Versteckte Objekte</div> <div class="font-bold text-2xl pb-2">Objekte</div>
<div class="grid gap-3 grid-cols-2 flex-grow auto-rows-min p-4 pt-0 relative"> <div class="grid gap-3 grid-cols-3 flex-grow auto-rows-min p-4 pt-0 relative">
<button <CrewObjectCard
v-for="[objectId, object] in definition.objectsById.entries().filter(([id, _o]) => !controller.visibleObjectIds.has(id))" v-for="[objectId, object] in definition.objectsById.entries()"
:key="objectId" :key="objectId"
class="flex flex-col items-center gap-2 bg-dark-600 rounded-lg p-3" :object-id="objectId"
@click="controller.setObjectVisibility(objectId, true)" :object="object"
> :is-visible="controller.visibleObjectIds.has(objectId)"
<ObjectPicture :object-id="objectId"/> @set-visibility="isVisible => controller.setObjectVisibility(objectId, isVisible)"
<span class="text-sm text-gray-200 text-center"> />
{{ object.label }}
</span>
</button>
</div> </div>
</section> </section>
<section class="w-80"> <section class="w-80">
<div class="font-bold text-2xl pb-2">Interaktionen</div> <div class="font-bold text-2xl pb-2">Interaktionen</div>
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<div v-if="controller.sortedInteractionQueue.value.length === 0" class="text-xl text-gray-200"> <div v-if="controller.sortedInteractionQueueWithDefined.value.length === 0" class="text-xl text-gray-200">
Keine Interaktionen vorhanden. Keine Interaktionen vorhanden.
</div> </div>
<transition-group v-else tag="div" name="list" class="flex flex-col gap-4 relative h-80vh"> <transition-group v-else tag="div" name="list" class="flex flex-col gap-4 relative h-90vh overflow-y-auto">
<InteractionQueueItemCard <CrewInteractionCard
v-for="(item, index) in controller.sortedInteractionQueue.value" v-for="(item, index) in controller.sortedInteractionQueueWithDefined.value"
:key="item.id" :key="item.id"
:style="{ zIndex: 1000 - index }" :style="{ zIndex: 1000 - index }"
:item="item" :interaction="item.interaction"
mode="director" :controller="controller"
:definition="definition"
:votes="item.votes"
/> />
</transition-group> </transition-group>
</transition> </transition>
@ -39,11 +38,11 @@
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import ObjectPicture from "./ObjectPicture.vue" import CrewInteractionCard from "./CrewInteractionCard.vue"
import InteractionQueueItemCard from "./InteractionQueueItemCard.vue"
import type { InteractionSceneController } from "./index" import type { InteractionSceneController } from "./index"
import type { InteractionSceneDefinition } from "../../../shared/script/types" import type { InteractionSceneDefinition } from "../../../shared/script/types"
import { useGame } from "../../game" import { useGame } from "../../game"
import CrewObjectCard from "./CrewObjectCard.vue"
const props = defineProps<{ const props = defineProps<{
controller: InteractionSceneController controller: InteractionSceneController

View file

@ -28,25 +28,12 @@
</div> </div>
</div> </div>
<button <button
v-if="mode === 'audience'"
class="align-end py-2 w-full" class="align-end py-2 w-full"
:class="isCurrentSuggestion ? 'bg-blue-500' : 'bg-gray-700'" :class="isCurrentSuggestion ? 'bg-blue-500' : 'bg-gray-700'"
@click="toggleVote()" @click="toggleVote()"
> >
+1 +1
</button> </button>
<button
v-if="mode === 'director'"
class="align-end py-1 w-full bg-green-800"
>
<CheckIcon class="relative top-2px"/>
</button>
<button
v-if="mode === 'director'"
class="align-end py-1 w-full bg-red-900"
>
<TrashIcon class="relative top-2px"/>
</button>
</div> </div>
</div> </div>
</template> </template>
@ -73,11 +60,8 @@
votes: number votes: number
controller: InteractionSceneController controller: InteractionSceneController
definition: InteractionSceneDefinition definition: InteractionSceneDefinition
mode: "audience" | "director"
}>() }>()
const game = useGame()
const isCurrentSuggestion = computed(() => props.controller.suggestedInteractionId.value === getSuggestedInteractionId(props.interaction)) const isCurrentSuggestion = computed(() => props.controller.suggestedInteractionId.value === getSuggestedInteractionId(props.interaction))
function toggleVote() { function toggleVote() {

View file

@ -1,6 +1,10 @@
<template> <template>
<TabbedContainer v-model:activeTabId="activeTabId" :tabs="TABS" :content-props="{ controller, definition }" <TabbedContainer
@event:switch-tab="(tabId: any) => (activeTabId = tabId)"/> v-model:activeTabId="activeTabId"
:tabs="TABS"
:content-props="{ controller, definition }"
@event:switch-tab="(tabId: any) => (activeTabId = tabId)"
/>
</template> </template>
<style module lang="scss"> <style module lang="scss">

View file

@ -11,11 +11,10 @@
</button> </button>
</div> </div>
<transition-group v-else tag="div" name="list" class="flex flex-col gap-4 relative"> <transition-group v-else tag="div" name="list" class="flex flex-col gap-4 relative">
<InteractionQueueItemCard <InteractionQueueItem
v-for="(item, index) in controller.sortedInteractionQueue.value" v-for="(item, index) in controller.sortedInteractionQueue.value"
:key="item.id" :key="item.id"
:style="{ zIndex: 1000 - index }" :style="{ zIndex: 1000 - index }"
mode="audience"
:interaction="item.interaction" :interaction="item.interaction"
:votes="item.votes" :votes="item.votes"
:controller="controller" :controller="controller"
@ -31,7 +30,7 @@
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import InteractionQueueItemCard from "./InteractionQueueItemCard.vue" import InteractionQueueItem from "./InteractionQueueItem.vue"
import { useGame } from "../../game" import { useGame } from "../../game"
import ArrowRightIcon from "virtual:icons/ph/arrow-right" import ArrowRightIcon from "virtual:icons/ph/arrow-right"
import type { InteractionSceneController } from "./index" import type { InteractionSceneController } from "./index"
@ -45,6 +44,4 @@
const emit = defineEmits<{ const emit = defineEmits<{
"switch-tab": [string] "switch-tab": [string]
}>() }>()
const game = useGame()
</script> </script>

View file

@ -3,7 +3,7 @@ import type { InteractionSceneDefinition } from "../../../shared/script/types"
import PlayerView from "./PlayerView.vue" import PlayerView from "./PlayerView.vue"
import type { InteractionSceneEvent } from "../../../shared/scene-types/interaction" import type { InteractionSceneEvent } from "../../../shared/scene-types/interaction"
import { getSuggestedInteractionId } from "../../../shared/scene-types/interaction" import { getSuggestedInteractionId } from "../../../shared/scene-types/interaction"
import DuoView from "./DuoView.vue" import CrewView from "./CrewView.vue"
import type { SuggestedInteraction } from "../../../shared/mutations" import type { SuggestedInteraction } from "../../../shared/mutations"
import { computed, reactive, type Reactive, ref, type Ref } from "vue" import { computed, reactive, type Reactive, ref, type Ref } from "vue"
import { trpcClient } from "../../trpc" import { trpcClient } from "../../trpc"
@ -11,27 +11,71 @@ import { trpcClient } from "../../trpc"
export const InteractionSceneType: SceneType<InteractionSceneDefinition, InteractionSceneController> = { export const InteractionSceneType: SceneType<InteractionSceneDefinition, InteractionSceneController> = {
id: "interaction", id: "interaction",
playerView: PlayerView, playerView: PlayerView,
duoView: DuoView, crewView: CrewView,
createController(definition: InteractionSceneDefinition): InteractionSceneController { createController(definition: InteractionSceneDefinition): InteractionSceneController {
const visibleObjectIds = reactive(new Set<string>()) const visibleObjectIds = reactive(new Set<string>())
const objectCompletionById = reactive(new Map<string, number>())
const replacedObjectIdMap = reactive(new Map<string, string>())
const interactionQueue = reactive(new Map<string, InteractionQueueItem>()) const interactionQueue = reactive(new Map<string, InteractionQueueItem>())
const suggestedInteraction = ref<SuggestedInteraction | null>(null) const suggestedInteraction = ref<SuggestedInteraction | null>(null)
const suggestedInteractionId = computed(() => suggestedInteraction.value === null ? null : getSuggestedInteractionId(suggestedInteraction.value)) const suggestedInteractionId = computed(() => suggestedInteraction.value === null ? null : getSuggestedInteractionId(suggestedInteraction.value))
const ongoingInteractionExecution = ref<SuggestedInteraction | null>(null)
return { return {
visibleObjectIds, visibleObjectIds,
interactionQueue, interactionQueue,
sortedInteractionQueue: computed(() => [...interactionQueue.values()].sort((a, b) => a.votes - b.votes)), sortedInteractionQueue: computed(() => [...interactionQueue.values()].sort((a, b) => a.votes - b.votes)),
sortedInteractionQueueWithDefined: computed(() => {
const result: InteractionQueueItem[] = [...interactionQueue.values()].sort((a, b) => a.votes - b.votes)
for (const [interactionId, interaction] of definition.interactionsById.entries()) {
let suggestion: SuggestedInteraction
switch (interaction.type) {
case "use":
suggestion = {
type: "use",
objectId: interaction.objectId
}
break
case "combine":
suggestion = {
type: "combine",
objectIds: new Set(interaction.inputObjects.keys())
}
}
if (!interactionQueue.has(interactionId)) {
result.push({
id: interactionId,
interaction: suggestion,
votes: 0
})
}
}
return result
}),
suggestedInteraction, suggestedInteraction,
suggestedInteractionId, suggestedInteractionId,
ongoingInteractionExecution,
handleEvent(event: InteractionSceneEvent) { handleEvent(event: InteractionSceneEvent) {
switch (event.type) { switch (event.type) {
case "votes-changed": case "votes-changed":
const removedInteractionIds = new Set(interactionQueue.keys())
Object.entries(event.votesByInteractionId).forEach(([interactionId, votes]) => { Object.entries(event.votesByInteractionId).forEach(([interactionId, votes]) => {
if (votes <= 0) { if (votes > 0) {
interactionQueue.delete(interactionId) interactionQueue.get(interactionId)!.votes = votes
removedInteractionIds.delete(interactionId)
}
})
removedInteractionIds.forEach(interactionId => {
if (interactionId === suggestedInteractionId.value) suggestedInteraction.value = null if (interactionId === suggestedInteractionId.value) suggestedInteraction.value = null
} else interactionQueue.get(interactionId)!.votes = votes return interactionQueue.delete(interactionId)
}) })
break break
@ -42,8 +86,34 @@ export const InteractionSceneType: SceneType<InteractionSceneDefinition, Interac
break break
case "object-visibility-changed": case "object-visibility-changed":
if (event.isVisible) visibleObjectIds.delete(event.objectId) if (event.isVisible) visibleObjectIds.add(event.objectId)
else visibleObjectIds.add(event.objectId) else visibleObjectIds.delete(event.objectId)
break
case "object-completion-step-changed":
objectCompletionById.set(event.objectId, event.step)
const object = definition.objectsById.get(event.objectId)!
if (object.completion!.steps === event.step) {
replacedObjectIdMap.set(event.objectId, object.completion!.replaceWith)
visibleObjectIds.delete(event.objectId)
visibleObjectIds.add(object.completion!.replaceWith)
}
break
case "interaction-execution-started":
ongoingInteractionExecution.value = event.interaction
break
case "interaction-execution-cancelled":
ongoingInteractionExecution.value = null
break
case "interaction-execution-finished":
const interactionId = getSuggestedInteractionId(this.ongoingInteractionExecution.value!)
ongoingInteractionExecution.value = null
interactionQueue.delete(interactionId)
if (suggestedInteractionId.value === interactionId) suggestedInteraction.value = null
break break
} }
}, },
@ -53,7 +123,16 @@ export const InteractionSceneType: SceneType<InteractionSceneDefinition, Interac
async setInteractionVote(interaction: SuggestedInteraction | null) { async setInteractionVote(interaction: SuggestedInteraction | null) {
await trpcClient.player.interactionScene.setInteractionVote.mutate({ interaction }) await trpcClient.player.interactionScene.setInteractionVote.mutate({ interaction })
suggestedInteraction.value = interaction suggestedInteraction.value = interaction
} },
async startInteractionExecution(interaction: SuggestedInteraction) {
await trpcClient.crew.interactionScene.startInteractionExecution.mutate({ interaction })
},
async cancelInteractionExecution(interactionId: string, onlyIfOngoing: boolean) {
await trpcClient.crew.interactionScene.cancelInteractionExecution.mutate({ interactionId, onlyIfOngoing })
},
async finishInteractionExecution(interactionId: string, onlyIfOngoing: boolean) {
await trpcClient.crew.interactionScene.finishInteractionExecution.mutate({ interactionId, onlyIfOngoing })
},
} }
} }
} }
@ -68,10 +147,18 @@ export interface InteractionSceneController extends SceneController {
visibleObjectIds: Reactive<Set<string>> visibleObjectIds: Reactive<Set<string>>
interactionQueue: Reactive<Map<string, InteractionQueueItem>> interactionQueue: Reactive<Map<string, InteractionQueueItem>>
sortedInteractionQueue: Readonly<Ref<Array<InteractionQueueItem>>> sortedInteractionQueue: Readonly<Ref<Array<InteractionQueueItem>>>
sortedInteractionQueueWithDefined: Readonly<Ref<Array<InteractionQueueItem>>>
suggestedInteraction: Ref<SuggestedInteraction | null> suggestedInteraction: Ref<SuggestedInteraction | null>
suggestedInteractionId: Readonly<Ref<string | null>> suggestedInteractionId: Readonly<Ref<string | null>>
ongoingInteractionExecution: Ref<SuggestedInteraction | null>
setObjectVisibility(objectId: string, isVisible: boolean): Promise<void> setObjectVisibility(objectId: string, isVisible: boolean): Promise<void>
setInteractionVote(interaction: SuggestedInteraction | null): Promise<void> setInteractionVote(interaction: SuggestedInteraction | null): Promise<void>
startInteractionExecution(interaction: SuggestedInteraction): Promise<void>
cancelInteractionExecution(interactionId: string, onlyIfOngoing: boolean): Promise<void>
finishInteractionExecution(interactionId: string, onlyIfOngoing: boolean): Promise<void>
} }

View file

@ -2,12 +2,12 @@ import type { SceneController, SceneType } from "../base"
import type { TextSceneDefinition } from "../../../shared/script/types" import type { TextSceneDefinition } from "../../../shared/script/types"
import type { TextSceneEvent } from "../../../shared/scene-types/text" import type { TextSceneEvent } from "../../../shared/scene-types/text"
import PlayerView from "./PlayerView.vue" import PlayerView from "./PlayerView.vue"
import DuoView from "./DuoView.vue" import CrewView from "./CrewView.vue"
export const TextSceneType: SceneType<TextSceneDefinition, TextSceneController> = { export const TextSceneType: SceneType<TextSceneDefinition, TextSceneController> = {
id: "text", id: "text",
playerView: PlayerView, playerView: PlayerView,
duoView: DuoView, crewView: CrewView,
createController(definition: TextSceneDefinition): TextSceneController { createController(definition: TextSceneDefinition): TextSceneController {
return { return {
handleEvent(event: TextSceneEvent) { handleEvent(event: TextSceneEvent) {

View file

@ -19,7 +19,7 @@
</template> </template>
</div> </div>
</section> </section>
<component :is="game.currentScene.type.duoView" :controller="game.currentScene.controller" :definition="game.currentScene.definition"/> <component :is="game.currentScene.type.crewView" :controller="game.currentScene.controller" :definition="game.currentScene.definition"/>
</div> </div>
</template> </template>

View file

@ -25,6 +25,7 @@ export type InteractionSceneEvent =
| { type: "interaction-queued"; interaction: SuggestedInteraction } | { type: "interaction-queued"; interaction: SuggestedInteraction }
| { type: "votes-changed"; votesByInteractionId: Record<string, number> } | { type: "votes-changed"; votesByInteractionId: Record<string, number> }
| { type: "object-visibility-changed"; objectId: string; isVisible: boolean } | { type: "object-visibility-changed"; objectId: string; isVisible: boolean }
| { type: "object-completion-step-changed"; objectId: string; step: number }
| { type: "interaction-execution-started"; interaction: SuggestedInteraction } | { type: "interaction-execution-started"; interaction: SuggestedInteraction }
| { type: "interaction-execution-finished" } | { type: "interaction-execution-finished" }
| { type: "interaction-execution-cancelled" } | { type: "interaction-execution-cancelled" }