Files
front/pages/game/[id].vue
T
legonzaur 7a338fe5fc
release-tag / release-image (push) Successful in 28s
Fix : sacrifice on Discard
2024-07-15 18:07:40 +02:00

556 lines
16 KiB
Vue

<script setup lang="ts">
import LoadingScreen from "~/components/LoadingScreen.vue";
import { subscribe, useToken } from "~/netcode";
import { delay } from "~/netcode/events";
import {
type Container,
type Effect,
type Game,
type Player,
} from "~/netcode/interfaces";
import { playSound } from "~/helpers/audioHelpers";
import TokenComponent from "~/components/tokens/TokenComponent.vue";
const config = useRuntimeConfig();
const settingsStore = useSettingsStore();
const authStore = useAuthStore();
const route = useRoute();
const gameId = route.params.id;
const {
data: game,
pending,
error,
refresh,
} = await useFetch<Game>(config.public.endpoint + `/games/${gameId}`, {
credentials: "include",
});
if (game.value == null) {
navigateTo("/lobby");
throw new Error("game not found");
}
const containers = computed<Record<string, Container>>(() => ({
[game.value!.fireGems._id]: game.value!.fireGems,
[game.value!.market._id]: game.value!.market,
[game.value!.marketStack._id]: game.value!.marketStack,
...Object.fromEntries(
game
.value!.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
.map((p) => [
[p.board._id, p.board],
[p.discard._id, p.discard],
[p.hand._id, p.hand],
[p.stack._id, p.stack],
])
.flat(1)
),
}));
const eventSource = subscribe(`/games/${gameId}/subscribe`);
const actions = ref<(() => Promise<any>)[]>([]);
function queueAction(callback: () => Promise<any>) {
actions.value.push(callback);
if (actions.value.length == 1) {
execAction(callback);
}
}
async function execAction(callback: () => Promise<any>) {
await callback();
actions.value.shift();
if (actions.value.length > 0) {
execAction(actions.value[0]);
}
}
eventSource.addEventListener("message", async (message) => {
const current_game = game.value;
if (!current_game) {
throw new Error("game not found");
}
const data = JSON.parse(message.data);
console.log(data);
if (data.type == "start") {
throw new Error("not implemented");
} else if (data.type == "populateContainer") {
queueAction(async () => {
// await containerEvents.get(data.container)?.startAnimation();
});
const fromContainer = containers.value[data.container._id as string];
queueAction(async () => {
for (const card of data.container.cards) {
playSound("/sounds/distribute.mp3");
fromContainer.cards.push(card);
await delay(settingsStore.animationSpeed);
}
});
queueAction(async () => {
// await containerEvents.get(data.container)?.endAnimation();
});
} else if (data.type == "shuffleContainer") {
queueAction(async () => {
playSound("/sounds/shuffle.mp3");
const container = containers.value[data.container as string];
container.shuffling = true;
await delay(settingsStore.animationSpeed * 2);
container.shuffling = false;
await delay(settingsStore.animationSpeed * 2);
});
} else if (data.type == "distributeCards") {
// containerEvents.get(data.from)?.getCardsPosition(data.cards)
queueAction(async () => {
// containerEvents.get(data.from)?.startAnimation();
// containerEvents.get(data.to)?.startAnimation();
});
const fromContainer = containers.value[data.from as string];
const toContainer = containers.value[data.to as string];
for (const card of data.cards) {
queueAction(async () => {
const index = fromContainer.cards.findIndex(
(c) => !c || c._id == card._id
);
fromContainer.cards.splice(index, 1);
await delay(settingsStore.animationSpeed);
});
queueAction(async () => {
playSound("/sounds/distribute.mp3");
toContainer.cards.push(card);
await delay(settingsStore.animationSpeed);
});
}
queueAction(async () => {
// containerEvents.get(data.from)?.endAnimation();
// containerEvents.get(data.to)?.endAnimation();
await delay(settingsStore.animationSpeed * 2);
});
} else if (data.type == "endTurn") {
queueAction(async () => {
current_game.currentTurn = data.currentTurn;
await delay(settingsStore.animationSpeed);
const playingPlayer = current_game.teams.find(
(t) => t._id == current_game.currentTurn.currentTeam
)?.players[0];
if (playingPlayer) {
focusPlayer(playingPlayer);
await delay(settingsStore.animationSpeed);
}
});
} else if (data.type == "lockCard") {
queueAction(async () => {
playSound("/sounds/lock.mp3");
current_game.currentTurn.cardsLocked.push(data.card);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "destroyCard") {
const fromContainer = containers.value[data.from as string];
queueAction(async () => {
const index = fromContainer.cards.findIndex(
(c) => !c || c._id == data.card
);
fromContainer.cards.splice(index, 1);
await delay(settingsStore.animationSpeed);
});
queueAction(async () => {
playSound("/sounds/destroy.mp3");
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "joinGame") {
queueAction(async () => {
playSound("/sounds/teleport.mp3");
current_game.teams.push(data.team);
});
} else if (data.type == "lockEffect") {
queueAction(async () => {
current_game.currentTurn.lockedEffects.push(data.effect);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "unlockEffect") {
queueAction(async () => {
const effectIndex = current_game.currentTurn.lockedEffects.indexOf(
data.effect
);
current_game.currentTurn.lockedEffects.splice(effectIndex, 1);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "currentTurn.updateGold") {
queueAction(async () => {
console.log(`adding gold amount : ` + data.operation);
current_game.currentTurn.gold = data.gold;
});
} else if (data.type == "currentTurn.updateDamage") {
queueAction(async () => {
console.log(`adding damage amount : ` + data.operation);
current_game.currentTurn.damage = data.damage;
});
} else if (data.type == "currentTurn.addToken") {
queueAction(async () => {
current_game.currentTurn.tokens.push(data.token);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "currentTurn.useToken") {
queueAction(async () => {
const index = current_game.currentTurn.tokens.findIndex(
(t) => t._id == data.tokenId
);
current_game.currentTurn.tokens.splice(index, 1);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "team.updateHealth") {
queueAction(async () => {
const team = game.value?.teams.find((t) => t._id == data.team);
if (!team) {
throw new Error("Team not found");
}
console.log(`adding health amount : ` + data.operation);
team.health = data.health;
});
} else if (data.type == "player.addFuckUMarker") {
queueAction(async () => {
console.log(`adding fuckUMarkers : ` + data.operation);
const targetPlayer = playerList.value.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot add fuckU Markers : player not found");
}
targetPlayer.fuckUMarkers = data.fuckUMarkers;
});
} else if (data.type == "player.removeFuckUMarker") {
queueAction(async () => {
console.log(`adding fuckUMarkers : ` + data.operation);
const targetPlayer = playerList.value.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot remove fuckU Markers : player not found");
}
targetPlayer.fuckUMarkers = data.fuckUMarkers;
});
} else if (data.type == "player.addDiscardMarker") {
queueAction(async () => {
console.log(`adding discardMarkers : ` + data.operation);
const targetPlayer = playerList.value.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot add discard Markers : player not found");
}
targetPlayer.discardMarkers = data.discardMarkers;
});
} else if (data.type == "player.removeDiscardMarker") {
queueAction(async () => {
console.log(`adding fuckUMarkers : ` + data.operation);
const targetPlayer = playerList.value.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot remove discard Markers : player not found");
}
targetPlayer.discardMarkers = data.discardMarkers;
});
} else if (data.type == "killTeam") {
queueAction(async () => {
const index = game.value?.teams.findIndex((t) => t._id == data.teamId);
if (!index) {
console.error("team index not found. Desyncs might happen");
await refresh();
return;
}
game.value?.teams.splice(index, 1);
});
} else if (data.type == "endGame") {
queueAction(async () => {
alert("game Ended");
});
}
});
const playerList = computed(() =>
game.value!.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
);
const selfPlayer = computed(() =>
authStore.logged
? playerList.value?.find((p) => p.user.userId == authStore.selfUser.userId)
: undefined
);
const isTurn = computed(
() =>
selfPlayer.value &&
game.value &&
game.value.teams.find((t) => t.players.includes(selfPlayer.value!))?._id ==
game.value.currentTurn.currentTeam
);
const turnChangeAnimationSpeed = computed(
() => settingsStore.animationSpeed * 2 + "ms"
);
// TODO : current turn sound
// TODO : winner screen
// TODO : join sound
const showResults = ref(false);
const isWinner = ref(false);
const loaded = ref(false);
const discard_unwrapped = ref(false);
const focusedPlayer = ref<Player>(selfPlayer.value ?? playerList.value[0]);
function showLoserScreen() {
// playSound('/sounds/ding.mp3')
showResults.value = true;
isWinner.value = false;
}
function showWinnerScreen() {
// playSound('/sounds/ding.mp3')
showResults.value = true;
isWinner.value = true;
}
// const clientStore = useClientSideStore()
// const restack_discarded_champion = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CHAMPION, target)
// const restack_discarded_card = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CARD, target)
// const sacrifice = (target: Card) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
function focusPlayer(p: Player) {
focusedPlayer.value = p;
}
// watch<Team>(() => goStore.current_game!.current_turn, (new_team, old_team) => {
// if (focusedPlayer.value.team != old_team) { return }
// const first_player = goStore.current_game?.players.find(p => p.team == goStore.current_game?.current_turn)
// if (!first_player) {
// return
// }
// focusPlayer(first_player)
// })
function useTokenClick(t: Effect) {
console.log(t);
useToken(game.value!._id, t._id);
}
function showDiscard() {
discard_unwrapped.value = true;
}
onUnmounted(() => {
eventSource.close();
});
</script>
<template>
<div @contextmenu.prevent="false" id="game_board" v-if="game">
<AnimatedBackground
@ready="loaded = true"
:isTurn="isTurn ?? false"
></AnimatedBackground>
<Transition name="slide-left" mode="out-in">
<div id="stack_discard" :key="focusedPlayer._id">
<div id="stack">
<CardsGrouped
:container="focusedPlayer.stack"
:wrapped="true"
:hidden="true"
>
</CardsGrouped>
</div>
<div
id="discard"
:class="{ invisible: discard_unwrapped }"
@click="() => (discard_unwrapped = true)"
>
<CardsGrouped
:container="focusedPlayer.discard"
:wrapped="true"
@card-click="showDiscard"
:no-effects="true"
>
</CardsGrouped>
</div>
</div>
</Transition>
<MarketCards
:game="game"
:market="game.market"
:fire_gems="game.fireGems"
:marketStack="game.marketStack"
></MarketCards>
<div>
<TokenComponent
:token="t"
v-for="t in game.currentTurn.tokens"
@useToken="useTokenClick"
></TokenComponent>
</div>
<div id="player_list_container">
<div id="player_list">
<PlayerListElement
v-for="p in playerList"
:player="p"
:game="game"
@click="focusPlayer(p)"
:focused="focusedPlayer == p"
:self="selfPlayer == p"
>
</PlayerListElement>
</div>
</div>
<div id="current_player">
<Transition name="slide-left" mode="out-in">
<PlayerSlot
v-if="focusedPlayer"
:player="focusedPlayer"
:game="game!"
:key="focusedPlayer._id"
></PlayerSlot>
</Transition>
</div>
<DiscardOverlay
v-show="discard_unwrapped"
:game="game"
:player="focusedPlayer"
:discard="focusedPlayer.discard"
@close="discard_unwrapped = false"
></DiscardOverlay>
<ResultDialog
:isVisible="showResults"
:won="isWinner"
:players="playerList"
@close="showResults = false"
>
</ResultDialog>
<GlobalOverlay></GlobalOverlay>
<SelfView v-if="selfPlayer" :container="selfPlayer.hand"></SelfView>
<LoadingScreen v-if="!loaded"></LoadingScreen>
</div>
</template>
<style scoped>
#game_status {
position: fixed;
z-index: 10;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 255, 0.75);
}
#game_board {
display: grid;
grid-template-columns: 1fr min-content 1fr;
grid-template-rows: min-content min-content 1fr;
grid-template-areas:
"stack_discard market turn_stats"
"player_list player_list player_list"
"current_player current_player current_player";
height: 100%;
}
#player_list {
display: flex;
flex-direction: inherit;
justify-content: center;
}
#current_player {
grid-area: current_player;
display: flex;
flex-direction: column;
justify-content: center;
/* background: url('/img/bg-darkgrass.png'); */
}
#stack_discard {
grid-area: stack_discard;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
#stack,
#discard {
display: flex;
flex-direction: row;
}
#discard {
cursor: pointer;
}
.discard_overlay {
z-index: 9;
display: flex;
/* background: blue; */
pointer-events: none;
gap: 2px;
flex-wrap: wrap-reverse;
align-content: flex-end;
cursor: default;
position: absolute;
padding-bottom: inherit;
padding-left: 40px;
height: calc(100% - 64px);
padding-top: 64px;
width: calc(100% -40px);
margin-left: 0;
}
#discard_unwrapped_bg {
display: none;
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
z-index: 8;
background: rgba(0, 0, 0, 0.5);
pointer-events: all;
padding: inherit;
}
#discard_unwrapped_bg.unwrapped {
display: block;
}
#market {
grid-area: market;
display: flex;
flex-direction: row;
justify-content: center;
}
#player_list_container {
grid-area: player_list;
display: flex;
flex-direction: row;
justify-content: center;
/* background-image: url("/img/bg-darkgrass.png"); */
}
#player_list {
/* border: 5px solid #000; */
display: flex;
}
.slide-left-enter-active,
.slide-left-leave-active {
transition: all v-bind("settingsStore.animationSpeed * 2 + 'ms'") ease-out;
}
.slide-left-enter-from {
opacity: 0;
transform: translateX(100%);
}
.slide-left-leave-to {
opacity: 0;
transform: translateX(-100%);
}
</style>