Feat : add Leave, kick and game configuration
This commit is contained in:
@@ -67,6 +67,17 @@ button:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button.yellow {
|
||||
background-color: rgba(255, 165, 0, 0.7);
|
||||
}
|
||||
|
||||
button:hover:enabled {
|
||||
background-color: #45a049;
|
||||
}
|
||||
button.yellow:hover {
|
||||
background-color: rgba(255, 165, 0, 0.8);
|
||||
}
|
||||
|
||||
#__nuxt {
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
import { kickPlayer } from "~/netcode";
|
||||
import type { Game } from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
game: Game;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const isVisible = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button @click="isVisible = true" class="dialog-open-button yellow">
|
||||
Game Configuration
|
||||
</button>
|
||||
<div
|
||||
:class="{ visible: isVisible }"
|
||||
class="dialog dialog-background"
|
||||
@click="isVisible = false"
|
||||
></div>
|
||||
<div class="dialog dialog-box" :class="{ visible: isVisible }" @click.stop>
|
||||
<div class="dialog-header">
|
||||
<h3>Extensions</h3>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Enable extensions</span>
|
||||
</div>
|
||||
<div class="dialog-body dialog-flex">
|
||||
<span v-for="extension in props.game.packs"
|
||||
><input type="checkbox" checked="true" disabled />{{ extension }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<h3>Players</h3>
|
||||
</div>
|
||||
<template v-for="team in game.teams">
|
||||
<template v-for="player in team.players">
|
||||
<div class="dialog-header">
|
||||
<span>{{ player.user.username }}</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<button
|
||||
:disabled="game.owner._id == player.user._id"
|
||||
@click="kickPlayer(game._id, player._id)"
|
||||
>
|
||||
Kick
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dialog {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
transition: 0.3s all var(--animation-curve);
|
||||
}
|
||||
.dialog-open-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dialog-open-button:active {
|
||||
filter: drop-shadow(0px 0px 1px);
|
||||
}
|
||||
|
||||
.dialog-flex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.dialog-background {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
transition: 0.3s all linear;
|
||||
}
|
||||
|
||||
.dialog-background.visible {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 100%);
|
||||
padding: 0 20px;
|
||||
border-radius: 5px;
|
||||
width: 800px;
|
||||
border-radius: 30px;
|
||||
max-width: 100%;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
display: grid;
|
||||
grid-template-columns: 33% 33% 33%;
|
||||
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6));
|
||||
background: rgb(124, 85, 13);
|
||||
}
|
||||
|
||||
.dialog-box.visible {
|
||||
bottom: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
}
|
||||
|
||||
.dialog-box h3 {
|
||||
font-size: 30px;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.dialog-header,
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 10px 0;
|
||||
grid-column-start: 2;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
grid-column-start: 3;
|
||||
}
|
||||
</style>
|
||||
@@ -27,7 +27,7 @@ const close = () => {
|
||||
<h3 v-else>Défaite!</h3>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<GamePlayerIcon :player="players[0]"></GamePlayerIcon>
|
||||
<GamePlayerIcon :user="players[0].user"></GamePlayerIcon>
|
||||
<p v-if="won">Vous avez gagné!</p>
|
||||
<p v-else>
|
||||
Vous avez perdu face à ce magnifique individu qui a très clairement
|
||||
@@ -433,27 +433,6 @@ function discardCardClick(card: Card) {
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.end_turn_button {
|
||||
background-color: #4caf50;
|
||||
/* Green background color */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 13px 10px;
|
||||
margin: 0 0 0 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.end_turn_button:hover {
|
||||
background-color: #45a049;
|
||||
/* Darker green on hover */
|
||||
}
|
||||
|
||||
.end_turn_button:active {
|
||||
transform: translateY(2px);
|
||||
/* Add a slight press effect on click */
|
||||
|
||||
+18
-11
@@ -1,6 +1,5 @@
|
||||
import { playSound } from "~/helpers/audioHelpers";
|
||||
import type { Container, Game, Player } from "./interfaces";
|
||||
import type { Store } from "pinia";
|
||||
|
||||
export function delay(delay: number) {
|
||||
return new Promise((resolve) => {
|
||||
@@ -8,16 +7,6 @@ export function delay(delay: number) {
|
||||
});
|
||||
}
|
||||
|
||||
// export type ContainerHandler = {
|
||||
// populateContainer: () => void;
|
||||
// shuffleContainer: () => void;
|
||||
// getCardsPosition: (cards: string) => any;
|
||||
// startAnimation: () => void;
|
||||
// endAnimation: () => void;
|
||||
// };
|
||||
|
||||
// export const containerEvents = reactive(new Map<string, ContainerHandler>());
|
||||
|
||||
type handlerInput = {
|
||||
game: Game;
|
||||
containers: Ref<Record<string, Container>>;
|
||||
@@ -102,6 +91,21 @@ export const handlers: {
|
||||
playSound("/sounds/teleport.mp3");
|
||||
game.teams.push(data.team);
|
||||
},
|
||||
leaveTeam: async (data: any, { game }) => {
|
||||
const team = game.teams.find((t) => t._id == data.team._id);
|
||||
if (team == undefined) {
|
||||
throw new Error("Cannot find team");
|
||||
}
|
||||
const playerIndex = team.players.findIndex((p) => p._id == data.playerId);
|
||||
if (playerIndex == -1) {
|
||||
throw new Error("Cannot find player in team");
|
||||
}
|
||||
team.players.splice(playerIndex, 1);
|
||||
if (team.players.length === 0) {
|
||||
const index = game.teams.findIndex((t) => t._id == data.team._id);
|
||||
game.teams.splice(index, 1);
|
||||
}
|
||||
},
|
||||
lockEffect: async (data: any, { game, settingsStore }) => {
|
||||
game.currentTurn.lockedEffects.push(data.effect);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
@@ -187,4 +191,7 @@ export const handlers: {
|
||||
endGame: async () => {
|
||||
alert("game ended");
|
||||
},
|
||||
delete: async () => {
|
||||
alert("game deleted");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -50,6 +50,10 @@ export async function startGame(game: string) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteGame(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
}
|
||||
|
||||
export async function joinGame(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(
|
||||
@@ -61,6 +65,28 @@ export async function joinGame(game: string) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function leaveGame(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(
|
||||
config.public.endpoint + `/games/${game}/leave`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function kickPlayer(game: string, playerId: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(
|
||||
config.public.endpoint + `/games/${game}/kick/${playerId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function endTurn(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
await $fetch(config.public.endpoint + `/games/${game}/endTurn`, {
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface Game extends GameObject {
|
||||
currentTurn: PlayingTurn;
|
||||
started: boolean;
|
||||
owner: User;
|
||||
packs: string[];
|
||||
}
|
||||
|
||||
export interface Team extends GameObject {
|
||||
|
||||
+23
-12
@@ -1,6 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import LoadingScreen from "~/components/game/overlay/LoadingScreen.vue";
|
||||
import { endTurn, subscribe, useToken, startGame, joinGame } from "~/netcode";
|
||||
import {
|
||||
endTurn,
|
||||
subscribe,
|
||||
useToken,
|
||||
startGame,
|
||||
joinGame,
|
||||
deleteGame,
|
||||
leaveGame,
|
||||
} from "~/netcode";
|
||||
import { delay } from "~/netcode/events";
|
||||
import {
|
||||
type Container,
|
||||
@@ -238,13 +246,13 @@ onUnmounted(() => {
|
||||
></GamePlayerSlot>
|
||||
</Transition>
|
||||
</div>
|
||||
<GameOverlayResultDialog
|
||||
<GameModalResultDialog
|
||||
:isVisible="showResults"
|
||||
:won="isWinner"
|
||||
:players="playerList"
|
||||
@close="showResults = false"
|
||||
>
|
||||
</GameOverlayResultDialog>
|
||||
</GameModalResultDialog>
|
||||
<GamePlayerSelf
|
||||
v-if="selfPlayer"
|
||||
:container="selfPlayer.hand"
|
||||
@@ -254,7 +262,7 @@ onUnmounted(() => {
|
||||
|
||||
<!-- <LoadingScreen v-if="!loaded"></LoadingScreen> -->
|
||||
<footer>
|
||||
<GameOverlayOptionsDialog></GameOverlayOptionsDialog>
|
||||
<GameModalOptionsDialog></GameModalOptionsDialog>
|
||||
<ClientOnly>
|
||||
<div id="turn_buttons" v-if="game">
|
||||
<template v-if="game.started">
|
||||
@@ -269,13 +277,16 @@ onUnmounted(() => {
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="authStore.logged">
|
||||
<button
|
||||
class="end_turn_button turn_icon"
|
||||
@click="startGame(game._id)"
|
||||
v-if="game.owner.userId == authStore.selfUser.userId"
|
||||
>
|
||||
Start game
|
||||
</button>
|
||||
<template v-if="game.owner.userId == authStore.selfUser.userId">
|
||||
<button
|
||||
class="end_turn_button turn_icon"
|
||||
@click="startGame(game._id)"
|
||||
>
|
||||
Start game
|
||||
</button>
|
||||
<GameModalConfigureDialog :game="game"></GameModalConfigureDialog>
|
||||
</template>
|
||||
|
||||
<button
|
||||
class="end_turn_button turn_icon"
|
||||
@click="joinGame(game._id)"
|
||||
@@ -287,7 +298,7 @@ onUnmounted(() => {
|
||||
>
|
||||
Join game
|
||||
</button>
|
||||
<button v-else>Leave game</button>
|
||||
<button v-else @click="leaveGame(game._id)">Leave game</button>
|
||||
</template>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
|
||||
+19
-1
@@ -32,12 +32,30 @@ onBeforeMount(async () => {
|
||||
games.value?.push(data);
|
||||
} else if (data.type == "delete") {
|
||||
const index = games.value?.findIndex((g) => g._id == data._id);
|
||||
if (index == -1 || !index) {
|
||||
if (index == -1 || index == undefined) {
|
||||
throw new Error("Couldn't delete game");
|
||||
}
|
||||
games.value?.splice(index, 1);
|
||||
} else if (data.type == "joinGame") {
|
||||
games.value?.find((g) => g._id == data.game)?.teams.push(data.team);
|
||||
} else if (data.type == "leaveTeam") {
|
||||
const game = games.value?.find((g) => g._id == data.game);
|
||||
if (game == undefined) {
|
||||
throw new Error("Cannot find game");
|
||||
}
|
||||
const team = game.teams.find((t) => t._id == data.team._id);
|
||||
if (team == undefined) {
|
||||
throw new Error("Cannot find team");
|
||||
}
|
||||
const playerIndex = team.players.findIndex((p) => p._id == data.playerId);
|
||||
if (playerIndex == -1) {
|
||||
throw new Error("Cannot find player in team");
|
||||
}
|
||||
team.players.splice(playerIndex, 1);
|
||||
if (team.players.length === 0) {
|
||||
const index = game.teams.findIndex((t) => t._id == data.team._id);
|
||||
game.teams.splice(index, 1);
|
||||
}
|
||||
} else if (data.type == "start") {
|
||||
const targetGame = games.value?.find((g) => g._id == data.game);
|
||||
if (targetGame) {
|
||||
|
||||
Reference in New Issue
Block a user