WIP: Fix and improve interaction scenes
All checks were successful
Build / build (push) Successful in 1m8s
All checks were successful
Build / build (push) Successful in 1m8s
This commit is contained in:
parent
97e70432f0
commit
63516d0267
16 changed files with 260 additions and 66 deletions
|
@ -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)
|
||||||
|
|
|
@ -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" })
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
57
frontend/scene-types/interaction/CrewInteractionCard.vue
Normal file
57
frontend/scene-types/interaction/CrewInteractionCard.vue
Normal 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>
|
42
frontend/scene-types/interaction/CrewObjectCard.vue
Normal file
42
frontend/scene-types/interaction/CrewObjectCard.vue
Normal 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>
|
|
@ -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
|
|
@ -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() {
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
}
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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" }
|
Loading…
Add table
Reference in a new issue