Compare commits

..
Author SHA1 Message Date
legonzaur a6490fc180 Feat : add more tokens descriptions
release-tag / release-image (push) Successful in 30s
2024-08-27 14:59:48 +02:00
legonzaur 19bed27f78 Fix : token compilation
release-tag / release-image (push) Successful in 30s
2024-08-27 14:05:54 +02:00
legonzaur 29cd28953d Feat : add surrender button
release-tag / release-image (push) Successful in 51s
2024-08-27 13:08:15 +02:00
legonzaur b5d89d4fe9 Feat : add game deletion 2024-08-27 12:05:17 +02:00
legonzaur 0fb4877266 Feat : add Leave, kick and game configuration 2024-08-27 11:51:31 +02:00
legonzaur ddcb6c6849 chore/cleanup : move game handlers into netcode directory 2024-08-06 14:41:22 +02:00
legonzaur afebf1d9de chore : fix ide error 2024-08-06 14:28:16 +02:00
legonzaur 4e8a619cb9 fix : dockerfile
release-tag / release-image (push) Successful in 30s
2024-08-06 13:41:06 +02:00
legonzaur cb74d78928 fix : update dockerfile
release-tag / release-image (push) Failing after 11s
2024-08-06 13:38:40 +02:00
legonzaur a3ff9596d7 ci : add tokens to dockerfile
release-tag / release-image (push) Failing after 10s
2024-08-06 13:36:00 +02:00
legonzaur d18a2ab4f2 feat : token visuals draft 2024-08-06 13:30:43 +02:00
legonzaur 517f1bef5d chore: optimize loading by using defineComponent instead of defineAsyncComponent 2024-08-06 11:48:21 +02:00
legonzaur 77724d5539 ci : remove public directory
release-tag / release-image (push) Successful in 30s
2024-08-04 11:39:41 +02:00
legonzaur 21c41bb9fd ci : fix image directory
release-tag / release-image (push) Successful in 28s
2024-08-04 11:36:52 +02:00
15 changed files with 672 additions and 280 deletions
+3 -1
View File
@@ -24,4 +24,6 @@ logs
!.env.example
assets/images/*
public/img/skins
public/img/skins
public/img/icon
public/img/tokens
+13 -5
View File
@@ -1,15 +1,22 @@
FROM node:lts-alpine AS install-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm ci
COPY . .
FROM git.legonzaur.fr/heronfief/cards-gen:main AS svg-stage
FROM git.legonzaur.fr/heronfief/cards-gen:main AS card-svg-stage
RUN python3 -u ./src/generate_cards_mvp.py
FROM git.legonzaur.fr/heronfief/tokens-gen:main AS token-svg-stage
RUN python3 -u ./src/generate_tokens_mvp.py
FROM install-stage AS build-stage
COPY --from=svg-stage /app/output/heron ./assets/images/cards/heron-svg
COPY --from=svg-stage /app/img/skins ./public/img
COPY --from=card-svg-stage /app/output/heron ./assets/images/cards/heron-svg
COPY --from=card-svg-stage /app/img/skins ./public/img
COPY --from=token-svg-stage /app/output/heron ./assets/images/tokens
COPY --from=token-svg-stage /app/img/icon ./public/img
ARG NUXT_PUBLIC_ENDPOINT
ARG NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT
@@ -20,5 +27,6 @@ RUN npx nuxi generate
# étape de production
FROM nginx:stable-alpine AS production-stage
COPY --from=build-stage /app/.output/public /usr/share/nginx/html
COPY --from=svg-stage /app/img/skins /usr/share/nginx/html/public/img
COPY --from=card-svg-stage /app/img/skins /usr/share/nginx/html/img/skins
COPY --from=token-svg-stage /app/img/icon /usr/share/nginx/html/img/icon
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
+22 -2
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { cards, compileCards } from "./services/loadCards";
import { compileCards, compileTokens } from "./services/loadCards";
import { getCurrentInstance } from "vue";
// import goStore, { oauth2_register_discord, setup_websocket, heron_cookie_login } from "@/netcode";
@@ -12,7 +12,8 @@ const app = getCurrentInstance()?.appContext.app;
if (!app) {
throw new Error("Couldn't bake cards: instance is null");
}
await compileCards(app);
await compileCards();
await compileTokens();
console.log("compiling done");
useHead({
@@ -66,6 +67,25 @@ button:disabled {
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 {
height: 100%;
user-select: none;
+46
View File
@@ -0,0 +1,46 @@
{
"prepare":{
"title":"Prepare",
"description":"Repeat a champion's effects"
},
"stun":{
"title":"Stun",
"description":"Stun (kill) an ennemy's champion"
},
"sacrifice":{
"title": "Sacrifice",
"description": "Destroy a card, forever"
},
"discard":{
"title":"Discard",
"description":"Oops ! You must put a card in your discard..."
},
"make_discard":{
"title": "Make Discard",
"description": "Give a fuck to your opponent"
},
"restack_discarded_champion":{
"title":"Restack Discarded Champion",
"description":"Take a champion from your discard and put it on top of your stack"
},
"restack_discarded_card":{
"title":"Restack Discarded Card",
"description":"Take a card from your discard and put it on top of your stack"
},
"stack_next_action_bought":{
"title":"Stack Next Action Bought",
"description":"When you buy an action, you may put it on top of your stack"
},
"stack_next_card_bought":{
"title":"Stack Next Card Bought",
"description":"When you buy a card, you may put it on top of your stack"
},
"play_next_card_bought":{
"title":"Play Next Card Bought",
"description":"When you buy a card, you may put it in play"
},
"draw_and_discard":{
"title":"Draw And Discard",
"description":"You may draw a card, then discard a card"
}
}
+53 -13
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { Effect } from "~/netcode/interfaces";
import { tokens } from "~/services/loadCards";
import effect_locales from "~/assets/locales/effects.json";
type Props = {
token: Effect;
};
@@ -14,24 +15,63 @@ function onClick(e: MouseEvent) {
</script>
<template>
<div @click.stop="onClick" @mousedown.left.stop="" id="token">
{{ props.token.effect }}
<div @click.stop="onClick" @mousedown.left.stop="" class="token_container">
<Component
:is="tokens[props.token.effect]"
v-if="props.token.effect in tokens"
/>
<span v-else>{{ props.token.effect }}</span>
<div class="token_description" v-if="props.token.effect in effect_locales">
<span
><b>{{
effect_locales[props.token.effect as keyof typeof effect_locales]
.title
}}</b></span
>
<span>{{
effect_locales[props.token.effect as keyof typeof effect_locales]
.description
}}</span>
</div>
</div>
</template>
<style scoped>
#token {
width: 50px;
height: 50px;
background: white;
border: 1px solid white;
<style>
.token_container {
margin: 15px;
/* backdrop-filter: blur(60px); */
background: rgba(201, 212, 38, 1);
border: 5px solid black;
width: min-content;
cursor: pointer;
box-sizing: border-box;
transition: 0.2s all;
border-radius: 15px;
border-radius: 10px;
display: flex;
flex-direction: row;
transition: 0.1s all var(--animation-curve);
position: relative;
z-index: 50;
}
#token:hover {
border: 1px solid red;
.token_container:hover {
border: 5px solid red;
transform: scale(110%);
}
.token_description {
min-width: max-content;
min-height: 45px;
position: absolute;
right: -10px;
transform: translateX(100%);
display: none;
flex-direction: column;
background: rgb(168, 175, 74);
border: 5px solid red;
border-radius: 10px;
}
.token_container:hover .token_description {
display: flex;
}
</style>
+166
View File
@@ -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>
</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
-21
View File
@@ -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 */
+191 -9
View File
@@ -1,15 +1,197 @@
import { playSound } from "~/helpers/audioHelpers";
import type { Container, Game, Player } from "./interfaces";
export function delay(delay: number) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
// 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>>;
settingsStore: ReturnType<typeof useSettingsStore>;
playerList: Ref<Array<Player>>;
refresh: () => Promise<void>;
focusPlayer: (player: Player) => void;
};
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");
},
};
+41
View File
@@ -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) {
const config = useRuntimeConfig();
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) {
const config = useRuntimeConfig();
await $fetch(config.public.endpoint + `/games/${game}/endTurn`, {
+1
View File
@@ -72,6 +72,7 @@ export interface Game extends GameObject {
currentTurn: PlayingTurn;
started: boolean;
owner: User;
packs: string[];
}
export interface Team extends GameObject {
+56 -184
View File
@@ -1,6 +1,15 @@
<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,
surrender,
} from "~/netcode";
import { delay } from "~/netcode/events";
import {
type Container,
@@ -8,7 +17,7 @@ import {
type Game,
type Player,
} from "~/netcode/interfaces";
import { playSound } from "~/helpers/audioHelpers";
import { handlers } from "~/netcode/events";
const config = useRuntimeConfig();
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 () => {
if (game.value == null) {
navigateTo("/lobby");
@@ -250,7 +98,14 @@ onBeforeMount(async () => {
const data = JSON.parse(message.data);
queueAction(async () => {
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 {
console.error(data.type + " not implemented");
}
@@ -260,8 +115,12 @@ onBeforeMount(async () => {
const playerList = computed(
() =>
game.value &&
game.value.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
(game.value &&
game.value.teams.reduce(
(acc, t) => acc.concat(t.players),
[] as Player[]
)) ??
[]
);
const selfPlayer = computed(() =>
authStore.logged
@@ -388,13 +247,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"
@@ -404,7 +263,14 @@ onUnmounted(() => {
<!-- <LoadingScreen v-if="!loaded"></LoadingScreen> -->
<footer>
<GameOverlayOptionsDialog></GameOverlayOptionsDialog>
<div>
<GameModalOptionsDialog></GameModalOptionsDialog>
<GameModalConfigureDialog
v-if="game && game.owner.userId == authStore.selfUser.userId"
:game="game"
></GameModalConfigureDialog>
</div>
<ClientOnly>
<div id="turn_buttons" v-if="game">
<template v-if="game.started">
@@ -419,30 +285,36 @@ 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>
</template>
<button
class="end_turn_button turn_icon"
@click="joinGame(game._id)"
v-else-if="
playerList?.every(
(p) => p.user.userId != authStore.selfUser.userId
)
"
v-else-if="!selfPlayer"
>
Join game
</button>
<button v-else>Leave game</button>
<button v-else @click="leaveGame(game._id)">Leave game</button>
</template>
</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>
<button @click="navigateTo('/lobby')">Return to lobby</button>
</footer>
</div>
</template>
+19 -1
View File
@@ -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) {
+60 -43
View File
@@ -1,40 +1,13 @@
import type { App, Component, DefineComponent } from "vue";
export const cards = {} as Record<string, Component>;
export const tokens = {} as Record<string, Component>;
export async function compileCards(app: App<any>) {
export async function compileCards() {
const svgs = import.meta.glob("@/assets/images/cards/heron-svg/*.svg", {
eager: true,
});
// const components = await Promise.all(
// Object.entries(svgs).map(async ([k, v]) => {
// const regex = /([\d]+)(?=\.svg)/gm;
// const url = (v as any).default as string;
// let template = await (await fetch(url)).text();
// let arrayTemplate = template.split("\n");
// arrayTemplate.splice(0, 1);
// template = arrayTemplate.join("\n");
// return [
// k.match(regex)![0],
// defineComponent({
// props: ["used_effects_indexes", "card"],
// setup(_props) {
// const count = ref(0);
// return { count };
// },
// methods: {
// effectClick(effect: number) {
// this.$emit("effectClick", effect);
// },
// },
// template: template,
// }),
// ];
// })
// );
const components = await Promise.all(
Object.entries(svgs).map(async ([k, v]) => {
const regex = /([\d]+)(?=\.svg)/gm;
@@ -47,23 +20,33 @@ export async function compileCards(app: App<any>) {
template = await (await fetch(url)).text();
}
let arrayTemplate = template.split("\n");
arrayTemplate.splice(0, 1);
template = arrayTemplate.join("\n");
template = template.replace(/\<\?.*\?\>/gm, "");
return [
Number(k.match(regex)![0]).toString(),
// markRaw(
// defineAsyncComponent({
// loader: async () =>
// defineComponent({
// props: ["used_effects_indexes", "cardId"],
// methods: {
// effectClick(effect: number) {
// this.$emit("effectClick", effect);
// },
// },
// template: template,
// }),
// })
// ),
markRaw(
defineAsyncComponent({
loader: async () =>
defineComponent({
props: ["used_effects_indexes", "cardId"],
methods: {
effectClick(effect: number) {
this.$emit("effectClick", effect);
},
},
template: template,
}),
defineComponent({
props: ["used_effects_indexes", "cardId"],
methods: {
effectClick(effect: number) {
this.$emit("effectClick", effect);
},
},
template: template,
})
),
];
@@ -79,3 +62,37 @@ export async function compileCards(app: App<any>) {
Object.assign(cards, Object.fromEntries(components));
}
export async function compileTokens() {
const svgs = import.meta.glob("@/assets/images/tokens/*.svg", {
eager: true,
});
const components = await Promise.all(
Object.entries(svgs).map(async ([k, v]) => {
const regex = /([\w]+)(?=\.svg)/gm;
const url = (v as any).default as string;
try {
let template: string;
if (process.dev && !process.client) {
template = (await import(/* @vite-ignore */ k + "?raw")).default;
} else {
template = await (await fetch(url)).text();
}
template = template.replace(/\<\?.*\?\>/gm, "");
return [
k.match(regex)![0].toString(),
markRaw(
defineComponent({
template: template,
})
),
];
} catch (e) {
console.error(e, k);
return [];
}
})
);
Object.assign(tokens, Object.fromEntries(components));
}