Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3d535e122 | ||
|
|
dd4a938632 | ||
|
|
d8aa87cf97 | ||
|
|
79e28b5006 | ||
|
|
e1784dffca | ||
|
|
39e4855b91 | ||
|
|
c30d23e45f | ||
|
|
c3bb7d555c | ||
|
|
d0ebd3746e | ||
|
|
f0387cebc8 | ||
|
|
a330806afe | ||
|
|
25672130cd | ||
|
|
a6490fc180 | ||
|
|
19bed27f78 | ||
|
|
29cd28953d | ||
|
|
b5d89d4fe9 | ||
|
|
0fb4877266 | ||
|
|
ddcb6c6849 | ||
|
|
afebf1d9de | ||
|
|
4e8a619cb9 | ||
|
|
cb74d78928 | ||
|
|
a3ff9596d7 | ||
|
|
d18a2ab4f2 | ||
|
|
517f1bef5d | ||
|
|
77724d5539 | ||
|
|
21c41bb9fd | ||
|
|
bb625b1fee | ||
|
|
fd3cf0ba23 |
+3
-1
@@ -24,4 +24,6 @@ logs
|
||||
!.env.example
|
||||
|
||||
assets/images/*
|
||||
public/img/skins
|
||||
public/img/skins
|
||||
public/img/icon
|
||||
public/img/tokens
|
||||
+13
-3
@@ -1,14 +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=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
|
||||
@@ -19,4 +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=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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -54,7 +54,7 @@ const shuffleAnimationSpeed = computed(
|
||||
() => settingsStore.animationSpeed * 2 + "ms"
|
||||
);
|
||||
|
||||
function populateContainer() {}
|
||||
function populateContainer() { }
|
||||
|
||||
// const rotate = ref(false);
|
||||
// async function shuffleContainer() {
|
||||
@@ -64,7 +64,7 @@ function populateContainer() {}
|
||||
// await delay(settingsStore.animationSpeed * 2);
|
||||
// }
|
||||
|
||||
function getCardsPosition(cards: string) {}
|
||||
function getCardsPosition(cards: string) { }
|
||||
|
||||
const animationCallbacks = ref<Array<() => void>>([]);
|
||||
|
||||
@@ -132,41 +132,23 @@ function discardCardClick(card: Card) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="cards_container"
|
||||
:class="{ wrapped, rotate: container.shuffling }"
|
||||
>
|
||||
<div class="cards_container" :class="{ wrapped, rotate: container.shuffling }">
|
||||
<TransitionGroup name="list">
|
||||
<GameCard
|
||||
@click="$emit('cardClick', c._id)"
|
||||
@effectClick="$emit('effectClick', c._id, $event)"
|
||||
v-for="(c, i) in cards"
|
||||
:cardId="c?.cardId"
|
||||
:uuid="c._id"
|
||||
:wrapped="wrapped"
|
||||
:key="c?._id || i"
|
||||
:locked="
|
||||
locked ||
|
||||
<GameCard @click="$emit('cardClick', c._id)" @effectClick="$emit('effectClick', c._id, $event)"
|
||||
v-for="(c, i) in cards" :cardId="c?.cardId" :uuid="c._id" :wrapped="wrapped" :key="c?._id || i" :locked="locked ||
|
||||
!playingTurn ||
|
||||
playingTurn?.cardsLocked.includes(c._id.toString())
|
||||
"
|
||||
:used_effects_indexes="c?.effects?.reduce( (prev,e, i)=>{
|
||||
if(playingTurn?.lockedEffects.includes(e._id)){
|
||||
prev.push(i)
|
||||
}
|
||||
return prev
|
||||
},[] as number[])"
|
||||
:noEffects="noEffects"
|
||||
:discardable="discardable"
|
||||
>
|
||||
" :used_effects_indexes="c?.effects?.reduce((prev, e, i) => {
|
||||
if (playingTurn?.lockedEffects.includes(e._id)) {
|
||||
prev.push(i)
|
||||
}
|
||||
return prev
|
||||
}, [] as number[])" :noEffects="noEffects" :discardable="discardable">
|
||||
<template v-for="t in tokens">
|
||||
<GameToken
|
||||
:token="t"
|
||||
@useToken="tokenClick(t, c)"
|
||||
v-if="
|
||||
(t.effect == CardEffects.SACRIFICE &&
|
||||
playingTurn &&
|
||||
!playingTurn?.cardsLocked.includes(c._id.toString())) ||
|
||||
<GameToken :token="t" @useToken="tokenClick(t, c)" v-if="
|
||||
((t.effect == CardEffects.SACRIFICE &&
|
||||
playingTurn &&
|
||||
!playingTurn?.cardsLocked.includes(c._id.toString())) ||
|
||||
(t.effect == CardEffects.PREPARE &&
|
||||
c.cardType == CardType.CHAMPION) ||
|
||||
(t.effect == CardEffects.STACK_NEXT_ACTION_BOUGHT &&
|
||||
@@ -180,24 +162,16 @@ function discardCardClick(card: Card) {
|
||||
(t.effect == CardEffects.RESTACK_DISCARDED_ACTION &&
|
||||
c.cardType == CardType.ACTION) ||
|
||||
(t.effect == CardEffects.RESTACK_DISCARDED_CHAMPION &&
|
||||
c.cardType == CardType.CHAMPION)
|
||||
"
|
||||
></GameToken>
|
||||
c.cardType == CardType.CHAMPION)) && ((wrapped && i == (cards.length - 1)) || !wrapped)
|
||||
"></GameToken>
|
||||
</template>
|
||||
<button
|
||||
v-if="championsKillable && c.cardType == CardType.CHAMPION"
|
||||
@click.stop="damageChampionClick(c)"
|
||||
@mousedown.left.stop=""
|
||||
>
|
||||
<button v-if="championsKillable && c.cardType == CardType.CHAMPION" @click.stop="damageChampionClick(c)"
|
||||
@mousedown.left.stop="">
|
||||
Damage
|
||||
</button>
|
||||
<button
|
||||
v-if="
|
||||
discardable && !playingTurn?.cardsLocked.includes(c._id.toString())
|
||||
"
|
||||
@mousedown.left.stop=""
|
||||
@click.stop="discardCardClick(c)"
|
||||
>
|
||||
<button v-if="
|
||||
discardable && !playingTurn?.cardsLocked.includes(c._id.toString())
|
||||
" @mousedown.left.stop="" @click.stop="discardCardClick(c)">
|
||||
Discard
|
||||
</button>
|
||||
</GameCard>
|
||||
@@ -255,7 +229,7 @@ function discardCardClick(card: Card) {
|
||||
max-height: 10px;
|
||||
}
|
||||
|
||||
.empty > .card_image {
|
||||
.empty>.card_image {
|
||||
filter: brightness(0.5);
|
||||
max-height: 250px;
|
||||
}
|
||||
@@ -269,6 +243,7 @@ use * use[data-effect-id="0"] {
|
||||
.cards_container .card #background rect:first-child {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.cards_container .card.wrapped #background rect:first-child {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -20,15 +20,22 @@ const props = defineProps<Props>();
|
||||
|
||||
const isVisible = ref(false);
|
||||
|
||||
const discardTokens = computed(() =>
|
||||
props.game.currentTurn.tokens.filter((t) =>
|
||||
[
|
||||
CardEffects.RESTACK_DISCARDED_ACTION,
|
||||
CardEffects.RESTACK_DISCARDED_CARD,
|
||||
CardEffects.RESTACK_DISCARDED_CHAMPION,
|
||||
CardEffects.SACRIFICE,
|
||||
].includes(t.effect)
|
||||
)
|
||||
const discardTokens = computed(() => {
|
||||
// Only check for tokens if it's current player's turn
|
||||
const team = props.game.teams.find((t) => t._id == props.game.currentTurn.currentTeam);
|
||||
if (team?.players.some(p => p._id == props.player._id)) {
|
||||
return props.game.currentTurn.tokens.filter((t) =>
|
||||
[
|
||||
CardEffects.RESTACK_DISCARDED_ACTION,
|
||||
CardEffects.RESTACK_DISCARDED_CARD,
|
||||
CardEffects.RESTACK_DISCARDED_CHAMPION,
|
||||
CardEffects.SACRIFICE,
|
||||
].includes(t.effect)
|
||||
)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
function tokenClick({ card, token }: { card: Card; token: Effect }) {
|
||||
@@ -45,43 +52,21 @@ function openDiscard() {
|
||||
|
||||
<template>
|
||||
<div id="discard">
|
||||
<GameCardGrouped
|
||||
@cardClick="openDiscard"
|
||||
:container="player.discard"
|
||||
:wrapped="true"
|
||||
:no-effects="true"
|
||||
>
|
||||
<GameCardGrouped @cardClick="openDiscard" :container="player.discard" :wrapped="true" :no-effects="true">
|
||||
</GameCardGrouped>
|
||||
</div>
|
||||
<div
|
||||
class="discard-animations discard-background"
|
||||
:class="{ visible: isVisible, fullscreen: settingsStore.discardFullscreen }"
|
||||
@click.stop="isVisible = false"
|
||||
></div>
|
||||
<div
|
||||
class="discard-animations discard-menu"
|
||||
:class="{ visible: isVisible, fullscreen: settingsStore.discardFullscreen }"
|
||||
@click.stop="isVisible = false"
|
||||
>
|
||||
<div class="discard-animations discard-background"
|
||||
:class="{ visible: isVisible, fullscreen: settingsStore.discardFullscreen }" @click.stop="isVisible = false"></div>
|
||||
<div class="discard-animations discard-menu"
|
||||
:class="{ visible: isVisible, fullscreen: settingsStore.discardFullscreen }" @click.stop="isVisible = false">
|
||||
<span>Discard of {{ player.user.username }}</span>
|
||||
<button
|
||||
class="discard-fullscreen-btn"
|
||||
@click.stop="
|
||||
settingsStore.discardFullscreen = !settingsStore.discardFullscreen
|
||||
"
|
||||
>
|
||||
<button class="discard-fullscreen-btn" @click.stop="
|
||||
settingsStore.discardFullscreen = !settingsStore.discardFullscreen
|
||||
">
|
||||
TOGGLE FULLSCREEN
|
||||
</button>
|
||||
<GameCardGrouped
|
||||
:container="player.discard"
|
||||
:wrapped="false"
|
||||
:hidden="false"
|
||||
:tokens="discardTokens"
|
||||
:noEffects="true"
|
||||
:playingTurn="game.currentTurn"
|
||||
:locked="true"
|
||||
@tokenClick="tokenClick"
|
||||
></GameCardGrouped>
|
||||
<GameCardGrouped :container="player.discard" :wrapped="false" :hidden="false" :tokens="discardTokens"
|
||||
:noEffects="true" :playingTurn="game.currentTurn" :locked="true" @tokenClick="tokenClick"></GameCardGrouped>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -89,6 +74,7 @@ function openDiscard() {
|
||||
span {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.discard-fullscreen-btn {
|
||||
pointer-events: all;
|
||||
}
|
||||
@@ -96,6 +82,7 @@ span {
|
||||
.discard-animations {
|
||||
transition: 0.3s all var(--animation-curve);
|
||||
}
|
||||
|
||||
.discard-background {
|
||||
transition: 0.3s all linear;
|
||||
position: fixed;
|
||||
@@ -145,7 +132,7 @@ span {
|
||||
left: 0%;
|
||||
}
|
||||
|
||||
.discard-menu > .cards_container {
|
||||
.discard-menu>.cards_container {
|
||||
pointer-events: all;
|
||||
overflow: auto;
|
||||
padding: 30px;
|
||||
@@ -154,7 +141,7 @@ span {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.discard-menu.fullscreen > .cards_container {
|
||||
.discard-menu.fullscreen>.cards_container {
|
||||
max-width: 50%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -52,12 +52,19 @@ const isSelf = computed(
|
||||
);
|
||||
const isSelfTurn = computed(() => isSelf && isPlayerTurn);
|
||||
|
||||
const cardTokens = computed(() =>
|
||||
props.game.currentTurn.tokens.filter(
|
||||
(t) =>
|
||||
[CardEffects.PREPARE, CardEffects.STUN].includes(t.effect) ||
|
||||
(CardEffects.SACRIFICE == t.effect && isSelf)
|
||||
)
|
||||
const cardTokens = computed(() => {
|
||||
if (!isPlayerTurn.value) {
|
||||
return props.game.currentTurn.tokens.filter((t) => t.effect == CardEffects.STUN);
|
||||
}
|
||||
else {
|
||||
return props.game.currentTurn.tokens.filter(
|
||||
(t) =>
|
||||
[CardEffects.PREPARE, CardEffects.SACRIFICE].includes(t.effect)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
const warningText = ref("Effets restants!");
|
||||
@@ -171,21 +178,10 @@ function discardCardClick(card: Card) {
|
||||
</div> -->
|
||||
<!-- <br /> -->
|
||||
|
||||
<GameCardGrouped
|
||||
:container="player.board"
|
||||
:wrapped="false"
|
||||
:playingTurn="props.game.currentTurn"
|
||||
@cardClick="boardCardClick"
|
||||
@effectClick="effectClick"
|
||||
:tokens="cardTokens"
|
||||
@tokenClick="tokenClick"
|
||||
:championsKillable="!isSelf && isSelfTurn.value"
|
||||
:discardable="
|
||||
(player.fuckUMarkers > 0 || player.discardMarkers > 0) && isSelf
|
||||
"
|
||||
@damageChampionClick="damageChampionClick"
|
||||
@discardCardClick="discardCardClick"
|
||||
>
|
||||
<GameCardGrouped :container="player.board" :wrapped="false" :playingTurn="props.game.currentTurn"
|
||||
@cardClick="boardCardClick" @effectClick="effectClick" :tokens="cardTokens" @tokenClick="tokenClick"
|
||||
:championsKillable="!isSelf && isSelfTurn.value" :discardable="(player.fuckUMarkers > 0 || player.discardMarkers > 0) && isSelf
|
||||
" @damageChampionClick="damageChampionClick" @discardCardClick="discardCardClick">
|
||||
</GameCardGrouped>
|
||||
<br />
|
||||
<!-- <div class="cards_container"> -->
|
||||
@@ -433,27 +429,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 */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useAuthStore } from "#imports";
|
||||
|
||||
const validPaths = ["/login", "/lobby", "/rules", "/test"];
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
const authStore = useAuthStore();
|
||||
const config = useRuntimeConfig();
|
||||
@@ -37,17 +38,13 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
if (to.path.startsWith("/game")) {
|
||||
return;
|
||||
}
|
||||
if (to.path == "/login") {
|
||||
if (to.path == "/logout") {
|
||||
// const session = useCookie("heron.session");
|
||||
// session.value = null;
|
||||
// return navigateTo("/lobby");
|
||||
return;
|
||||
}
|
||||
if (to.path == "/lobby") {
|
||||
return;
|
||||
}
|
||||
if (to.path == "/rules") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (to.path == "/test") {
|
||||
if (validPaths.includes(to.path)) {
|
||||
return;
|
||||
}
|
||||
// In a real app you would probably not redirect every route to `/`
|
||||
|
||||
+196
-9
@@ -1,15 +1,202 @@
|
||||
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 (data: any, { game }) => {
|
||||
const winningPlayers = game.teams
|
||||
.find((t) => t._id == data.winner)
|
||||
?.players.map((p) => p.user.username)
|
||||
.join(", ");
|
||||
alert(`game ended. Winner : ${winningPlayers}`);
|
||||
},
|
||||
delete: async () => {
|
||||
alert("game deleted");
|
||||
navigateTo("/lobby");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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`, {
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface Game extends GameObject {
|
||||
currentTurn: PlayingTurn;
|
||||
started: boolean;
|
||||
owner: User;
|
||||
packs: string[];
|
||||
}
|
||||
|
||||
export interface Team extends GameObject {
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ server {
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
index lobby/index.html;
|
||||
try_files $uri $uri/ /lobby/index.html =404;
|
||||
}
|
||||
|
||||
error_page 404 /usr/share/nginx/html/404.html;
|
||||
|
||||
Generated
+4284
-4721
File diff suppressed because it is too large
Load Diff
+6
-4
@@ -4,8 +4,10 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"cards-gen": "sh -c 'docker run --rm --pull=always -v ./assets/images/cards/heron-svg:/app/output/heron git.legonzaur.fr/heronfief/cards-gen:main' && sudo chown -R $USER:$USER ./assets/images/cards/heron-svg",
|
||||
"pull-images": "sh -c 'docker run --rm --pull=always -v ./public/img:/repo/images/skins git.legonzaur.fr/heronfief/cards-gen:main cp -r img/skins/ /repo/images/skins' && sudo chown -R $USER:$USER ./public/",
|
||||
"fetch": "npm run cards-gen && npm run pull-images",
|
||||
"tokens-gen": "sh -c 'docker run --rm --pull=always -v ./assets/images/tokens:/app/output/heron git.legonzaur.fr/heronfief/tokens-gen:main' && sudo chown -R $USER:$USER ./assets/images/tokens",
|
||||
"pull-token-images": "sh -c 'docker run --rm --pull=always -v ./public/img:/repo/images/tokens git.legonzaur.fr/heronfief/tokens-gen:main cp -r img/icon/ /repo/images/tokens' && sudo chown -R $USER:$USER ./public/",
|
||||
"pull-card-images": "sh -c 'docker run --rm --pull=always -v ./public/img:/repo/images/skins git.legonzaur.fr/heronfief/cards-gen:main cp -r img/skins/ /repo/images/skins' && sudo chown -R $USER:$USER ./public/",
|
||||
"fetch": "npm run cards-gen && npm run pull-card-images && npm run tokens-gen && npm run pull-token-images",
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
@@ -15,12 +17,12 @@
|
||||
"dependencies": {
|
||||
"@pinia/nuxt": "^0.5.1",
|
||||
"gsap": "^3.12.5",
|
||||
"nuxt": "^3.11.2",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pinia-plugin-persistedstate/nuxt": "^1.2.0"
|
||||
"@pinia-plugin-persistedstate/nuxt": "^1.2.0",
|
||||
"nuxt": "^3.14.1592"
|
||||
}
|
||||
}
|
||||
|
||||
+67
-247
@@ -1,14 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import LoadingScreen from "~/components/game/overlay/LoadingScreen.vue";
|
||||
import { endTurn, subscribe, useToken, startGame, joinGame } from "~/netcode";
|
||||
import { delay } from "~/netcode/events";
|
||||
import {
|
||||
endTurn,
|
||||
subscribe,
|
||||
useToken,
|
||||
startGame,
|
||||
joinGame,
|
||||
deleteGame,
|
||||
leaveGame,
|
||||
surrender,
|
||||
} from "~/netcode";
|
||||
import {
|
||||
type Container,
|
||||
type Effect,
|
||||
type Game,
|
||||
type Player,
|
||||
} from "~/netcode/interfaces";
|
||||
import { playSound } from "~/helpers/audioHelpers";
|
||||
import { handlers } from "~/netcode/events";
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
const settingsStore = useSettingsStore();
|
||||
@@ -19,7 +27,6 @@ const gameId = route.query.id;
|
||||
|
||||
const {
|
||||
data: game,
|
||||
pending,
|
||||
error,
|
||||
refresh,
|
||||
execute,
|
||||
@@ -67,167 +74,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 +96,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 +113,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
|
||||
@@ -325,124 +182,86 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<div @contextmenu.prevent="false" id="game_board">
|
||||
<AnimatedBackground
|
||||
@ready="loaded = true"
|
||||
:isTurn="isTurn ?? false"
|
||||
></AnimatedBackground>
|
||||
<AnimatedBackground @ready="loaded = true" :isTurn="isTurn ?? false"></AnimatedBackground>
|
||||
<ClientOnly>
|
||||
<template v-if="game && focusedPlayer && playerList">
|
||||
<!-- <Transition name="slide-left" mode="out-in"> -->
|
||||
<div id="stack_discard" :key="focusedPlayer?._id">
|
||||
<div id="stack">
|
||||
<GameCardGrouped
|
||||
:container="focusedPlayer.stack"
|
||||
:wrapped="true"
|
||||
:hidden="true"
|
||||
>
|
||||
<GameCardGrouped :container="focusedPlayer.stack" :wrapped="true" :hidden="true">
|
||||
</GameCardGrouped>
|
||||
</div>
|
||||
|
||||
<GameOverlayDiscard
|
||||
:game="game"
|
||||
:player="focusedPlayer"
|
||||
></GameOverlayDiscard>
|
||||
<GameOverlayDiscard :game="game" :player="focusedPlayer"></GameOverlayDiscard>
|
||||
</div>
|
||||
<!-- </Transition> -->
|
||||
|
||||
<GameMarket
|
||||
:game="game"
|
||||
:market="game.market"
|
||||
:fire_gems="game.fireGems"
|
||||
:marketStack="game.marketStack"
|
||||
>
|
||||
<GameMarket :game="game" :market="game.market" :fire_gems="game.fireGems" :marketStack="game.marketStack">
|
||||
</GameMarket>
|
||||
<div>
|
||||
<GameToken
|
||||
:token="t"
|
||||
v-for="t in game.currentTurn.tokens"
|
||||
@useToken="useTokenClick"
|
||||
></GameToken>
|
||||
<GameToken :token="t" v-for="t in game.currentTurn.tokens" @useToken="useTokenClick"></GameToken>
|
||||
</div>
|
||||
<div id="player_list_container">
|
||||
<div id="player_list">
|
||||
<GamePlayerListElement
|
||||
v-for="p in playerList"
|
||||
:player="p"
|
||||
:game="game"
|
||||
@click="focusPlayer(p)"
|
||||
:focused="focusedPlayer == p"
|
||||
:is-self-turn="isTurn"
|
||||
:self="selfPlayer == p"
|
||||
>
|
||||
<GamePlayerListElement v-for="p in playerList" :player="p" :game="game" @click="focusPlayer(p)"
|
||||
:focused="focusedPlayer == p" :is-self-turn="isTurn" :self="selfPlayer == p">
|
||||
</GamePlayerListElement>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="current_player">
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<GamePlayerSlot
|
||||
v-if="focusedPlayer"
|
||||
:player="focusedPlayer"
|
||||
:game="game"
|
||||
:key="focusedPlayer._id"
|
||||
></GamePlayerSlot>
|
||||
<GamePlayerSlot v-if="focusedPlayer" :player="focusedPlayer" :game="game" :key="focusedPlayer._id">
|
||||
</GamePlayerSlot>
|
||||
</Transition>
|
||||
</div>
|
||||
<GameOverlayResultDialog
|
||||
:isVisible="showResults"
|
||||
:won="isWinner"
|
||||
:players="playerList"
|
||||
@close="showResults = false"
|
||||
>
|
||||
</GameOverlayResultDialog>
|
||||
<GamePlayerSelf
|
||||
v-if="selfPlayer"
|
||||
:container="selfPlayer.hand"
|
||||
></GamePlayerSelf>
|
||||
<GameModalResultDialog :isVisible="showResults" :won="isWinner" :players="playerList"
|
||||
@close="showResults = false">
|
||||
</GameModalResultDialog>
|
||||
<GamePlayerSelf v-if="selfPlayer" :container="selfPlayer.hand"></GamePlayerSelf>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
|
||||
<!-- <LoadingScreen v-if="!loaded"></LoadingScreen> -->
|
||||
<footer>
|
||||
<GameOverlayOptionsDialog></GameOverlayOptionsDialog>
|
||||
<ClientOnly>
|
||||
<div id="turn_buttons" v-if="game">
|
||||
<div>
|
||||
<GameModalOptionsDialog></GameModalOptionsDialog>
|
||||
<ClientOnly>
|
||||
<GameModalConfigureDialog v-if="game && game.owner.userId == authStore.selfUser.userId" :game="game">
|
||||
</GameModalConfigureDialog>
|
||||
</ClientOnly>
|
||||
|
||||
</div>
|
||||
|
||||
<ClientOnly v-if="game">
|
||||
<div id="turn_buttons">
|
||||
<template v-if="game.started">
|
||||
<button
|
||||
id="end_turn"
|
||||
class="end_turn_button turn_icon"
|
||||
:class="{ hidden: !isTurn }"
|
||||
@click="endTurn(game._id)"
|
||||
v-if="isTurn"
|
||||
>
|
||||
<button id="end_turn" class="end_turn_button turn_icon" :class="{ hidden: !isTurn }"
|
||||
@click="endTurn(game._id)" v-if="isTurn">
|
||||
END TURN
|
||||
</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>
|
||||
<button
|
||||
class="end_turn_button turn_icon"
|
||||
@click="joinGame(game._id)"
|
||||
v-else-if="
|
||||
playerList?.every(
|
||||
(p) => p.user.userId != authStore.selfUser.userId
|
||||
)
|
||||
"
|
||||
>
|
||||
<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="!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.started && selfPlayer && game.owner._id != selfPlayer.user._id" 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>
|
||||
@@ -457,6 +276,7 @@ footer {
|
||||
/* background: red; */
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#game_status {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
|
||||
+24
-2
@@ -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) {
|
||||
@@ -56,10 +74,14 @@ onUnmounted(() => {
|
||||
<div class="background">
|
||||
<header>
|
||||
<ClientOnly>
|
||||
<template v-if="authStore.logged">
|
||||
<GamePlayerIcon
|
||||
v-if="authStore.logged"
|
||||
|
||||
:user="(authStore.selfUser as User)"
|
||||
/>
|
||||
<button @click="navigateTo('/logout')">Logout</button>
|
||||
</template>
|
||||
|
||||
<button v-else @click="navigateTo('/login')">Login</button>
|
||||
</ClientOnly>
|
||||
</header>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const code = route.query.code;
|
||||
|
||||
navigateTo(config.public.endpoint + "/logout", { external: true });
|
||||
</script>
|
||||
+61
-44
@@ -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>) {
|
||||
const svgs = import.meta.glob("@/assets/images/cards/heron-svg/*.svg", {
|
||||
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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user