Feat : various fixes, sounds and multiplayer additions

This commit is contained in:
2024-07-03 23:27:10 +02:00
parent 866a537924
commit 25cf8a55e9
16 changed files with 433 additions and 272 deletions
+18 -17
View File
@@ -15,17 +15,15 @@ const canvas = ref<HTMLCanvasElement | null>(null);
const canvas_resize_temp = ref<HTMLCanvasElement | null>(null); const canvas_resize_temp = ref<HTMLCanvasElement | null>(null);
export interface Props { export interface Props {
colorStart?: [number, number, number]; isTurn?: boolean;
colorEnd?: [number, number, number];
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
colorStart: () => [0, 0.3, 0], isTurn: () => false,
colorEnd: () => [0, 1, 0],
}); });
const _colorStart = reactive(props.colorStart); const _colorStart = reactive([0, 0.3, 0]);
const _colorEnd = reactive(props.colorEnd); const _colorEnd = reactive([0, 1, 0]);
function initShader() { function initShader() {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
@@ -119,15 +117,19 @@ watch(
} }
); );
// watch(() => [props.game.currentTurn], () => { watch(
// if (props.game.teams.find() != goStore.self?.team) { () => [props.isTurn],
// gsap.to(_colorStart, { duration: 0.5, 0: 0, 1: .2, 2: .3 }) () => {
// gsap.to(_colorEnd, { duration: 0.5, 0: 0, 1: 1, 2: 1 }) if (props.isTurn) {
// } else { gsap.to(_colorStart, { duration: 0.5, 0: 0, 1: 0.3, 2: 0 });
// gsap.to(_colorStart, { duration: 0.5, 0: 0, 1: .3, 2: 0 }) gsap.to(_colorEnd, { duration: 0.5, 0: 0, 1: 1, 2: 0 });
// gsap.to(_colorEnd, { duration: 0.5, 0: 0, 1: 1, 2: 0 }) } else {
// } gsap.to(_colorStart, { duration: 0.5, 0: 0, 1: 0.2, 2: 0.3 });
// }, { immediate: true }) gsap.to(_colorEnd, { duration: 0.5, 0: 0, 1: 1, 2: 1 });
}
},
{ immediate: true }
);
watch([_colorEnd, _colorStart], () => { watch([_colorEnd, _colorStart], () => {
recolorShader(); recolorShader();
@@ -153,8 +155,7 @@ onUnmounted(() => {
:class="{ pixelated: settingsStore.shaderPixelated }" :class="{ pixelated: settingsStore.shaderPixelated }"
width="480" width="480"
height="270" height="270"
>test</canvas ></canvas>
>
</template> </template>
<style scoped> <style scoped>
+152 -115
View File
@@ -1,200 +1,237 @@
<script setup lang="ts"> <script setup lang="ts">
export interface Props { export interface Props {
uuid?: string uuid?: string;
card_id?: number card_id?: number;
locked?: boolean locked?: boolean;
wrapped?: boolean wrapped?: boolean;
tilted?: boolean tilted?: boolean;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
wrapped: () => false, wrapped: () => false,
tilted: () => true, tilted: () => true,
locked: () => true locked: () => true,
}) });
const emit = defineEmits(["click"]) const emit = defineEmits(["click"]);
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore();
const childCount = ref<number>(0) const childCount = ref<number>(0);
const card = ref<HTMLElement>() const card = ref<HTMLElement>();
const extension = computed(() => { const extension = computed(() => {
if (settingsStore.cardStyle == CardStyles.SVG) { if (settingsStore.cardStyle == CardStyles.SVG) {
return ".svg" return ".svg";
} }
return ".png" return ".png";
}) });
function cardClick() { function cardClick() {
if (!props.card_id) return if (!props.card_id) return;
emit("click") emit("click");
} }
const isZooming = ref<boolean>(false) const isZooming = ref<boolean>(false);
function mouseenter(e: MouseEvent) { function mouseenter(e: MouseEvent) {
// User is Right Clicking // User is Right Clicking
if ((e.buttons & 2) == 2) { if ((e.buttons & 2) == 2) {
isZooming.value = true isZooming.value = true;
} }
// goStore.hoveredCard = props.card_id // goStore.hoveredCard = props.card_id
} }
function mouseleave(e: MouseEvent) { function mouseleave(e: MouseEvent) {
isZooming.value = false isZooming.value = false;
} }
function rightClickDown(e: MouseEvent) { function rightClickDown(e: MouseEvent) {
isZooming.value = true isZooming.value = true;
} }
function rightClickUp(e: MouseEvent) { function rightClickUp(e: MouseEvent) {
isZooming.value = false isZooming.value = false;
} }
function activateEffect(e: CustomEvent) { function activateEffect(e: CustomEvent) {
console.log(e.detail, props.uuid) console.log(e.detail, props.uuid);
} }
onMounted(() => { onMounted(() => {
card.value?.style.setProperty("--random-seed", -Math.random() * 10 + 's') card.value?.style.setProperty("--random-seed", -Math.random() * 10 + "s");
}) });
</script> </script>
<template> <template>
<div ref="card" class="card" :class="{ active: childCount > 0, wrapped, unlocked: !props.locked, zoom: isZooming }" <div
@contextmenu.prevent="false" @click.left="cardClick" @mousedown.right="rightClickDown" ref="card"
@mouseup.right="rightClickUp" @mouseenter="mouseenter" @mouseleave="mouseleave"> class="card"
<div id="contextMenuButtons" :ref="(el) => { childCount = ((el as Element)?.childElementCount ?? 0) }"> :class="{
<slot></slot> active: childCount > 0,
</div> wrapped,
<template v-if="props.card_id"> unlocked: !props.locked,
<svg width="180" height="250" v-if="settingsStore.cardStyle == CardStyles.SVG" viewBox="0 0 298 417"> zoom: isZooming,
<use :xlink:href="`/cards/${settingsStore.cardStyle}/` + props.card_id.toString().padStart(3, '0') + extension + '#card' + props.card_id" }"
@activate_effect="activateEffect($event)"> @contextmenu.prevent="false"
</use> @click.left="cardClick"
</svg> @mousedown.right="rightClickDown"
<img :class="`card_image`" v-else @mouseup.right="rightClickUp"
:src="`/cards/${settingsStore.cardStyle}/` + props.card_id.toString().padStart(3, '0') + extension" @mouseenter="mouseenter"
draggable="false"> @mouseleave="mouseleave"
</template> >
<div
<img :class="`card_image`" v-else :src="'/cards/background.png'" draggable="false"> id="contextMenuButtons"
:ref="(el) => { childCount = ((el as Element)?.childElementCount ?? 0) }"
>
<slot></slot>
</div> </div>
<template v-if="props.card_id">
<svg
width="180"
height="250"
v-if="settingsStore.cardStyle == CardStyles.SVG"
viewBox="0 0 298 417"
>
<use
:xlink:href="
`/cards/${settingsStore.cardStyle}/` +
props.card_id.toString().padStart(3, '0') +
extension +
'#card' +
props.card_id
"
@activate_effect="activateEffect($event)"
></use>
</svg>
<img
:class="`card_image`"
v-else
:src="
`/cards/${settingsStore.cardStyle}/` +
props.card_id.toString().padStart(3, '0') +
extension
"
draggable="false"
/>
</template>
<img
:class="`card_image`"
v-else
:src="'/cards/background.png'"
draggable="false"
/>
</div>
</template> </template>
<style scoped> <style scoped>
.card { .card {
--max-height: 250px; --max-height: 250px;
--margin-bottom: calc(var(--max-height) * 0.05); --margin-bottom: calc(var(--max-height) * 0.05);
/* max-width: 256px; */ /* max-width: 256px; */
position: relative; position: relative;
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-bottom: 0px; margin-bottom: 0px;
margin-top: var(--margin-bottom); margin-top: var(--margin-bottom);
transition: all 0.075s cubic-bezier(1, 1.81, .59, 1.42); transition: all 0.075s cubic-bezier(1, 1.81, 0.59, 1.42);
isolation: isolate; isolation: isolate;
pointer-events: all; pointer-events: all;
max-height: var(--max-height); max-height: var(--max-height);
} }
.card.active { .card.active {
cursor: pointer cursor: pointer;
} }
.card_image { .card_image {
max-height: var(--max-height); max-height: var(--max-height);
z-index: 1; z-index: 1;
height: var(--max-height); height: var(--max-height);
} }
.card.wrapped { .card.wrapped {
height: 6px; height: 1px;
margin: 0; margin: 0;
} }
.card.wrapped .card_image { .card.wrapped .card_image {
filter: drop-shadow(.5px .5px .5px gray) drop-shadow(-.5px -.5px .5px gray); filter: drop-shadow(0.5px 0.5px 0.5px gray)
drop-shadow(-0.5px -0.5px 0.5px gray);
} }
.card.unlocked { .card.unlocked {
margin-top: 0px; margin-top: 0px;
margin-bottom: var(--margin-bottom); margin-bottom: var(--margin-bottom);
transform: translateY(calc(var(--margin-bottom)*-.25)); transform: translateY(calc(var(--margin-bottom) * -0.25));
filter: drop-shadow(calc(var(--margin-bottom)*.3) var(--margin-bottom) 1px rgba(0, 0, 0, .60)); filter: drop-shadow(
cursor: pointer; calc(var(--margin-bottom) * 0.3) var(--margin-bottom) 1px rgba(0, 0, 0, 0.6)
);
cursor: pointer;
} }
.card.zoom { .card.zoom {
transform: scale(200%); transform: scale(200%);
z-index: 10; z-index: 10;
} }
#contextMenuButtons { #contextMenuButtons {
position: absolute; position: absolute;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
word-break: break-word; word-break: break-word;
bottom: 0; bottom: 0;
width: 99%; width: 99%;
transform: translateY(-0px); transform: translateY(-0px);
transition: 0.1s transform cubic-bezier(1, 1.81, .59, 1.42); transition: 0.1s transform cubic-bezier(1, 1.81, 0.59, 1.42);
} }
.card.active:hover #contextMenuButtons { .card.active:hover #contextMenuButtons {
transform: translateY(100%); transform: translateY(100%);
} }
</style> </style>
<style> <style>
.card.unlocked>* { .card.unlocked > * {
pointer-events: none; pointer-events: none;
} }
#contextMenuButtons>* { #contextMenuButtons > * {
flex: 1; flex: 1;
border-radius: 12px; border-radius: 12px;
min-height: 50px; min-height: 50px;
} }
@keyframes gelatine { @keyframes gelatine {
from,
to {
}
from, 25% {
to {} transform: scale(1, 1.5);
}
25% { 50% {
transform: scale(1, 1.5); transform: scale(1.5, 1);
} }
50% { 75% {
transform: scale(1.5, 1); transform: scale(1.1, 1.3);
} }
75% {
transform: scale(1.1, 1.3);
}
} }
.svg_resource_icon { .svg_resource_icon {
cursor: pointer; cursor: pointer;
animation: gelatine 0.3s normal; animation: gelatine 0.3s normal;
transition: transform .1s cubic-bezier(0.175, 0.885, 0.32, 1.275); transition: transform 0.1s cubic-bezier(0.175, 0.885, 0.32, 1.275);
} }
.svg_resource_icon:active { .svg_resource_icon:active {
animation: none; animation: none;
} }
.svg_resource_icon:hover { .svg_resource_icon:hover {
transform: scale(130%); transform: scale(130%);
} }
</style> </style>
+18 -7
View File
@@ -55,13 +55,24 @@ function endAnimation() {
animationCallbacks.value.forEach((p) => p()); animationCallbacks.value.forEach((p) => p());
} }
containerEvents.set(props.container._id, { watch(
populateContainer, props.container,
shuffleContainer, (newValue, oldValue) => {
getCardsPosition, if (oldValue) {
startAnimation, containerEvents.delete(oldValue._id);
endAnimation, }
}); if (newValue) {
containerEvents.set(newValue._id, {
populateContainer,
shuffleContainer,
getCardsPosition,
startAnimation,
endAnimation,
});
}
},
{ immediate: true }
);
async function onEnter(_el: any, done: () => void) { async function onEnter(_el: any, done: () => void) {
animationCallbacks.value.push(done); animationCallbacks.value.push(done);
+6 -1
View File
@@ -3,6 +3,7 @@ import type { Container } from "~/netcode/interfaces";
export interface Props { export interface Props {
market: Container; market: Container;
marketStack: Container;
fire_gems: Container; fire_gems: Container;
} }
@@ -28,7 +29,11 @@ function fireGemCardClick(e: string) {
<template> <template>
<div id="market"> <div id="market">
<CardPreview :tilted="false" :card_id="undefined"></CardPreview> <CardsGrouped
:container="marketStack"
:wrapped="true"
:hidden="true"
></CardsGrouped>
<div class="separator"></div> <div class="separator"></div>
<CardsGrouped <CardsGrouped
:container="market" :container="market"
+23 -20
View File
@@ -1,40 +1,43 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Player } from '~/netcode/Player'; import type { Player } from "~/netcode/interfaces";
export interface Props { export interface Props {
player: Player; player: Player;
} }
const props = defineProps<Props>() const props = defineProps<Props>();
</script> </script>
<template> <template>
<div class="player-icon-container"> <div class="player-icon-container">
<img class="avatar" v-if="props.player.image" :src="props.player.image" draggable="false"> <img
<b class="player-name">{{ props.player.username }}</b> class="avatar"
</div> v-if="props.player.user.avatar"
:src="`https://cdn.discordapp.com/avatars/${props.player.user.userId}/${props.player.user.avatar}.webp`"
draggable="false"
/>
<b class="player-name">{{ props.player.user.username }}</b>
</div>
</template> </template>
<style scoped> <style scoped>
.player-icon-container { .player-icon-container {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 10px; margin: 10px;
} }
.avatar { .avatar {
height: 50px; height: 50px;
border-radius: 25px; border-radius: 25px;
} }
.player-name { .player-name {
font-size: 20px; font-size: 20px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin-left: 10px; margin-left: 10px;
color: white; color: white;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
} }
</style> </style>
+90 -79
View File
@@ -1,126 +1,137 @@
<script setup lang="ts"> <script setup lang="ts">
import goStore from '@/netcode'; import type { Game, Player } from "~/netcode/interfaces";
import type { Player } from "~/netcode/Player";
const emit = defineEmits(['click']) const emit = defineEmits(["click"]);
const view = ref<"main" | "discard">("main")
function show(data: "main" | "discard") {
view.value = data
}
export interface Props { export interface Props {
player: Player; game: Game;
focused?: Player player: Player;
focused?: boolean;
isTurn?: boolean;
self?: boolean;
} }
const props = defineProps<Props>()
const props = withDefaults(defineProps<Props>(), {
isTurn: () => false,
self: () => false,
});
</script> </script>
<template> <template>
<div class="player_list_element" @click="$emit('click')" <div
:class="{ self: props.player == goStore.self, focused: props.focused == props.player, isTurn: props.player.team == goStore.current_game?.current_turn }"> class="player_list_element"
<div id="player_banner" class="small"> @click="$emit('click')"
<div class="ready-bubble not-ready" v-if="!props.player.ready && !goStore.current_game?.started">Pas encore :class="{
prêt... self,
</div> focused,
<div class="ready-bubble ready" v-if="props.player.ready && !goStore.current_game?.started">Prêt!</div> isTurn,
<div id="player_info"> }"
<PlayerIcon :player="props.player"></PlayerIcon> >
</div> <div id="player_banner" class="small">
<div id="player_stats"> <!-- <div
<template v-if="goStore.current_game?.started"> class="ready-bubble not-ready"
<PlayerStats :player="props.player"> v-if="!props.player.ready && !goStore.current_game?.started"
</PlayerStats> >
</template> Pas encore prêt...
</div> </div>
</div> <div
class="ready-bubble ready"
v-if="props.player.ready && !goStore.current_game?.started"
>
Prêt!
</div> -->
<div id="player_info">
<PlayerIcon :player="props.player"></PlayerIcon>
</div>
<div id="player_stats">
<!-- <template v-if="goStore.current_game?.started"> -->
<PlayerStats :game="props.game" :player="props.player"> </PlayerStats>
<!-- </template> -->
</div>
</div> </div>
</div>
</template> </template>
<style scoped> <style scoped>
.player_list_element { .player_list_element {
border-radius: 10px; border-radius: 10px;
margin: 10px; margin: 10px;
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, .60)); filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6));
transform: translate(-2.5px, -7.5px); transform: translate(-2.5px, -7.5px);
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
display: flex; display: flex;
justify-content: center; justify-content: center;
box-sizing: border-box; box-sizing: border-box;
transition: all .1s; transition: all 0.1s;
width: 300px; width: 300px;
cursor: pointer cursor: pointer;
} }
.player_list_element::before { .player_list_element::before {
border-radius: 10px; border-radius: 10px;
background: rgb(15, 179, 255); background: rgb(15, 179, 255);
content: ""; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
opacity: .5; opacity: 0.5;
} }
.player_list_element.focused { .player_list_element.focused {
filter: unset; filter: unset;
transform: unset; transform: unset;
} }
.player_list_element.isTurn::before { .player_list_element.isTurn::before {
opacity: .1; opacity: 0.1;
} }
.player_list_element.self::before { .player_list_element.self::before {
background: rgb(33, 177, 76); background: rgb(33, 177, 76);
} }
#player_banner { #player_banner {
display: flex; display: flex;
grid-area: player_stats; grid-area: player_stats;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1; z-index: 1;
pointer-events: none; pointer-events: none;
} }
#player_banner.small { #player_banner.small {
flex-direction: row flex-direction: row;
} }
#player_banner>* { #player_banner > * {
pointer-events: all; pointer-events: all;
} }
#player_stats { #player_stats {
display: flex; display: flex;
grid-area: player_stats; grid-area: player_stats;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.ready-bubble { .ready-bubble {
color: white; color: white;
padding: 0.5em; padding: 0.5em;
border-radius: 1em; border-radius: 1em;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
} }
.ready { .ready {
background-color: green; background-color: green;
} }
.not-ready { .not-ready {
background-color: maroon; background-color: maroon;
} }
</style> </style>
+7 -2
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { endTurn, lockCard } from "~/netcode"; import { endTurn, joinGame, lockCard, startGame } from "~/netcode";
import type { Game, Player } from "~/netcode/interfaces"; import type { Game, Player } from "~/netcode/interfaces";
import { useTurnStore } from "~/stores/turnStore"; import { useTurnStore } from "~/stores/turnStore";
@@ -105,7 +105,6 @@ function boardCardClick(cardUUID: string) {
<template> <template>
<div class="player" :class="{ turn: isPlayerTurn, self: isSelf }"> <div class="player" :class="{ turn: isPlayerTurn, self: isSelf }">
<div id="player_banner" class="big">test</div>
<div id="turn_buttons"> <div id="turn_buttons">
<button <button
id="end_turn" id="end_turn"
@@ -115,6 +114,12 @@ function boardCardClick(cardUUID: string) {
> >
END TURN END TURN
</button> </button>
<button class="end_turn_button turn_icon" @click="startGame(game._id)">
Start game
</button>
<button class="end_turn_button turn_icon" @click="joinGame(game._id)">
Join game
</button>
<div <div
class="warning" class="warning"
+15 -3
View File
@@ -1,18 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Player } from "~/netcode/interfaces"; import { playSound } from "~/helpers/audioHelpers";
import type { Game, Player } from "~/netcode/interfaces";
export interface Props { export interface Props {
game: Game;
player: Player; player: Player;
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
const team = computed(() =>
props.game.teams.find((t) =>
t.players.map((p) => p._id).includes(props.player._id)
)
);
const isTurn = computed(
() => team.value && team.value?._id == props.game.currentTurn.currentTeam
);
</script> </script>
<template> <template>
<div class="turn_icon"> <div class="turn_icon">
<img src="/img/champion-shield.png" class="icon_img" draggable="false" /> <img src="/img/champion-shield.png" class="icon_img" draggable="false" />
<span class="turn_icon_number"> <span class="turn_icon_number">
{{ player.team?.health }} {{ team?.health }}
</span> </span>
</div> </div>
<!-- <div class="turn_icon" v-if="showTurnData"> <!-- <div class="turn_icon" v-if="showTurnData">
@@ -30,7 +42,7 @@ const props = defineProps<Props>();
<div class="turn_icon"> <div class="turn_icon">
<img src="/img/fuck.svg" class="icon_img" draggable="false" /> <img src="/img/fuck.svg" class="icon_img" draggable="false" />
<span class="turn_icon_number">{{ <span class="turn_icon_number">{{
(player.turn?.discard_markers ?? 0) + (player.turn?.fuck_u_markers ?? 0) (isTurn && (player.discardMarkers ?? 0) + (player.fuckUMarkers ?? 0)) || 0
}}</span> }}</span>
</div> </div>
</template> </template>
+21 -3
View File
@@ -48,7 +48,25 @@ export async function whoami() {
export async function createGame() { export async function createGame() {
const config = useRuntimeConfig(); const config = useRuntimeConfig();
await $fetch<User>(new URL("/games", config.public.endpoint).href, { const res = await $fetch<Game>(new URL("/games", config.public.endpoint).href, {
method: "POST",
credentials: "include",
});
console.log(res)
navigateTo("/game/" + res._id)
}
export async function startGame(game: string) {
const config = useRuntimeConfig();
const res = await $fetch<Game>(new URL(`games/${game}/start`, config.public.endpoint).href, {
method: "POST",
credentials: "include",
});
}
export async function joinGame(game: string) {
const config = useRuntimeConfig();
const res = await $fetch<Game>(new URL(`games/${game}/join`, config.public.endpoint).href, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
}); });
@@ -56,7 +74,7 @@ export async function createGame() {
export async function endTurn(game: string) { export async function endTurn(game: string) {
const config = useRuntimeConfig(); const config = useRuntimeConfig();
await $fetch<User>( await $fetch(
new URL(`games/${game}/endTurn`, config.public.endpoint).href, new URL(`games/${game}/endTurn`, config.public.endpoint).href,
{ {
method: "POST", method: "POST",
@@ -67,7 +85,7 @@ export async function endTurn(game: string) {
export async function lockCard(game: string, cardId: string) { export async function lockCard(game: string, cardId: string) {
const config = useRuntimeConfig(); const config = useRuntimeConfig();
await $fetch<User>( await $fetch(
new URL(`games/${game}/turn/lockCard/${cardId}`, config.public.endpoint).href, new URL(`games/${game}/turn/lockCard/${cardId}`, config.public.endpoint).href,
{ {
method: "POST", method: "POST",
+7
View File
@@ -16,10 +16,13 @@ export interface Game extends GameObject {
marketStack: Container; marketStack: Container;
teams: Team[]; teams: Team[];
currentTurn: PlayingTurn; currentTurn: PlayingTurn;
started: boolean
owner: User
} }
export interface Team extends GameObject { export interface Team extends GameObject {
players: Player[]; players: Player[];
health: number;
} }
export interface Player extends GameObject { export interface Player extends GameObject {
@@ -28,10 +31,14 @@ export interface Player extends GameObject {
hand: Container; hand: Container;
stack: Container; stack: Container;
user: User; user: User;
discardMarkers: number
fuckUMarkers: number
} }
export interface User extends GameObject { export interface User extends GameObject {
userId: string; userId: string;
avatar: string;
username: string;
} }
export interface Container extends GameObject { export interface Container extends GameObject {
+59 -19
View File
@@ -3,9 +3,11 @@ import LoadingScreen from "~/components/LoadingScreen.vue";
import { subscribe } from "~/netcode"; import { subscribe } from "~/netcode";
import { containerEvents, delay } from "~/netcode/events"; import { containerEvents, delay } from "~/netcode/events";
import { type Container, type Game, type Player } from "~/netcode/interfaces"; import { type Container, type Game, type Player } from "~/netcode/interfaces";
import { playSound } from "~/helpers/audioHelpers";
const config = useRuntimeConfig(); const config = useRuntimeConfig();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const authStore = useAuthStore();
const route = useRoute(); const route = useRoute();
const gameId = route.params.id; const gameId = route.params.id;
@@ -76,9 +78,10 @@ eventSource.addEventListener("message", async (message) => {
queueAction(async () => { queueAction(async () => {
await containerEvents.get(data.container)?.startAnimation(); await containerEvents.get(data.container)?.startAnimation();
}); });
const fromContainer = containers.value[data.container.id as string]; const fromContainer = containers.value[data.container._id as string];
queueAction(async () => { queueAction(async () => {
for (const card of data.container.cards) { for (const card of data.container.cards) {
playSound("/sounds/distribute.mp3");
fromContainer.cards.push(card); fromContainer.cards.push(card);
await delay(settingsStore.animationSpeed); await delay(settingsStore.animationSpeed);
} }
@@ -88,6 +91,8 @@ eventSource.addEventListener("message", async (message) => {
}); });
} else if (data.type == "shuffleContainer") { } else if (data.type == "shuffleContainer") {
queueAction(async () => { queueAction(async () => {
playSound("/sounds/shuffle.mp3");
// TODO : IMPORTANT : find a better way than containerEvents
await containerEvents.get(data.container)?.shuffleContainer(); await containerEvents.get(data.container)?.shuffleContainer();
}); });
} else if (data.type == "distributeCards") { } else if (data.type == "distributeCards") {
@@ -108,6 +113,7 @@ eventSource.addEventListener("message", async (message) => {
}); });
queueAction(async () => { queueAction(async () => {
playSound("/sounds/distribute.mp3");
toContainer.cards.push(card); toContainer.cards.push(card);
await delay(settingsStore.animationSpeed); await delay(settingsStore.animationSpeed);
}); });
@@ -119,16 +125,27 @@ eventSource.addEventListener("message", async (message) => {
}); });
} else if (data.type == "endTurn") { } else if (data.type == "endTurn") {
queueAction(async () => { queueAction(async () => {
current_game.currentTurn.cardsLocked = []; current_game.currentTurn = data.currentTurn;
current_game.currentTurn.effectsUsed = [];
current_game.currentTurn.damage = 0;
current_game.currentTurn.gold = 0;
await delay(settingsStore.animationSpeed); await delay(settingsStore.animationSpeed);
const playingPlayer = current_game.teams.find(
(t) => t._id == current_game.currentTurn.currentTeam
)?.players[0];
console.log(playingPlayer);
if (playingPlayer) {
focusPlayer(playingPlayer);
await delay(settingsStore.animationSpeed);
}
}); });
} else if (data.type == "lockCard") { } else if (data.type == "lockCard") {
queueAction(async () => { queueAction(async () => {
playSound("/sounds/lock.mp3");
current_game.currentTurn.cardsLocked.push(data.card); current_game.currentTurn.cardsLocked.push(data.card);
}); });
} else if (data.type == "joinGame") {
queueAction(async () => {
playSound("/sounds/teleport.mp3");
current_game.teams.push(data.team);
});
} else if (data.type == "useEffect") { } else if (data.type == "useEffect") {
queueAction(async () => { queueAction(async () => {
current_game.currentTurn.effectsUsed.push(data.effect); current_game.currentTurn.effectsUsed.push(data.effect);
@@ -141,8 +158,6 @@ eventSource.addEventListener("message", async (message) => {
} }
}); });
const authStore = useAuthStore();
const playerList = computed(() => const playerList = computed(() =>
game.value!.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[]) game.value!.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
); );
@@ -151,6 +166,13 @@ const selfPlayer = computed(() =>
? playerList.value?.find((p) => p.user.userId == authStore.selfUser.userId) ? playerList.value?.find((p) => p.user.userId == authStore.selfUser.userId)
: undefined : undefined
); );
const isTurn = computed(
() =>
selfPlayer.value &&
game.value &&
game.value.teams.find((t) => t.players.includes(selfPlayer.value!))?._id ==
game.value.currentTurn.currentTeam
);
// TODO : current turn sound // TODO : current turn sound
@@ -161,6 +183,8 @@ const selfPlayer = computed(() =>
const showResults = ref(false); const showResults = ref(false);
const isWinner = ref(false); const isWinner = ref(false);
const loaded = ref(false); const loaded = ref(false);
const discard_unwrapped = ref(false);
const focusedPlayer = ref<Player>(selfPlayer.value ?? playerList.value[0]);
function showLoserScreen() { function showLoserScreen() {
// playSound('/sounds/ding.mp3') // playSound('/sounds/ding.mp3')
@@ -174,9 +198,6 @@ function showWinnerScreen() {
isWinner.value = true; isWinner.value = true;
} }
const discard_unwrapped = ref(false);
const focusedPlayer = ref<Player>(selfPlayer.value ?? playerList.value[0]);
// const clientStore = useClientSideStore() // const clientStore = useClientSideStore()
// const restack_discarded_champion = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CHAMPION, target) // 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 restack_discarded_card = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CARD, target)
@@ -202,7 +223,10 @@ onUnmounted(() => {
<template> <template>
<div @contextmenu.prevent="false" id="game_board" v-if="game"> <div @contextmenu.prevent="false" id="game_board" v-if="game">
<AnimatedBackground @ready="loaded = true"></AnimatedBackground> <AnimatedBackground
@ready="loaded = true"
:isTurn="isTurn ?? false"
></AnimatedBackground>
<div id="stack_discard"> <div id="stack_discard">
<div id="stack"> <div id="stack">
<CardsGrouped <CardsGrouped
@@ -223,14 +247,25 @@ onUnmounted(() => {
</div> </div>
</div> </div>
<MarketCards :market="game.market" :fire_gems="game.fireGems"></MarketCards> <MarketCards
:market="game.market"
:fire_gems="game.fireGems"
:marketStack="game.marketStack"
></MarketCards>
<!-- <div id="player_list_container"> <div id="player_list_container">
<div id="player_list"> <div id="player_list">
<PlayerListElement v-for="p in playerList" :player="p" @click="focusPlayer(p)" :focused="focusedPlayer"> <PlayerListElement
</PlayerListElement> v-for="p in playerList"
</div> :player="p"
</div> --> :game="game"
@click="focusPlayer(p)"
:focused="focusedPlayer == p"
:self="selfPlayer == p"
>
</PlayerListElement>
</div>
</div>
<div id="current_player"> <div id="current_player">
<PlayerSlot <PlayerSlot
@@ -273,7 +308,12 @@ onUnmounted(() => {
height: 100%; height: 100%;
} }
#player_list, #player_list {
display: flex;
flex-direction: inherit;
justify-content: center;
}
#current_player { #current_player {
overflow: auto; overflow: auto;
position: relative; position: relative;
+8 -6
View File
@@ -20,10 +20,13 @@ eventSource.addEventListener("message", (message) => {
throw new Error("Couldn't delete game"); throw new Error("Couldn't delete game");
} }
games.value.splice(index, 1); games.value.splice(index, 1);
} else if (data.type == "update") { } else if (data.type == "joinGame") {
throw new Error("not implemented"); games.value.find((g) => g._id == data.game)?.teams.push(data.team);
} else if (data.type == "start") { } else if (data.type == "start") {
throw new Error("not implemented"); const targetGame = games.value.find((g) => g._id == data.game);
if (targetGame) {
targetGame.started = true;
}
} }
}); });
@@ -58,9 +61,8 @@ onUnmounted(() => {
authStore.selfUser.userId == g.owner.userId ? 'own_game' : '' authStore.selfUser.userId == g.owner.userId ? 'own_game' : ''
" "
> >
<template v-if="g.started && g.ended"> GAME OVER </template>
<template <template
v-else-if=" v-if="
g.teams.every((t) => g.teams.every((t) =>
t.players.every( t.players.every(
(p) => p.user.userId !== authStore.selfUser.userId (p) => p.user.userId !== authStore.selfUser.userId
@@ -101,7 +103,7 @@ onUnmounted(() => {
<img <img
@click="router.push('/rules')" @click="router.push('/rules')"
class="overlay-image" class="overlay-image"
src="https://static.wikia.nocookie.net/umineko/img/9/9d/PC_Erika.Casual.Sprite_36.png" src="https://static.wikia.nocookie.net/umineko/images/9/9d/PC_Erika.Casual.Sprite_36.png"
alt="pétasse" alt="pétasse"
/> />
<div class="overlay-button"> <div class="overlay-button">
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.