Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19bed27f78 | ||
|
|
29cd28953d | ||
|
|
b5d89d4fe9 | ||
|
|
0fb4877266 | ||
|
|
ddcb6c6849 | ||
|
|
afebf1d9de |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
FROM node:lts-alpine AS install-stage
|
FROM node:lts-alpine AS install-stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
FROM git.legonzaur.fr/heronfief/cards-gen:main AS card-svg-stage
|
FROM git.legonzaur.fr/heronfief/cards-gen:main AS card-svg-stage
|
||||||
|
|||||||
@@ -67,6 +67,25 @@ button:disabled {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.yellow {
|
||||||
|
background-color: rgba(255, 165, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.red {
|
||||||
|
background-color: rgba(255, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover:enabled {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
button.yellow:hover {
|
||||||
|
background-color: rgba(255, 165, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.red:hover {
|
||||||
|
background-color: rgba(255, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
#__nuxt {
|
#__nuxt {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|||||||
@@ -23,9 +23,15 @@ function onClick(e: MouseEvent) {
|
|||||||
<span v-else>{{ props.token.effect }}</span>
|
<span v-else>{{ props.token.effect }}</span>
|
||||||
<div class="token_description" v-if="props.token.effect in effect_locales">
|
<div class="token_description" v-if="props.token.effect in effect_locales">
|
||||||
<span
|
<span
|
||||||
><b>{{ effect_locales[props.token.effect].title }}</b></span
|
><b>{{
|
||||||
|
effect_locales[props.token.effect as keyof typeof effect_locales]
|
||||||
|
.title
|
||||||
|
}}</b></span
|
||||||
>
|
>
|
||||||
<span>{{ effect_locales[props.token.effect].description }}</span>
|
<span>{{
|
||||||
|
effect_locales[props.token.effect as keyof typeof effect_locales]
|
||||||
|
.description
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { deleteGame, kickPlayer } from "~/netcode";
|
||||||
|
import type { Game } from "~/netcode/interfaces";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
game: Game;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletionConfirmation = ref("Delete Game");
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const isVisible = ref(false);
|
||||||
|
|
||||||
|
watch(isVisible, () => {
|
||||||
|
deletionConfirmation.value = "Delete Game";
|
||||||
|
});
|
||||||
|
|
||||||
|
function deleteButtonClick() {
|
||||||
|
if (deletionConfirmation.value == "Delete Game") {
|
||||||
|
deletionConfirmation.value = "Are you sure?";
|
||||||
|
} else {
|
||||||
|
deleteGame(props.game._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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 class="dialog-header">
|
||||||
|
<h3>Danger Zone</h3>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-header">
|
||||||
|
<button class="red" @click="deleteButtonClick">
|
||||||
|
{{ deletionConfirmation }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</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>
|
<h3 v-else>Défaite!</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-body">
|
<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-if="won">Vous avez gagné!</p>
|
||||||
<p v-else>
|
<p v-else>
|
||||||
Vous avez perdu face à ce magnifique individu qui a très clairement
|
Vous avez perdu face à ce magnifique individu qui a très clairement
|
||||||
@@ -433,27 +433,6 @@ function discardCardClick(card: Card) {
|
|||||||
transform: translate(-50%, -50%);
|
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 {
|
.end_turn_button:active {
|
||||||
transform: translateY(2px);
|
transform: translateY(2px);
|
||||||
/* Add a slight press effect on click */
|
/* Add a slight press effect on click */
|
||||||
|
|||||||
+191
-9
@@ -1,15 +1,197 @@
|
|||||||
|
import { playSound } from "~/helpers/audioHelpers";
|
||||||
|
import type { Container, Game, Player } from "./interfaces";
|
||||||
|
|
||||||
export function delay(delay: number) {
|
export function delay(delay: number) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(resolve, delay);
|
setTimeout(resolve, delay);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// export type ContainerHandler = {
|
type handlerInput = {
|
||||||
// populateContainer: () => void;
|
game: Game;
|
||||||
// shuffleContainer: () => void;
|
containers: Ref<Record<string, Container>>;
|
||||||
// getCardsPosition: (cards: string) => any;
|
settingsStore: ReturnType<typeof useSettingsStore>;
|
||||||
// startAnimation: () => void;
|
playerList: Ref<Array<Player>>;
|
||||||
// endAnimation: () => void;
|
refresh: () => Promise<void>;
|
||||||
// };
|
focusPlayer: (player: Player) => void;
|
||||||
|
};
|
||||||
// export const containerEvents = reactive(new Map<string, ContainerHandler>());
|
export const handlers: {
|
||||||
|
[key: string]: (data: any, input: handlerInput) => Promise<void>;
|
||||||
|
} = {
|
||||||
|
start: async (data: any, { game }) => {
|
||||||
|
game.started = true;
|
||||||
|
game.currentTurn = data.game.currentTurn;
|
||||||
|
},
|
||||||
|
populateContainer: async (data: any, { containers }) => {
|
||||||
|
const fromContainer = containers.value[data.container._id as string];
|
||||||
|
playSound("/sounds/distribute.mp3");
|
||||||
|
for (const card of data.container.cards) {
|
||||||
|
fromContainer.cards.push(card);
|
||||||
|
// if (!settingsStore.fastAnimations) {
|
||||||
|
// await delay(settingsStore.animationSpeed);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shuffleContainer: async (data: any, { containers, settingsStore }) => {
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
distributeCards: async (data: any, { containers, settingsStore }) => {
|
||||||
|
const fromContainer = containers.value[data.from as string];
|
||||||
|
const toContainer = containers.value[data.to as string];
|
||||||
|
if (settingsStore.fastAnimations) {
|
||||||
|
playSound("/sounds/distribute.mp3");
|
||||||
|
}
|
||||||
|
for (const card of data.cards) {
|
||||||
|
const index = fromContainer.cards.findIndex(
|
||||||
|
(c) => !c || c._id == card._id
|
||||||
|
);
|
||||||
|
fromContainer.cards.splice(index, 1);
|
||||||
|
if (!settingsStore.fastAnimations) {
|
||||||
|
await delay(settingsStore.animationSpeed);
|
||||||
|
playSound("/sounds/distribute.mp3");
|
||||||
|
}
|
||||||
|
toContainer.cards.push(card);
|
||||||
|
if (!settingsStore.fastAnimations) {
|
||||||
|
await delay(settingsStore.animationSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await delay(settingsStore.animationSpeed * 2);
|
||||||
|
},
|
||||||
|
endTurn: async (data: any, { game, settingsStore, focusPlayer }) => {
|
||||||
|
game.currentTurn = data.currentTurn;
|
||||||
|
await delay(settingsStore.animationSpeed);
|
||||||
|
const playingPlayer = game.teams.find(
|
||||||
|
(t) => t._id == game.currentTurn.currentTeam
|
||||||
|
)?.players[0];
|
||||||
|
if (playingPlayer) {
|
||||||
|
focusPlayer(playingPlayer);
|
||||||
|
await delay(settingsStore.animationSpeed);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lockCard: async (data: any, { game, settingsStore }) => {
|
||||||
|
playSound("/sounds/lock.mp3");
|
||||||
|
game.currentTurn.cardsLocked.push(data.card);
|
||||||
|
await delay(settingsStore.animationSpeed);
|
||||||
|
},
|
||||||
|
destroyCard: async (data: any, { containers, settingsStore }) => {
|
||||||
|
const fromContainer = containers.value[data.from as string];
|
||||||
|
const index = fromContainer.cards.findIndex(
|
||||||
|
(c) => !c || c._id == data.card
|
||||||
|
);
|
||||||
|
fromContainer.cards.splice(index, 1);
|
||||||
|
playSound("/sounds/destroy.mp3");
|
||||||
|
await delay(settingsStore.animationSpeed);
|
||||||
|
},
|
||||||
|
joinGame: async (data: any, { game }) => {
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
unlockEffect: async (data: any, { game, settingsStore }) => {
|
||||||
|
const effectIndex = game.currentTurn.lockedEffects.indexOf(data.effect);
|
||||||
|
game.currentTurn.lockedEffects.splice(effectIndex, 1);
|
||||||
|
await delay(settingsStore.animationSpeed);
|
||||||
|
},
|
||||||
|
"currentTurn.updateGold": async (data: any, { game }) => {
|
||||||
|
console.log(`adding gold amount : ` + data.operation);
|
||||||
|
game.currentTurn.gold = data.gold;
|
||||||
|
},
|
||||||
|
"currentTurn.updateDamage": async (data: any, { game }) => {
|
||||||
|
console.log(`adding damage amount : ` + data.operation);
|
||||||
|
game.currentTurn.damage = data.damage;
|
||||||
|
},
|
||||||
|
"currentTurn.addToken": async (data: any, { game, settingsStore }) => {
|
||||||
|
game.currentTurn.tokens.push(data.token);
|
||||||
|
await delay(settingsStore.animationSpeed);
|
||||||
|
},
|
||||||
|
"currentTurn.useToken": async (data: any, { game, settingsStore }) => {
|
||||||
|
const index = game.currentTurn.tokens.findIndex(
|
||||||
|
(t) => t._id == data.tokenId
|
||||||
|
);
|
||||||
|
game.currentTurn.tokens.splice(index, 1);
|
||||||
|
if (!settingsStore.fastAnimations) {
|
||||||
|
await delay(settingsStore.animationSpeed);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"team.updateHealth": async (data: any, { game }) => {
|
||||||
|
const team = game.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;
|
||||||
|
},
|
||||||
|
"player.addFuckUMarker": async (data: any, { playerList }) => {
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
"player.removeFuckUMarker": async (data: any, { playerList }) => {
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
"player.addDiscardMarker": async (data: any, { playerList }) => {
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
"player.removeDiscardMarker": async (data: any, { playerList }) => {
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
killTeam: async (data: any, { game, refresh }) => {
|
||||||
|
const index = game.teams.findIndex((t) => t._id == data.teamId);
|
||||||
|
if (!index) {
|
||||||
|
console.error("team index not found. Desyncs might happen");
|
||||||
|
await refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game.teams.splice(index, 1);
|
||||||
|
},
|
||||||
|
consumeEffect: async (data: any) => {
|
||||||
|
console.warn("consumeEffect not implemented");
|
||||||
|
},
|
||||||
|
endGame: async () => {
|
||||||
|
alert("game ended");
|
||||||
|
},
|
||||||
|
delete: async () => {
|
||||||
|
alert("game deleted");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ export async function startGame(game: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteGame(game: string) {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const res = await $fetch<Game>(config.public.endpoint + `/games/${game}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function joinGame(game: string) {
|
export async function joinGame(game: string) {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const res = await $fetch<Game>(
|
const res = await $fetch<Game>(
|
||||||
@@ -61,6 +69,39 @@ 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 surrender(game: string) {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const res = await $fetch<Game>(
|
||||||
|
config.public.endpoint + `/games/${game}/surrender`,
|
||||||
|
{
|
||||||
|
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) {
|
export async function endTurn(game: string) {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
await $fetch(config.public.endpoint + `/games/${game}/endTurn`, {
|
await $fetch(config.public.endpoint + `/games/${game}/endTurn`, {
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export interface Game extends GameObject {
|
|||||||
currentTurn: PlayingTurn;
|
currentTurn: PlayingTurn;
|
||||||
started: boolean;
|
started: boolean;
|
||||||
owner: User;
|
owner: User;
|
||||||
|
packs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Team extends GameObject {
|
export interface Team extends GameObject {
|
||||||
|
|||||||
+56
-184
@@ -1,6 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LoadingScreen from "~/components/game/overlay/LoadingScreen.vue";
|
import LoadingScreen from "~/components/game/overlay/LoadingScreen.vue";
|
||||||
import { endTurn, subscribe, useToken, startGame, joinGame } from "~/netcode";
|
import {
|
||||||
|
endTurn,
|
||||||
|
subscribe,
|
||||||
|
useToken,
|
||||||
|
startGame,
|
||||||
|
joinGame,
|
||||||
|
deleteGame,
|
||||||
|
leaveGame,
|
||||||
|
surrender,
|
||||||
|
} from "~/netcode";
|
||||||
import { delay } from "~/netcode/events";
|
import { delay } from "~/netcode/events";
|
||||||
import {
|
import {
|
||||||
type Container,
|
type Container,
|
||||||
@@ -8,7 +17,7 @@ import {
|
|||||||
type Game,
|
type Game,
|
||||||
type Player,
|
type Player,
|
||||||
} from "~/netcode/interfaces";
|
} from "~/netcode/interfaces";
|
||||||
import { playSound } from "~/helpers/audioHelpers";
|
import { handlers } from "~/netcode/events";
|
||||||
|
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
@@ -67,167 +76,6 @@ async function execAction(callback: () => Promise<any>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlers = {
|
|
||||||
start: async (data: any, game: Game) => {
|
|
||||||
game.started = true;
|
|
||||||
game.currentTurn = data.game.currentTurn;
|
|
||||||
},
|
|
||||||
populateContainer: async (data: any) => {
|
|
||||||
const fromContainer = containers.value[data.container._id as string];
|
|
||||||
playSound("/sounds/distribute.mp3");
|
|
||||||
for (const card of data.container.cards) {
|
|
||||||
fromContainer.cards.push(card);
|
|
||||||
// if (!settingsStore.fastAnimations) {
|
|
||||||
// await delay(settingsStore.animationSpeed);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shuffleContainer: async (data: any) => {
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
distributeCards: async (data: any) => {
|
|
||||||
const fromContainer = containers.value[data.from as string];
|
|
||||||
const toContainer = containers.value[data.to as string];
|
|
||||||
if (settingsStore.fastAnimations) {
|
|
||||||
playSound("/sounds/distribute.mp3");
|
|
||||||
}
|
|
||||||
for (const card of data.cards) {
|
|
||||||
const index = fromContainer.cards.findIndex(
|
|
||||||
(c) => !c || c._id == card._id
|
|
||||||
);
|
|
||||||
fromContainer.cards.splice(index, 1);
|
|
||||||
if (!settingsStore.fastAnimations) {
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
playSound("/sounds/distribute.mp3");
|
|
||||||
}
|
|
||||||
toContainer.cards.push(card);
|
|
||||||
if (!settingsStore.fastAnimations) {
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await delay(settingsStore.animationSpeed * 2);
|
|
||||||
},
|
|
||||||
endTurn: async (data: any, game: Game) => {
|
|
||||||
game.currentTurn = data.currentTurn;
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
const playingPlayer = game.teams.find(
|
|
||||||
(t) => t._id == game.currentTurn.currentTeam
|
|
||||||
)?.players[0];
|
|
||||||
if (playingPlayer) {
|
|
||||||
focusPlayer(playingPlayer);
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
lockCard: async (data: any, game: Game) => {
|
|
||||||
playSound("/sounds/lock.mp3");
|
|
||||||
game.currentTurn.cardsLocked.push(data.card);
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
},
|
|
||||||
destroyCard: async (data: any) => {
|
|
||||||
const fromContainer = containers.value[data.from as string];
|
|
||||||
const index = fromContainer.cards.findIndex(
|
|
||||||
(c) => !c || c._id == data.card
|
|
||||||
);
|
|
||||||
fromContainer.cards.splice(index, 1);
|
|
||||||
playSound("/sounds/destroy.mp3");
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
},
|
|
||||||
joinGame: async (data: any, game: Game) => {
|
|
||||||
playSound("/sounds/teleport.mp3");
|
|
||||||
game.teams.push(data.team);
|
|
||||||
},
|
|
||||||
lockEffect: async (data: any, game: Game) => {
|
|
||||||
game.currentTurn.lockedEffects.push(data.effect);
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
},
|
|
||||||
unlockEffect: async (data: any, game: Game) => {
|
|
||||||
const effectIndex = game.currentTurn.lockedEffects.indexOf(data.effect);
|
|
||||||
game.currentTurn.lockedEffects.splice(effectIndex, 1);
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
},
|
|
||||||
"currentTurn.updateGold": async (data: any, game: Game) => {
|
|
||||||
console.log(`adding gold amount : ` + data.operation);
|
|
||||||
game.currentTurn.gold = data.gold;
|
|
||||||
},
|
|
||||||
"currentTurn.updateDamage": async (data: any, game: Game) => {
|
|
||||||
console.log(`adding damage amount : ` + data.operation);
|
|
||||||
game.currentTurn.damage = data.damage;
|
|
||||||
},
|
|
||||||
"currentTurn.addToken": async (data: any, game: Game) => {
|
|
||||||
game.currentTurn.tokens.push(data.token);
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
},
|
|
||||||
"currentTurn.useToken": async (data: any, game: Game) => {
|
|
||||||
const index = game.currentTurn.tokens.findIndex(
|
|
||||||
(t) => t._id == data.tokenId
|
|
||||||
);
|
|
||||||
game.currentTurn.tokens.splice(index, 1);
|
|
||||||
if (!settingsStore.fastAnimations) {
|
|
||||||
await delay(settingsStore.animationSpeed);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"team.updateHealth": async (data: any, game: Game) => {
|
|
||||||
const team = game.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;
|
|
||||||
},
|
|
||||||
"player.addFuckUMarker": async (data: any, game: Game) => {
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
"player.removeFuckUMarker": async (data: any, game: Game) => {
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
"player.addDiscardMarker": async (data: any, game: Game) => {
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
"player.removeDiscardMarker": async (data: any, game: Game) => {
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
killTeam: async (data: any, game: Game) => {
|
|
||||||
const index = game.teams.findIndex((t) => t._id == data.teamId);
|
|
||||||
if (!index) {
|
|
||||||
console.error("team index not found. Desyncs might happen");
|
|
||||||
await refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
game.teams.splice(index, 1);
|
|
||||||
},
|
|
||||||
consumeEffect: async (data: any) => {
|
|
||||||
console.warn("consumeEffect not implemented");
|
|
||||||
},
|
|
||||||
endGame: async () => {
|
|
||||||
alert("game ended");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
if (game.value == null) {
|
if (game.value == null) {
|
||||||
navigateTo("/lobby");
|
navigateTo("/lobby");
|
||||||
@@ -250,7 +98,14 @@ onBeforeMount(async () => {
|
|||||||
const data = JSON.parse(message.data);
|
const data = JSON.parse(message.data);
|
||||||
queueAction(async () => {
|
queueAction(async () => {
|
||||||
if (data.type in handlers) {
|
if (data.type in handlers) {
|
||||||
await handlers[data.type as keyof typeof handlers](data, current_game);
|
await handlers[data.type as keyof typeof handlers](data, {
|
||||||
|
game: current_game,
|
||||||
|
containers,
|
||||||
|
playerList,
|
||||||
|
refresh,
|
||||||
|
focusPlayer,
|
||||||
|
settingsStore,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error(data.type + " not implemented");
|
console.error(data.type + " not implemented");
|
||||||
}
|
}
|
||||||
@@ -260,8 +115,12 @@ onBeforeMount(async () => {
|
|||||||
|
|
||||||
const playerList = computed(
|
const playerList = computed(
|
||||||
() =>
|
() =>
|
||||||
game.value &&
|
(game.value &&
|
||||||
game.value.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
|
game.value.teams.reduce(
|
||||||
|
(acc, t) => acc.concat(t.players),
|
||||||
|
[] as Player[]
|
||||||
|
)) ??
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
const selfPlayer = computed(() =>
|
const selfPlayer = computed(() =>
|
||||||
authStore.logged
|
authStore.logged
|
||||||
@@ -388,13 +247,13 @@ onUnmounted(() => {
|
|||||||
></GamePlayerSlot>
|
></GamePlayerSlot>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<GameOverlayResultDialog
|
<GameModalResultDialog
|
||||||
:isVisible="showResults"
|
:isVisible="showResults"
|
||||||
:won="isWinner"
|
:won="isWinner"
|
||||||
:players="playerList"
|
:players="playerList"
|
||||||
@close="showResults = false"
|
@close="showResults = false"
|
||||||
>
|
>
|
||||||
</GameOverlayResultDialog>
|
</GameModalResultDialog>
|
||||||
<GamePlayerSelf
|
<GamePlayerSelf
|
||||||
v-if="selfPlayer"
|
v-if="selfPlayer"
|
||||||
:container="selfPlayer.hand"
|
:container="selfPlayer.hand"
|
||||||
@@ -404,7 +263,14 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<!-- <LoadingScreen v-if="!loaded"></LoadingScreen> -->
|
<!-- <LoadingScreen v-if="!loaded"></LoadingScreen> -->
|
||||||
<footer>
|
<footer>
|
||||||
<GameOverlayOptionsDialog></GameOverlayOptionsDialog>
|
<div>
|
||||||
|
<GameModalOptionsDialog></GameModalOptionsDialog>
|
||||||
|
<GameModalConfigureDialog
|
||||||
|
v-if="game && game.owner.userId == authStore.selfUser.userId"
|
||||||
|
:game="game"
|
||||||
|
></GameModalConfigureDialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<div id="turn_buttons" v-if="game">
|
<div id="turn_buttons" v-if="game">
|
||||||
<template v-if="game.started">
|
<template v-if="game.started">
|
||||||
@@ -419,30 +285,36 @@ onUnmounted(() => {
|
|||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="authStore.logged">
|
<template v-else-if="authStore.logged">
|
||||||
<button
|
<template v-if="game.owner.userId == authStore.selfUser.userId">
|
||||||
class="end_turn_button turn_icon"
|
<button
|
||||||
@click="startGame(game._id)"
|
class="end_turn_button turn_icon"
|
||||||
v-if="game.owner.userId == authStore.selfUser.userId"
|
@click="startGame(game._id)"
|
||||||
>
|
>
|
||||||
Start game
|
Start game
|
||||||
</button>
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="end_turn_button turn_icon"
|
class="end_turn_button turn_icon"
|
||||||
@click="joinGame(game._id)"
|
@click="joinGame(game._id)"
|
||||||
v-else-if="
|
v-else-if="!selfPlayer"
|
||||||
playerList?.every(
|
|
||||||
(p) => p.user.userId != authStore.selfUser.userId
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
Join game
|
Join game
|
||||||
</button>
|
</button>
|
||||||
<button v-else>Leave game</button>
|
<button v-else @click="leaveGame(game._id)">Leave game</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
v-if="game && game.started && selfPlayer"
|
||||||
|
class="red"
|
||||||
|
@click="surrender(game._id)"
|
||||||
|
>
|
||||||
|
Surrender
|
||||||
|
</button>
|
||||||
|
<button @click="navigateTo('/lobby')">Return to lobby</button>
|
||||||
|
</div>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
|
|
||||||
<button @click="navigateTo('/lobby')">Return to lobby</button>
|
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+19
-1
@@ -32,12 +32,30 @@ onBeforeMount(async () => {
|
|||||||
games.value?.push(data);
|
games.value?.push(data);
|
||||||
} else if (data.type == "delete") {
|
} else if (data.type == "delete") {
|
||||||
const index = games.value?.findIndex((g) => g._id == data._id);
|
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");
|
throw new Error("Couldn't delete game");
|
||||||
}
|
}
|
||||||
games.value?.splice(index, 1);
|
games.value?.splice(index, 1);
|
||||||
} else if (data.type == "joinGame") {
|
} else if (data.type == "joinGame") {
|
||||||
games.value?.find((g) => g._id == data.game)?.teams.push(data.team);
|
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") {
|
} else if (data.type == "start") {
|
||||||
const targetGame = games.value?.find((g) => g._id == data.game);
|
const targetGame = games.value?.find((g) => g._id == data.game);
|
||||||
if (targetGame) {
|
if (targetGame) {
|
||||||
|
|||||||
@@ -20,9 +20,8 @@ export async function compileCards() {
|
|||||||
template = await (await fetch(url)).text();
|
template = await (await fetch(url)).text();
|
||||||
}
|
}
|
||||||
|
|
||||||
let arrayTemplate = template.split("\n");
|
template = template.replace(/\<\?.*\?\>/gm, "");
|
||||||
arrayTemplate.splice(0, 1);
|
|
||||||
template = arrayTemplate.join("\n");
|
|
||||||
return [
|
return [
|
||||||
Number(k.match(regex)![0]).toString(),
|
Number(k.match(regex)![0]).toString(),
|
||||||
// markRaw(
|
// markRaw(
|
||||||
@@ -80,10 +79,7 @@ export async function compileTokens() {
|
|||||||
} else {
|
} else {
|
||||||
template = await (await fetch(url)).text();
|
template = await (await fetch(url)).text();
|
||||||
}
|
}
|
||||||
|
template = template.replace(/\<\?.*\?\>/gm, "");
|
||||||
let arrayTemplate = template.split("\n");
|
|
||||||
arrayTemplate.splice(0, 1);
|
|
||||||
template = arrayTemplate.join("\n");
|
|
||||||
return [
|
return [
|
||||||
k.match(regex)![0].toString(),
|
k.match(regex)![0].toString(),
|
||||||
markRaw(
|
markRaw(
|
||||||
|
|||||||
Reference in New Issue
Block a user