Compare commits

...
Author SHA1 Message Date
legonzaur b3d535e122 attempt to fix index html
release-tag / release-image (push) Successful in 28s
2024-12-21 19:25:06 +01:00
legonzaur dd4a938632 fix :attemt to fix visuals
release-tag / release-image (push) Successful in 41s
2024-12-21 19:22:35 +01:00
legonzaur d8aa87cf97 chore : add token gen script for local dev 2024-12-21 19:12:52 +01:00
legonzaur 79e28b5006 fix: some token display errors
release-tag / release-image (push) Successful in 30s
2024-12-21 19:03:25 +01:00
legonzaur e1784dffca fix : render game configuration button only on client
release-tag / release-image (push) Successful in 47s
2024-12-21 14:56:49 +01:00
legonzaur 39e4855b91 fix : tokens incorrectly showing on opponent's board
Closes #107
2024-12-21 11:32:41 +01:00
legonzaur c30d23e45f feat : show winning players on game end
related to #71
2024-12-21 11:23:18 +01:00
legonzaur c3bb7d555c chore: formatting 2024-12-21 11:21:57 +01:00
legonzaur d0ebd3746e fix : sacrifice tokens on opponent discard 2024-12-21 11:21:46 +01:00
legonzaur f0387cebc8 chore: update nuxt 2024-12-21 11:21:34 +01:00
legonzaur a330806afe feat: redirect to lobby when game ends 2024-12-21 00:12:40 +01:00
legonzaur 25672130cd feat: logout button 2024-12-20 21:56:37 +01:00
12 changed files with 4417 additions and 4932 deletions
+24 -49
View File
@@ -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;
}
+30 -43
View File
@@ -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>
+17 -21
View File
@@ -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"> -->
+6 -9
View File
@@ -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 `/`
+7 -2
View File
@@ -188,10 +188,15 @@ export const handlers: {
consumeEffect: async (data: any) => {
console.warn("consumeEffect not implemented");
},
endGame: async () => {
alert("game ended");
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");
},
};
+2 -2
View File
@@ -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;
+4284 -4721
View File
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -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"
}
}
+26 -78
View File
@@ -10,7 +10,6 @@ import {
leaveGame,
surrender,
} from "~/netcode";
import { delay } from "~/netcode/events";
import {
type Container,
type Effect,
@@ -28,7 +27,6 @@ const gameId = route.query.id;
const {
data: game,
pending,
error,
refresh,
execute,
@@ -184,80 +182,43 @@ 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>
<GameModalResultDialog
:isVisible="showResults"
:won="isWinner"
:players="playerList"
@close="showResults = false"
>
<GameModalResultDialog :isVisible="showResults" :won="isWinner" :players="playerList"
@close="showResults = false">
</GameModalResultDialog>
<GamePlayerSelf
v-if="selfPlayer"
:container="selfPlayer.hand"
></GamePlayerSelf>
<GamePlayerSelf v-if="selfPlayer" :container="selfPlayer.hand"></GamePlayerSelf>
</template>
</ClientOnly>
@@ -265,51 +226,37 @@ onUnmounted(() => {
<footer>
<div>
<GameModalOptionsDialog></GameModalOptionsDialog>
<GameModalConfigureDialog
v-if="game && game.owner.userId == authStore.selfUser.userId"
:game="game"
></GameModalConfigureDialog>
<ClientOnly>
<GameModalConfigureDialog v-if="game && game.owner.userId == authStore.selfUser.userId" :game="game">
</GameModalConfigureDialog>
</ClientOnly>
</div>
<ClientOnly>
<div id="turn_buttons" v-if="game">
<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">
<template v-if="game.owner.userId == authStore.selfUser.userId">
<button
class="end_turn_button turn_icon"
@click="startGame(game._id)"
>
<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"
>
<button class="end_turn_button turn_icon" @click="joinGame(game._id)" v-else-if="!selfPlayer">
Join 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)"
>
<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>
@@ -329,6 +276,7 @@ footer {
/* background: red; */
justify-content: space-between;
}
#game_status {
position: fixed;
z-index: 10;
+5 -1
View File
@@ -74,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>
+8
View File
@@ -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>
+2 -2
View File
@@ -4,7 +4,7 @@ export const cards = {} as Record<string, Component>;
export const tokens = {} as Record<string, Component>;
export async function compileCards() {
const svgs = import.meta.glob("@/assets/images/cards/heron-svg/*.svg", {
const svgs = import.meta.glob("./assets/images/cards/heron-svg/*.svg", {
eager: true,
});
@@ -64,7 +64,7 @@ export async function compileCards() {
}
export async function compileTokens() {
const svgs = import.meta.glob("@/assets/images/tokens/*.svg", {
const svgs = import.meta.glob("./assets/images/tokens/*.svg", {
eager: true,
});