Compare commits

..
Author SHA1 Message Date
legonzaur 08fb23e729 Fix : prevent fetching back data on build
release-tag / release-image (push) Successful in 29s
2024-08-02 09:08:59 +02:00
legonzaur 2876f7b91e Feat: better discard overlay
release-tag / release-image (push) Successful in 28s
2024-08-01 22:53:30 +02:00
legonzaur 775712371d Merge remote-tracking branch 'origin/main' into feat/netcode-rework 2024-08-01 20:08:32 +02:00
legonzaur 6edadc3ef3 Feat : add return to lobby button 2024-08-01 20:06:34 +02:00
legonzaur 7713faa4d0 Feat : add login button 2024-08-01 19:48:15 +02:00
legonzaur f9197d470e Feat : settings dialog animation 2024-08-01 18:28:27 +02:00
legonzaur de38416bcd Chore : cleanup 2 2024-08-01 17:38:01 +02:00
legonzaur 23e5f9f432 Chore : components cleanup 2024-08-01 17:31:09 +02:00
legonzaur 240ab7e1d4 Fix : add login page 2024-08-01 15:54:39 +02:00
legonzaur 80f6d02fc7 Update .gitea/workflows/build.yml 2024-08-01 10:44:09 +00:00
legonzaur c68446bf25 Ci : was it this simple
release-tag / release-image (push) Successful in 31s
2024-08-01 12:43:20 +02:00
legonzaur 63519429e6 CI : fuckit manual test
release-tag / release-image (push) Successful in 30s
2024-08-01 12:33:52 +02:00
legonzaur 5282ca3c4a Ci : update build yml
release-tag / release-image (push) Successful in 30s
2024-08-01 12:31:19 +02:00
legonzaur f5e4b10d1b Ci : fix build
release-tag / release-image (push) Successful in 31s
2024-08-01 12:23:25 +02:00
legonzaur efebc79d35 Ci : dockerfile ARG NUXT_PUBLIC_WELCOME_MESSAGE
release-tag / release-image (push) Successful in 32s
2024-08-01 12:19:43 +02:00
legonzaur ad324a9a97 CI : switch to args instead of secrets
release-tag / release-image (push) Successful in 28s
2024-08-01 12:06:52 +02:00
legonzaur 4145cacf1a Chore : add test page 2024-08-01 12:05:27 +02:00
legonzaur da54495d1c Ci : add more tags 2024-08-01 08:12:50 +00:00
legonzaur f60ec850f0 Ci : fix repo name in tag 2024-08-01 08:12:33 +00:00
39 changed files with 1339 additions and 1325 deletions
+7 -12
View File
@@ -10,8 +10,7 @@ jobs:
runs-on: ubuntu-latest
env:
DOCKER_ORG: heronfief
REPO_NAME: front
REPO_NAME: ${{ gitea.repository }}
ENDPOINT: git.legonzaur.fr
steps:
@@ -21,12 +20,6 @@ jobs:
lfs: false
persist-credentials: false
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: setup private key
shell: bash
run: |
@@ -38,7 +31,7 @@ jobs:
shell: bash
run: |
ssh-keyscan ${{ env.ENDPOINT }} >> ~/.ssh/known_hosts
git remote set-url origin git@${{ env.ENDPOINT }}:${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}.git
git remote set-url origin git@${{ env.ENDPOINT }}:${{ env.REPO_NAME }}.git
git lfs pull
- name: Login to DockerHub
@@ -52,7 +45,7 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.ENDPOINT }}/${{ env.DOCKER_ORG }}/${{ env.REPO_NAME }}
images: ${{ env.ENDPOINT }}/${{ env.REPO_NAME }}
tags: |
type=schedule
type=ref,event=branch
@@ -66,8 +59,10 @@ jobs:
with:
context: .
file: ./Dockerfile
secrets: |
"NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT=${{ secrets.NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT }}"
build-args: |
NUXT_PUBLIC_ENDPOINT=${{ vars.NUXT_PUBLIC_ENDPOINT }}
NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT=${{ vars.NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT }}
NUXT_PUBLIC_WELCOME_MESSAGE=${{ vars.NUXT_PUBLIC_WELCOME_MESSAGE }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+34
View File
@@ -0,0 +1,34 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "client: chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
},
{
"type": "node",
"request": "launch",
"name": "server: nuxt",
"outputCapture": "std",
"program": "${workspaceFolder}/node_modules/nuxi/bin/nuxi.mjs",
"args": [
"dev"
],
}
],
"compounds": [
{
"name": "fullstack: nuxt",
"configurations": [
"server: nuxt",
"client: chrome"
]
}
]
}
+2 -1
View File
@@ -4,5 +4,6 @@
"**/node_modules": true
},
"vue.server.hybridMode": true,
"editor.formatOnSave": true
"editor.formatOnSave": true,
"javascript.updateImportsOnFileMove.enabled": "never"
}
+7 -7
View File
@@ -1,15 +1,15 @@
FROM node:lts-alpine as build-stage
FROM node:lts-alpine AS build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN --mount=type=secret,id=NUXT_PUBLIC_WEBSOCKET_ENDPOINT \
--mount=type=secret,id=NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT \
NUXT_PUBLIC_WEBSOCKET_ENDPOINT=$(cat /run/secrets/NUXT_PUBLIC_WEBSOCKET_ENDPOINT ) \
NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT=$(cat /run/secrets/NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT ) \
npx nuxi generate
ARG NUXT_PUBLIC_ENDPOINT
ARG NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT
ARG NUXT_PUBLIC_WELCOME_MESSAGE
RUN npx nuxi generate
# étape de production
FROM nginx:stable-alpine as production-stage
FROM nginx:stable-alpine AS production-stage
COPY --from=build-stage /app/.output/public /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
+23 -5
View File
@@ -5,7 +5,6 @@ import { getCurrentInstance } from "vue";
const nuxtApp = useNuxtApp();
const router = useRouter();
const authStore = useAuthStore();
console.log("compiling cards...");
@@ -14,16 +13,14 @@ if (!app) {
throw new Error("Couldn't bake cards: instance is null");
}
await compileCards(app);
const _cards = cards;
console.log("compiling done");
useHead({
title: "Héron Realms",
});
onMounted(()=>{
onMounted(() => {
authStore.whoami();
})
});
</script>
<template>
<div class="root_dom_div">
@@ -35,6 +32,9 @@ onMounted(()=>{
</template>
<style>
:root {
--animation-curve: cubic-bezier(0.68, -0.275, 0.32, 1.275);
}
html {
text-align: center;
}
@@ -44,6 +44,24 @@ body {
height: 100vh;
}
button {
background-color: #4caf50;
border: none;
color: white;
text-align: center;
text-decoration: none;
font-family: "Roboto", sans-serif;
font-size: 16px;
width: max-content;
cursor: pointer;
border-radius: 12px;
transition-duration: 0.4s;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
padding: 10px 24px;
height: 50px;
}
#__nuxt {
height: 100%;
user-select: none;
-1
View File
@@ -201,7 +201,6 @@ self.addEventListener("message", (e) => {
screen.height = data.screen.height;
loadShader();
} else if (data.command == "killShader") {
console.log("killShader");
self.clearInterval(interval);
self.clearTimeout(timeout);
} else if (data.command == "resumeShader") {
-23
View File
@@ -1,23 +0,0 @@
<script lang="ts">
import type { Card } from "~/netcode/interfaces";
import { cards } from "~/services/loadCards";
export default {
props: ["card", "used_effects_indexes"],
data() {
const _card = this.card as Card;
return { cards };
},
beforeCreate() {
(this as any).component = () => cards[this.card.cardId];
},
};
</script>
<template>
<Component
:is="cards[card.cardId]"
:used_effects_indexes="used_effects_indexes"
v-if="card"
/>
</template>
-51
View File
@@ -1,51 +0,0 @@
<script setup lang="ts">
import goStore from '~/netcode';
export interface Props {
card_id: number | undefined
}
const settingsStore = useSettingsStore()
const props = defineProps<Props>()
const card: Ref<Element | null> = ref(null)
const x = computed(() => {
const bounds = card.value?.getBoundingClientRect()
let offset = 1
if (!bounds) { return 0 }
if ((bounds.width + goStore.clientMouseX) > (window.visualViewport?.width ?? Infinity)) {
offset = - (bounds.width + 1)
}
return goStore.clientMouseX + offset
})
const y = computed(() => {
const bounds = card.value?.getBoundingClientRect()
let offset = 1
if (!bounds) { return 0 }
if ((bounds.height + goStore.clientMouseY) > (window.visualViewport?.height ?? Infinity)) {
offset = - (bounds.height + 1)
}
return goStore.clientMouseY + offset
})
</script>
<template>
<div :class="`card_overlay`" :style="`top: ${y}px; left: ${x}px`" ref="card">
<img :class="`card_image`" v-if="props.card_id"
:src="`/cards/${settingsStore.cardStyle}/` + props.card_id.toString().padStart(3, '0') + '.png'"
draggable="false">
<!-- <img :class="`card_image`" v-if="!props.card_id" :src="'/cards/background.png'" draggable="false"> -->
</div>
</template>
<style scoped>
.card_overlay {
/* display: none; */
position: fixed;
z-index: 999;
display: flex;
pointer-events: none;
}
</style>
-410
View File
@@ -1,410 +0,0 @@
<script setup lang="ts">
import type { Card } from "~/netcode/interfaces";
export interface Props {
card?: Card;
card_id?: number;
locked?: boolean;
wrapped?: boolean;
tilted?: boolean;
interactible?: boolean;
noEffects?: boolean;
used_effects_indexes?: number[];
discardable?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
wrapped: () => false,
tilted: () => true,
locked: () => true,
interactible: () => true,
noEffects: () => false,
discardable: () => false,
});
const emit = defineEmits([
"click",
"effectClick",
"drop",
"move",
"startMove",
"stopMove",
]);
const settingsStore = useSettingsStore();
const childCount = ref<number>(0);
const card = ref<HTMLElement>();
const dragStatus = ref<{
hasClickedDown: boolean;
dragging: boolean;
intervalEvent?: ReturnType<typeof setTimeout>;
}>({
hasClickedDown: false,
dragging: false,
});
const initialOffsetX = ref(0);
const initialOffsetY = ref(0);
const mousePageX = ref(0);
const mousePageY = ref(0);
const offsetX = ref(0);
const offsetY = ref(0);
const offsetWidth = ref(0);
const offsetHeight = ref(0);
const translateX = ref(0);
const translateY = ref(0);
const extension = computed(() => {
if (settingsStore.cardStyle == CardStyles.SVG) {
return ".svg";
}
return ".png";
});
function cardClick() {
if (!props.card_id) return;
emit("click");
}
const isZooming = ref<boolean>(false);
function mouseenter(e: MouseEvent) {
// User is Right Clicking
if ((e.buttons & 2) == 2) {
isZooming.value = true;
}
// goStore.hoveredCard = props.card_id
}
function mouseleave(e: MouseEvent) {
isZooming.value = false;
}
function rightClickDown(e: MouseEvent) {
isZooming.value = true;
}
function rightClickUp(e: MouseEvent) {
isZooming.value = false;
}
function startDrag(e: MouseEvent) {
emit("startMove");
dragStatus.value.dragging = true;
translateX.value = 0;
translateY.value = 0;
const cardElement = card.value;
if (!cardElement) {
return;
}
mousePageX.value = e.pageX;
mousePageY.value = e.pageY;
initialOffsetX.value = e.offsetX;
initialOffsetY.value = e.offsetY;
offsetX.value = cardElement.offsetLeft + e.offsetX;
offsetY.value = cardElement.offsetTop + e.offsetY;
translateX.value = mousePageX.value - offsetX.value;
translateY.value = mousePageY.value - offsetY.value;
}
function leftClickDown(e: MouseEvent) {
dragStatus.value.hasClickedDown = true;
window.addEventListener("mousemove", mouseMove, false);
}
function leftClickUpGlobal(e: MouseEvent) {
mousePageX.value = e.pageX;
mousePageY.value = e.pageY;
if (dragStatus.value.dragging && dragStatus.value.hasClickedDown) {
dragStatus.value.dragging = false;
emit("drop", { x: e.pageX, y: e.pageY, uuid: props.card?._id });
emit("stopMove");
}
dragStatus.value.hasClickedDown = false;
clearTimeout(dragStatus.value.intervalEvent);
window.removeEventListener("mousemove", mouseMove, false);
}
function leftClickUp(e: MouseEvent) {
if (dragStatus.value.dragging == false && dragStatus.value.hasClickedDown) {
dragStatus.value.hasClickedDown = false;
cardClick();
return;
}
}
const sensitivity = 0.5;
function mouseMove(e: MouseEvent) {
if (
dragStatus.value.hasClickedDown &&
!dragStatus.value.dragging &&
Math.sqrt(Math.abs(e.movementX * e.movementY)) > sensitivity
&& settingsStore.enableDragging
) {
startDrag(e);
}
const cardElement = card.value;
if (!cardElement) {
return;
}
mousePageX.value = e.pageX;
mousePageY.value = e.pageY;
// console.log(cardElement.offsetLeft);
offsetX.value = cardElement.offsetLeft + initialOffsetX.value;
offsetY.value = cardElement.offsetTop + initialOffsetY.value;
translateX.value = mousePageX.value - offsetX.value;
translateY.value = mousePageY.value - offsetY.value;
// emit("move", { x: e.pageX, y: e.pageY, uuid: props.card?._id });
}
defineExpose({
offsetX,
offsetY,
offsetWidth,
offsetHeight,
uuid: props.card?._id,
});
onMounted(() => {
window.addEventListener("mouseup", leftClickUpGlobal, false);
const cardElement = card.value;
if (!cardElement) {
return;
}
offsetX.value = cardElement.offsetLeft;
offsetY.value = cardElement.offsetTop;
offsetWidth.value = cardElement.offsetWidth;
offsetHeight.value = cardElement.offsetHeight;
cardElement.offsetWidth;
});
// onRenderTriggered(() => {
// const cardElement = card.value;
// if (!cardElement) {
// return;
// }
// offsetX.value = cardElement.offsetLeft + initialOffsetX.value;
// offsetY.value = cardElement.offsetTop + initialOffsetY.value;
// translateX.value = mousePageX.value - offsetX.value;
// translateY.value = mousePageY.value - offsetY.value;
// });
onUpdated(() => {
const cardElement = card.value;
if (!cardElement) {
return;
}
offsetX.value = cardElement.offsetLeft + initialOffsetX.value;
offsetY.value = cardElement.offsetTop + initialOffsetY.value;
translateX.value = mousePageX.value - offsetX.value;
translateY.value = mousePageY.value - offsetY.value;
});
onUnmounted(() => {
window.removeEventListener("mouseup", leftClickUpGlobal, false);
// window.addEventListener("mousemove", mouseMove, false);
});
</script>
<template>
<div ref="card" class="card" :class="{
dragging: dragStatus.dragging,
active: childCount > 0,
wrapped,
unlocked: !props.locked,
zoom: isZooming,
no_effects: noEffects || !props.locked,
discardable,
}" @contextmenu.prevent="false" @mousedown.right="rightClickDown" @mouseup.right="rightClickUp"
@mousedown.left="leftClickDown" @mouseup.left="leftClickUp" @mouseenter="mouseenter" @mouseleave="mouseleave">
<template v-if="props.card_id">
<!-- <svg
width="180"
height="250"
viewBox="0 0 298 417"
>
<use
:xlink:href="
`/cards/${settingsStore.cardStyle}/` +
props.card_id.toString().padStart(3, '0') +
extension +
'#card' +
props.card_id
"
@activate_effect="activateEffect($event)"
></use>
</svg> -->
<CardComponent v-if="settingsStore.cardStyle == CardStyles.SVG && props.card" :card="props.card"
:used_effects_indexes="used_effects_indexes" @effectClick="emit('effectClick', $event)"></CardComponent>
<img :class="`card_image`" v-else :src="`/cards/${settingsStore.cardStyle}/` +
props.card_id.toString().padStart(3, '0') +
extension
" draggable="false" />
<div id="overlay">
<slot></slot>
</div>
</template>
<img :class="`card_image`" v-else :src="'/cards/background.png'" draggable="false" />
</div>
</template>
<style scoped>
.card.dragging {
transform: translateX(v-bind("translateX + 'px'")) translateY(v-bind("translateY + 'px'"));
cursor: grabbing;
z-index: 999;
}
.card.dragging>svg {
transform: scale(110%);
}
.card.active {
cursor: pointer;
}
.card_image {
max-height: var(--max-height);
z-index: 1;
height: var(--max-height);
}
.card.wrapped {
height: 1px;
margin: 0;
}
.card.wrapped .card_image {
filter: drop-shadow(0.5px 0.5px 0.5px gray) drop-shadow(-0.5px -0.5px 0.5px gray);
}
.card.zoom {
transform: scale(200%);
z-index: 10;
}
#contextMenuButtons {
position: absolute;
display: flex;
flex-direction: row;
flex-wrap: wrap;
word-break: break-word;
bottom: 0;
width: 99%;
transform: translateY(-0px);
transition: 0.1s transform cubic-bezier(1, 1.81, 0.59, 1.42);
}
.card.active:hover #contextMenuButtons {
transform: translateY(100%);
}
</style>
<style>
.card.no_effects svg>* {
pointer-events: none;
}
#contextMenuButtons>* {
flex: 1;
border-radius: 12px;
min-height: 50px;
}
@keyframes gelatine {
from,
to {}
25% {
transform: scale(1, 1.5);
}
50% {
transform: scale(1.5, 1);
}
75% {
transform: scale(1.1, 1.3);
}
}
.svg_resource_icon {
cursor: pointer;
animation: gelatine 0.3s normal;
transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.svg_resource_icon:active {
animation: none;
}
.svg_resource_icon:hover {
transform: scale(130%);
}
.used {
opacity: 0.2;
filter: grayscale(0.6);
pointer-events: none;
}
.svg_effect text {
cursor: pointer;
}
.card {
--max-height: 250px;
--max-width: 179px;
--margin-bottom: calc(var(--max-height) * 0.05);
position: relative;
display: flex;
justify-content: center;
margin-bottom: 0px;
margin-top: var(--margin-bottom);
transition: all 0.15s cubic-bezier(0.175, 0.885, 0.32, 1.275);
pointer-events: all;
max-height: var(--max-height);
max-width: var(--max-width);
/* cursor: grab; */
}
.card:not(svg) {
cursor: grab;
}
#overlay {
position: absolute;
top: 20%;
width: calc(100% - 8%);
height: 49%;
}
.card.unlocked {
margin-top: 0px;
margin-bottom: var(--margin-bottom);
transform: translateY(calc(var(--margin-bottom) * -0.25));
filter: drop-shadow(calc(var(--margin-bottom) * 0.3) var(--margin-bottom) 1px rgba(0, 0, 0, 0.6));
cursor: pointer;
}
.card.discardable {
cursor: not-allowed;
}
.card.discardable svg {
pointer-events: none;
}
</style>
-106
View File
@@ -1,106 +0,0 @@
<script setup lang="ts">
import { buyCard, buyGem, useToken } from "~/netcode";
import {
CardEffects,
type Card,
type Container,
type Effect,
type Game,
type Player,
} from "~/netcode/interfaces";
import TokenComponent from "./tokens/TokenComponent.vue";
export interface Props {
game: Game;
discard: Container;
player: Player;
}
const props = defineProps<Props>();
const authStore = useAuthStore();
const emit = defineEmits(["close"]);
function marketCardClick(e: string) {
buyCard(props.game._id, e);
}
function fireGemCardClick(e: string) {
buyGem(props.game._id);
}
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)
)
);
function tokenClick({ card, token }: { card: Card; token: Effect }) {
useToken(props.game._id, token._id, card._id);
}
</script>
<template>
<div class="overlay">
<button @click="$emit('close')">Close</button>
<span>Discard of {{ player.user.username }}</span>
<div class="discard_unwrapped" @click.stop>
<CardsGrouped
:container="discard"
:wrapped="false"
:hidden="false"
:tokens="discardTokens"
:noEffects="true"
:playingTurn="game.currentTurn"
@tokenClick="tokenClick"
></CardsGrouped>
</div>
</div>
</template>
<style scoped>
span {
background: white;
}
.overlay {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
position: fixed;
z-index: 200;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>
<style>
.discard_unwrapped {
width: max-content;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
/* position: relative; */
gap: 2px;
margin: 10px;
padding-left: 10px;
padding-right: 10px;
padding-bottom: 10px;
border-radius: 10px;
/* filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6)); */
/* backdrop-filter: blur(5px) brightness(80%); */
background: rgba(60, 47, 117, 0.46);
/* left: 50%;
top: 50%;
transform: translate(-50%, -50%); */
}
</style>
-33
View File
@@ -1,33 +0,0 @@
<script setup lang="ts">
const displayOptions = ref(false);
</script>
<template>
<div :class="`card_overlay`" ref="card">
<img
class="settings_button"
src="/img/settings.png"
alt="settings"
@click="displayOptions = true"
/>
</div>
<OptionsDialog :isVisible="displayOptions" @close="displayOptions = false" />
</template>
<style scoped>
.card_overlay {
/* display: none; */
position: fixed;
z-index: 999;
display: flex;
pointer-events: none;
/* top: left: */
top: 0;
}
.settings_button {
pointer-events: all;
width: 30px;
padding: 5px;
}
</style>
-167
View File
@@ -1,167 +0,0 @@
<script setup lang="ts">
const settingsStore = useSettingsStore();
export interface Props {
isVisible: boolean;
}
const _devicePixelRatio = computed(() => devicePixelRatio);
const _window = computed(() => window);
const props = withDefaults(defineProps<Props>(), {});
const emit = defineEmits(["close"]);
const close = () => {
emit("close");
};
</script>
<template>
<div v-if="isVisible" class="dialog-overlay" @click="close">
<div class="dialog-box" @click.stop>
<div class="dialog-header">
<h3>Visuals</h3>
</div>
<div class="dialog-header">
<span>Toggle Card Style</span>
</div>
<div class="dialog-body">
<button @click="settingsStore.switchCardStyles">
Toggle Card style
</button>
</div>
<div class="dialog-footer">
<span>{{ settingsStore.cardStyle }}</span>
</div>
<div class="dialog-header">
<span>Animation Speed</span>
</div>
<div class="dialog-body">
<input type="range" min="0" max="250" step="25" v-model="settingsStore.animationSpeed" />
</div>
<div class="dialog-footer">
<span>{{ settingsStore.animationSpeed }}ms</span>
</div>
<div class="dialog-header">
<span>Fast mode</span>
</div>
<div class="dialog-body">
<input type="checkbox" v-model="settingsStore.fastAnimations" />
</div>
<div class="dialog-header">
<h3>Graphics</h3>
</div>
<div class="dialog-header">
<span>Shader Quality</span>
</div>
<div class="dialog-body">
<input type="range" min="1" max="10" v-model="settingsStore.shaderQuality"
:disabled="settingsStore.shaderFramerate == 0" />
</div>
<div class="dialog-footer">
<span>{{
Math.floor(
(_window.innerWidth *
_devicePixelRatio *
settingsStore.shaderQuality) /
10
)
}}x{{
Math.floor(
(_window.innerHeight *
_devicePixelRatio *
settingsStore.shaderQuality) /
10
)
}}</span>
</div>
<div class="dialog-header">
<span>Shader Framerate</span>
</div>
<div class="dialog-body">
<input type="range" min="0" max="65" step="5" v-model="settingsStore.shaderFramerate" />
</div>
<div class="dialog-footer">
<span v-if="settingsStore.shaderFramerate == 65">vsync</span>
<span v-else>{{ settingsStore.shaderFramerate }} fps</span>
</div>
<div class="dialog-header">
<span>Pixelated</span>
</div>
<div class="dialog-body">
<input type="checkbox" v-model="settingsStore.shaderPixelated" />
</div>
<div class="dialog-header">
<h3>Other</h3>
</div>
<div class="dialog-header">
<span>Enable dragging (experimental)</span>
</div>
<div class="dialog-body">
<input type="checkbox" v-model="settingsStore.enableDragging" />
</div>
</div>
</div>
</template>
<style scoped>
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
display: grid;
}
.dialog-box {
background-image: url("../img/bg-grass.png");
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%;
}
.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>
View File
-114
View File
@@ -1,114 +0,0 @@
<script setup lang="ts">
import type { Container } from '~/netcode/interfaces';
export interface Props {
container: Container
}
const props = defineProps<Props>()
</script>
<template>
<div class="cards_container" id="self_hand">
<!-- <CardPreview :card_id="c.cardId" v-for="c in container.cards "> -->
<!-- <button v-if="c && goStore.self?.turn && (goStore.self.turn.fuck_u_markers ?? 0) > 0"
@click.stop="discard(c)">DISCARD</button> -->
<!-- </CardPreview> -->
<CardsGrouped :container="container" :wrapped="false">
</CardsGrouped>
</div>
</template>
<style scoped>
.cards_container {
display: flex;
flex-direction: row-reverse;
justify-content: center;
}
.stack_and_discard {
display: flex;
flex-direction: row-reverse;
justify-content: center;
}
#play_all {
display: flex;
flex-direction: row;
align-items: center;
font-size: 10em;
}
#turn_data {
position: fixed;
bottom: 10px;
left: 50%;
right: 50%;
display: flex;
flex-direction: column;
align-items: flex-start;
z-index: 9;
}
.warning {
background-color: rgba(255, 0, 0, 0.7);
color: white;
padding: 5px;
border-radius: 5px;
width: 60px;
font-size: 0.9em;
}
#end_turn {
z-index: 10;
}
#self_hand {
position: fixed;
z-index: 1;
display: flex;
align-items: stretch;
flex-direction: row;
flex-wrap: nowrap;
left: 50%;
bottom: -50px;
max-width: 100vw;
justify-content: flex-start;
transition: 0.5s all cubic-bezier(0.075, 0.82, 0.165, 1);
transform: translate(-50%, 150px)
}
#self_hand:hover {
transform: translate(-50%, 0px);
}
#end_turn {
width: 50px;
height: 50px;
}
</style>
<style>
#self_hand .card {
transition: 0.5s all cubic-bezier(0.075, 0.82, 0.165, 1);
overflow: visible;
}
#self_hand .card #contextMenuButtons {
top: 0;
bottom: unset;
}
#self_hand .card.active:hover #contextMenuButtons {
transform: translateY(-100%);
}
#self_hand .card:hover {
transform: translateY(-40px);
}
</style>
+261
View File
@@ -0,0 +1,261 @@
<script setup lang="ts">
export interface Props {
uuid?: string;
cardId: number | undefined;
locked?: boolean;
wrapped?: boolean;
tilted?: boolean;
interactible?: boolean;
noEffects?: boolean;
used_effects_indexes?: number[];
discardable?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
wrapped: () => false,
tilted: () => true,
locked: () => true,
interactible: () => true,
noEffects: () => false,
discardable: () => false,
});
const emit = defineEmits([
"click",
"effectClick",
"drop",
"move",
"startMove",
"stopMove",
]);
const settingsStore = useSettingsStore();
const childCount = ref<number>(0);
const extension = computed(() => {
if (settingsStore.cardStyle == CardStyles.SVG) {
return ".svg";
}
return ".png";
});
function cardClick() {
if (!props.cardId) return;
emit("click");
}
const isZooming = ref<boolean>(false);
function mouseenter(e: MouseEvent) {
// User is Right Clicking
if ((e.buttons & 2) == 2) {
isZooming.value = true;
}
// goStore.hoveredCard = props.card_id
}
function mouseleave(e: MouseEvent) {
isZooming.value = false;
}
function rightClickDown(e: MouseEvent) {
isZooming.value = true;
}
function rightClickUp(e: MouseEvent) {
isZooming.value = false;
}
</script>
<template>
<div
ref="cardHTMLElement"
class="card"
:class="{
active: childCount > 0,
wrapped,
unlocked: !props.locked,
zoom: isZooming,
no_effects: noEffects || !props.locked,
discardable,
}"
@contextmenu.prevent="false"
@mousedown.right="rightClickDown"
@mouseup.right="rightClickUp"
@click.stop="cardClick"
@mouseenter="mouseenter"
@mouseleave="mouseleave"
>
<template v-if="props.cardId">
<GameCardAtom
v-if="settingsStore.cardStyle == CardStyles.SVG"
:cardId="props.cardId"
:used_effects_indexes="used_effects_indexes"
@effectClick="emit('effectClick', $event)"
></GameCardAtom>
<div id="overlay">
<slot></slot>
</div>
</template>
<img
:class="`card_image`"
v-else
:src="'/cards/background.png'"
draggable="false"
/>
</div>
</template>
<style scoped>
/* .card.dragging {
transform: translateX(v-bind("translateX + 'px'"))
translateY(v-bind("translateY + 'px'"));
cursor: grabbing;
z-index: 999;
} */
/* .card.dragging > svg {
transform: scale(110%);
} */
.card.active {
cursor: pointer;
}
.card_image {
max-height: var(--max-height);
z-index: 1;
height: var(--max-height);
}
.card.wrapped {
height: 1px;
margin: 0;
}
.card.wrapped .card_image {
filter: drop-shadow(0.5px 0.5px 0.5px gray)
drop-shadow(-0.5px -0.5px 0.5px gray);
}
.card.zoom {
transform: scale(200%);
z-index: 10;
}
#contextMenuButtons {
position: absolute;
display: flex;
flex-direction: row;
flex-wrap: wrap;
word-break: break-word;
bottom: 0;
width: 99%;
transform: translateY(-0px);
transition: 0.05s transform var(--animation-curve);
}
.card.active:hover #contextMenuButtons {
transform: translateY(100%);
}
</style>
<style>
.card.no_effects svg > * {
pointer-events: none;
}
#contextMenuButtons > * {
flex: 1;
border-radius: 12px;
min-height: 50px;
}
@keyframes gelatine {
from,
to {
}
25% {
transform: scale(1, 1.5);
}
50% {
transform: scale(1.5, 1);
}
75% {
transform: scale(1.1, 1.3);
}
}
.svg_resource_icon {
cursor: pointer;
animation: gelatine 0.3s normal;
transition: all 1s var(--animation-curve);
}
.svg_resource_icon:active {
animation: none;
}
.svg_resource_icon:hover {
transform: scale(130%);
}
.used {
opacity: 0.2;
filter: grayscale(0.6);
pointer-events: none;
}
.svg_effect text {
cursor: pointer;
}
.card {
--max-height: 250px;
--max-width: 179px;
--margin-bottom: calc(var(--max-height) * 0.05);
position: relative;
display: flex;
justify-content: center;
margin-bottom: 0px;
margin-top: var(--margin-bottom);
transition: all 0.15s var(--animation-curve);
pointer-events: all;
max-height: var(--max-height);
max-width: var(--max-width);
/* cursor: grab; */
}
.card:not(svg) {
cursor: grab;
}
#overlay {
position: absolute;
top: 20%;
width: calc(100% - 8%);
height: 49%;
}
.card.unlocked {
margin-top: 0px;
margin-bottom: var(--margin-bottom);
transform: translateY(calc(var(--margin-bottom) * -0.25));
filter: drop-shadow(
calc(var(--margin-bottom) * 0.3) var(--margin-bottom) 1px rgba(0, 0, 0, 0.6)
);
cursor: pointer;
}
.card.discardable {
cursor: not-allowed;
}
.card.discardable svg {
pointer-events: none;
}
</style>
@@ -7,7 +7,6 @@ import {
type Effect,
type Game,
} from "~/netcode/interfaces";
import TokenComponent from "./tokens/TokenComponent.vue";
export interface Props {
game: Game;
@@ -61,14 +60,29 @@ function tokenClick({ card, token }: { card: Card; token: Effect }) {
<template>
<div class="market">
<CardsGrouped :container="market" @cardClick="marketCardClick" :noEffects="true" :tokens="marketTokens"
@tokenClick="tokenClick">
</CardsGrouped>
<GameCardGrouped
:container="market"
@cardClick="marketCardClick"
:noEffects="true"
:tokens="marketTokens"
@tokenClick="tokenClick"
>
</GameCardGrouped>
<div class="separator"></div>
<CardsGrouped :container="fire_gems" :wrapped="true" @cardClick="fireGemCardClick" :noEffects="true"
:tokens="gemsTokens" @tokenClick="tokenClick"></CardsGrouped>
<GameCardGrouped
:container="fire_gems"
:wrapped="true"
@cardClick="fireGemCardClick"
:noEffects="true"
:tokens="gemsTokens"
@tokenClick="tokenClick"
></GameCardGrouped>
<div class="separator"></div>
<CardsGrouped :container="marketStack" :wrapped="true" :hidden="true"></CardsGrouped>
<GameCardGrouped
:container="marketStack"
:wrapped="true"
:hidden="true"
></GameCardGrouped>
</div>
</template>
@@ -88,8 +102,8 @@ function tokenClick({ card, token }: { card: Card; token: Effect }) {
padding-bottom: 10px;
border-radius: 10px;
isolation: initial;
/* filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6)); */
/* backdrop-filter: blur(5px) brightness(80%); */
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6));
backdrop-filter: blur(5px) brightness(80%);
background: rgba(155, 62, 62, 0.46);
}
+30
View File
@@ -0,0 +1,30 @@
<script lang="ts">
import { cards } from "~/services/loadCards";
export default {
props: {
cardId: {
required: true,
type: Number,
},
used_effects_indexes: {
default: [],
type: Array,
},
},
data() {
return { cards };
},
beforeCreate() {
(this as any).component = () => cards[this.cardId];
},
};
</script>
<template>
<Component
:is="cards[cardId]"
:cardId="cardId"
:used_effects_indexes="used_effects_indexes"
/>
</template>
@@ -8,7 +8,6 @@ import {
type Effect,
type PlayingTurn,
} from "~/netcode/interfaces";
import TokenComponent from "./tokens/TokenComponent.vue";
export interface Props {
container: Container;
@@ -19,6 +18,7 @@ export interface Props {
tokens?: Effect[];
championsKillable?: boolean;
discardable?: boolean;
locked?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
@@ -43,7 +43,6 @@ const settingsStore = useSettingsStore();
const cards = computed(() => {
if (props.hidden) {
return props.container.cards.map((_v, i) => ({
_id: i,
cardId: undefined,
})) as unknown as Card[];
}
@@ -138,16 +137,18 @@ function discardCardClick(card: Card) {
:class="{ wrapped, rotate: container.shuffling }"
>
<TransitionGroup name="list">
<CardPreview
<GameCard
@click="$emit('cardClick', c._id)"
@effectClick="$emit('effectClick', c._id, $event)"
v-for="(c, i) in cards"
:card_id="c?.cardId"
:cardId="c?.cardId"
:uuid="c._id"
:wrapped="wrapped"
:key="c?._id || i"
:card="c"
:locked="
!playingTurn || playingTurn?.cardsLocked.includes(c._id.toString())
locked ||
!playingTurn ||
playingTurn?.cardsLocked.includes(c._id.toString())
"
:used_effects_indexes="c?.effects?.reduce( (prev,e, i)=>{
if(playingTurn?.lockedEffects.includes(e._id)){
@@ -159,7 +160,7 @@ function discardCardClick(card: Card) {
:discardable="discardable"
>
<template v-for="t in tokens">
<TokenComponent
<GameToken
:token="t"
@useToken="tokenClick(t, c)"
v-if="
@@ -181,7 +182,7 @@ function discardCardClick(card: Card) {
(t.effect == CardEffects.RESTACK_DISCARDED_CHAMPION &&
c.cardType == CardType.CHAMPION)
"
></TokenComponent>
></GameToken>
</template>
<button
v-if="championsKillable && c.cardType == CardType.CHAMPION"
@@ -199,7 +200,7 @@ function discardCardClick(card: Card) {
>
Discard
</button>
</CardPreview>
</GameCard>
</TransitionGroup>
<!-- <img :class="`card_image`" :src="'/cards/background.png'" draggable="false"> -->
</div>
@@ -230,7 +231,8 @@ function discardCardClick(card: Card) {
.cards_container {
display: flex;
flex-direction: row;
justify-content: space-evenly;
justify-content: flex-start;
gap: 30px;
min-width: 200px;
flex-wrap: wrap;
transition: rotate v-bind("shuffleAnimationSpeed") ease;
@@ -239,6 +241,7 @@ function discardCardClick(card: Card) {
.cards_container.wrapped {
flex-direction: column-reverse;
justify-content: center;
gap: 0;
}
.cards_container.wrapped:before {
+160
View File
@@ -0,0 +1,160 @@
<script setup lang="ts">
import { initCustomFormatter } from "vue";
import { buyCard, buyGem, useToken } from "~/netcode";
import {
CardEffects,
type Card,
type Container,
type Effect,
type Game,
type Player,
} from "~/netcode/interfaces";
export interface Props {
game: Game;
player: Player;
}
const settingsStore = useSettingsStore();
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)
)
);
function tokenClick({ card, token }: { card: Card; token: Effect }) {
useToken(props.game._id, token._id, card._id);
}
function openDiscard() {
isVisible.value = true;
if (!settingsStore.saveDiscardSize) {
settingsStore.discardFullscreen = false;
}
}
</script>
<template>
<div id="discard">
<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"
>
<span>Discard of {{ player.user.username }}</span>
<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>
</div>
</template>
<style scoped>
span {
background: white;
}
.discard-fullscreen-btn {
pointer-events: all;
}
.discard-animations {
transition: 0.3s all var(--animation-curve);
}
.discard-background {
transition: 0.3s all linear;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0);
pointer-events: none;
z-index: 200;
}
.discard-background.visible {
background: rgba(0, 0, 0, 0.6);
pointer-events: all;
backdrop-filter: blur(2px);
}
.discard-menu {
left: -75%;
top: 0;
width: 25%;
height: 100%;
padding-left: 50%;
background: rgb(100, 53, 44);
position: fixed;
z-index: 200;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.discard-menu.visible {
left: -50%;
}
.discard-menu.fullscreen {
left: -100%;
width: 100%;
padding: 0;
background: rgba(0, 0, 0, 0);
pointer-events: none;
}
.discard-menu.visible.fullscreen {
left: 0%;
}
.discard-menu > .cards_container {
pointer-events: all;
overflow: scroll;
padding: 30px;
max-width: 100%;
transition: 0.3s all var(--animation-curve);
justify-content: center;
}
.discard-menu.fullscreen > .cards_container {
max-width: 50%;
}
</style>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { Player } from "~/netcode/Player";
import type { Player } from "~/netcode/interfaces";
export interface Props {
isVisible: Boolean;
@@ -27,7 +27,7 @@ const close = () => {
<h3 v-else>Défaite!</h3>
</div>
<div class="dialog-body">
<PlayerIcon :player="players[0]"></PlayerIcon>
<GamePlayerIcon :player="players[0]"></GamePlayerIcon>
<p v-if="won">Vous avez gagné!</p>
<p v-else>
Vous avez perdu face à ce magnifique individu qui a très clairement
@@ -0,0 +1,211 @@
<script setup lang="ts">
const settingsStore = useSettingsStore();
const isVisible = ref(false);
const _devicePixelRatio = computed(() => devicePixelRatio);
const _window = computed(() => window);
</script>
<template>
<button @click="isVisible = true" class="dialog-open-button">
Open Settings
</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>Game</h3>
</div>
<div class="dialog-header">
<span>Save discard size</span>
</div>
<div class="dialog-body">
<input type="checkbox" v-model="settingsStore.saveDiscardSize" />
</div>
<div class="dialog-footer">
<template v-if="!settingsStore.saveDiscardSize"
>Discard will always open small
</template>
<template v-else="!settingsStore.saveDiscardSize"
>Discard will remember its size
</template>
</div>
<div class="dialog-header">
<h3>Visuals</h3>
</div>
<div class="dialog-header">
<span>Toggle Card Style</span>
</div>
<div class="dialog-body">
<button @click="settingsStore.switchCardStyles">Toggle Card style</button>
</div>
<div class="dialog-footer">
<span>{{ settingsStore.cardStyle }}</span>
</div>
<div class="dialog-header">
<span>Animation Speed</span>
</div>
<div class="dialog-body">
<input
type="range"
min="0"
max="250"
step="25"
v-model="settingsStore.animationSpeed"
/>
</div>
<div class="dialog-footer">
<span>{{ settingsStore.animationSpeed }}ms</span>
</div>
<div class="dialog-header">
<span>Fast mode</span>
</div>
<div class="dialog-body">
<input type="checkbox" v-model="settingsStore.fastAnimations" />
</div>
<div class="dialog-header">
<h3>Graphics</h3>
</div>
<div class="dialog-header">
<span>Shader Quality</span>
</div>
<div class="dialog-body">
<input
type="range"
min="1"
max="10"
v-model="settingsStore.shaderQuality"
:disabled="settingsStore.shaderFramerate == 0"
/>
</div>
<div class="dialog-footer">
<span v-if="_window"
>{{
Math.floor(
(_window.innerWidth *
_devicePixelRatio *
settingsStore.shaderQuality) /
10
)
}}x{{
Math.floor(
(_window.innerHeight *
_devicePixelRatio *
settingsStore.shaderQuality) /
10
)
}}</span
>
</div>
<div class="dialog-header">
<span>Shader Framerate</span>
</div>
<div class="dialog-body">
<input
type="range"
min="0"
max="65"
step="5"
v-model="settingsStore.shaderFramerate"
/>
</div>
<div class="dialog-footer">
<span v-if="settingsStore.shaderFramerate == 65">vsync</span>
<span v-else>{{ settingsStore.shaderFramerate }} fps</span>
</div>
<div class="dialog-header">
<span>Pixelated</span>
</div>
<div class="dialog-body">
<input type="checkbox" v-model="settingsStore.shaderPixelated" />
</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-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(38, 62, 15);
}
.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>
@@ -1,8 +1,8 @@
<script setup lang="ts">
import type { Player } from "~/netcode/interfaces";
import type { User } from "~/netcode/interfaces";
export interface Props {
player: Player;
user: User;
}
const props = defineProps<Props>();
@@ -12,11 +12,11 @@ const props = defineProps<Props>();
<div class="player-icon-container">
<img
class="avatar"
v-if="props.player.user.avatar"
:src="`https://cdn.discordapp.com/avatars/${props.player.user.userId}/${props.player.user.avatar}.webp`"
v-if="props.user.avatar"
:src="`https://cdn.discordapp.com/avatars/${props.user.userId}/${props.user.avatar}.webp`"
draggable="false"
/>
<b class="player-name">{{ props.player.user.username }}</b>
<b class="player-name">{{ props.user.username }}</b>
</div>
</template>
@@ -60,11 +60,12 @@ function damage() {
Prêt!
</div> -->
<div id="player_info">
<PlayerIcon :player="props.player"></PlayerIcon>
<GamePlayerIcon :user="props.player.user"></GamePlayerIcon>
</div>
<div id="player_stats">
<!-- <template v-if="goStore.current_game?.started"> -->
<PlayerStats :game="props.game" :player="props.player"> </PlayerStats>
<GamePlayerStats :game="props.game" :player="props.player">
</GamePlayerStats>
<!-- </template> -->
</div>
<button
+108
View File
@@ -0,0 +1,108 @@
<script setup lang="ts">
import type { Container } from "~/netcode/interfaces";
export interface Props {
container: Container;
}
const props = defineProps<Props>();
</script>
<template>
<div class="cards_container" id="self_hand">
<!-- <CardPreview :card_id="c.cardId" v-for="c in container.cards "> -->
<!-- <button v-if="c && goStore.self?.turn && (goStore.self.turn.fuck_u_markers ?? 0) > 0"
@click.stop="discard(c)">DISCARD</button> -->
<!-- </CardPreview> -->
<GameCardGrouped :container="container" :wrapped="false"> </GameCardGrouped>
</div>
</template>
<style scoped>
.cards_container {
display: flex;
flex-direction: row-reverse;
justify-content: center;
}
.stack_and_discard {
display: flex;
flex-direction: row-reverse;
justify-content: center;
}
#play_all {
display: flex;
flex-direction: row;
align-items: center;
font-size: 10em;
}
#turn_data {
position: fixed;
bottom: 10px;
left: 50%;
right: 50%;
display: flex;
flex-direction: column;
align-items: flex-start;
z-index: 9;
}
.warning {
background-color: rgba(255, 0, 0, 0.7);
color: white;
padding: 5px;
border-radius: 5px;
width: 60px;
font-size: 0.9em;
}
#end_turn {
z-index: 10;
}
#self_hand {
position: fixed;
z-index: 1;
display: flex;
align-items: stretch;
flex-direction: row;
flex-wrap: nowrap;
left: 50%;
bottom: -50px;
max-width: 100vw;
justify-content: flex-start;
transition: 0.5s all var(--animation-curve);
transform: translate(-50%, 150px);
}
#self_hand:hover {
transform: translate(-50%, 0px);
}
#end_turn {
width: 50px;
height: 50px;
}
</style>
<style>
#self_hand .card {
transition: 0.5s all var(--animation-curve);
overflow: visible;
}
#self_hand .card #contextMenuButtons {
top: 0;
bottom: unset;
}
#self_hand .card.active:hover #contextMenuButtons {
transform: translateY(-100%);
}
#self_hand .card:hover {
transform: translateY(-40px);
}
</style>
@@ -150,33 +150,6 @@ function discardCardClick(card: Card) {
<template>
<div class="player" :class="{ turn: isPlayerTurn, self: isSelf }">
<div id="turn_buttons">
<button
id="end_turn"
class="end_turn_button turn_icon"
:class="{ hidden: !isSelfTurn }"
@click="checkEndTurn"
>
END TURN
</button>
<button class="end_turn_button turn_icon" @click="startGame(game._id)">
Start game
</button>
<button class="end_turn_button turn_icon" @click="joinGame(game._id)">
Join game
</button>
<div
class="warning"
:class="{
'warning-fire-gem': warningText == 'P\'tite Fire gem?',
visible: warning,
}"
>
/!\<br />{{ warningText }}
</div>
</div>
<div id="current_player_board">
<!-- <div class="other_player_buttons_container">
<button @click.stop="damage_team(props.player.team)"
@@ -198,7 +171,7 @@ function discardCardClick(card: Card) {
</div> -->
<!-- <br /> -->
<CardsGrouped
<GameCardGrouped
:container="player.board"
:wrapped="false"
:playingTurn="props.game.currentTurn"
@@ -213,8 +186,8 @@ function discardCardClick(card: Card) {
@damageChampionClick="damageChampionClick"
@discardCardClick="discardCardClick"
>
</CardsGrouped>
<br>
</GameCardGrouped>
<br />
<!-- <div class="cards_container"> -->
<!-- HAND -->
<!-- <template v-if="isSelf">
+4 -23
View File
@@ -3,24 +3,6 @@ import { useAuthStore } from "#imports";
export default defineNuxtRouteMiddleware(async (to, from) => {
const authStore = useAuthStore();
const config = useRuntimeConfig();
if (to.path == "/login") {
const code = to.query.code;
if (!code) {
return navigateTo(config.public.discordLoginEndpoint, { external: true });
}
if (!Array.isArray(code)) {
try {
await authStore.whoami();
return navigateTo("/lobby");
} catch {
return navigateTo("/login");
}
} else {
return navigateTo("/login");
}
}
// if (to.path == '/') {
// const code = to.query.code
// if (code) {
@@ -51,12 +33,11 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
// return navigateTo('/login')
// }
// }
console.log(to.path)
if (to.path.startsWith("/game")) {
console.log(to.query.id)
// if (Array.isArray(to.query.id)) {
// return navigateTo("/lobby");
// }
return;
}
if (to.path == "/login") {
return;
}
if (to.path == "/lobby") {
+2 -19
View File
@@ -9,29 +9,12 @@ export function subscribe(endpoint: string) {
return evtSource;
}
export async function getLobby() {
export async function whoami() {
const config = useRuntimeConfig();
const req = await useFetch<Array<Game>>(config.public.endpoint + "/games", {
const req = await useFetch<User>(config.public.endpoint + "/whoami", {
credentials: "include",
server: false,
});
// await req.execute();
await req.execute();
if (req.data.value == null) {
throw new Error("Lobby answer is empty");
}
return req.data as Ref<Game[]>;
}
export async function whoami() {
const config = useRuntimeConfig();
const req = await useFetch<User>(
config.public.endpoint + "/whoami",
{
credentials: "include",
server: false,
}
);
await req.execute();
if (req.error.value && req.error.value.statusCode == 401) {
// await navigateTo(config.public.discordLoginEndpoint, {
+4 -2
View File
@@ -4,8 +4,8 @@ export default defineNuxtConfig({
ssr: true,
runtimeConfig: {
public: {
endpoint:
process.env.NUXT_PUBLIC_ENDPOINT || "https://playtest.legonzaur.fr/api", // can be overridden by NUXT_PUBLIC_ENDPOINT environment variable
welcomeMessage: "Hello",
endpoint: "https://playtest.legonzaur.fr/api", // can be overridden by NUXT_PUBLIC_ENDPOINT environment variable
discordLoginEndpoint: process.env.NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT,
},
},
@@ -15,6 +15,8 @@ export default defineNuxtConfig({
},
nitro: {
prerender: {
routes: ["/rules", "/login", "/game", "/lobby"],
crawlLinks: true,
},
},
+163 -107
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import LoadingScreen from "~/components/LoadingScreen.vue";
import { endTurn, subscribe, useToken } from "~/netcode";
import LoadingScreen from "~/components/game/overlay/LoadingScreen.vue";
import { endTurn, subscribe, useToken, startGame, joinGame } from "~/netcode";
import { delay } from "~/netcode/events";
import {
type Container,
@@ -9,7 +9,6 @@ import {
type Player,
} from "~/netcode/interfaces";
import { playSound } from "~/helpers/audioHelpers";
import TokenComponent from "~/components/tokens/TokenComponent.vue";
const config = useRuntimeConfig();
const settingsStore = useSettingsStore();
@@ -18,20 +17,17 @@ const authStore = useAuthStore();
const route = useRoute();
const gameId = route.query.id;
const {
data: game,
pending,
error,
refresh,
execute
execute,
} = await useFetch<Game>(config.public.endpoint + `/games/${gameId}`, {
credentials: "include",
server: false
server: false,
});
const awaitGameReady = execute()
const containers = computed<Record<string, Container>>(() => ({
[game.value!.fireGems._id]: game.value!.fireGems,
[game.value!.market._id]: game.value!.market,
@@ -70,7 +66,7 @@ async function execAction(callback: () => Promise<any>) {
const handlers = {
start: async () => {
throw new Error("not implemented")
throw new Error("not implemented");
},
populateContainer: async (data: any) => {
const fromContainer = containers.value[data.container._id as string];
@@ -146,9 +142,7 @@ const handlers = {
await delay(settingsStore.animationSpeed);
},
unlockEffect: async (data: any, game: Game) => {
const effectIndex = game.currentTurn.lockedEffects.indexOf(
data.effect
);
const effectIndex = game.currentTurn.lockedEffects.indexOf(data.effect);
game.currentTurn.lockedEffects.splice(effectIndex, 1);
await delay(settingsStore.animationSpeed);
},
@@ -183,9 +177,7 @@ const handlers = {
},
"player.addFuckUMarker": async (data: any, game: Game) => {
console.log(`adding fuckUMarkers : ` + data.operation);
const targetPlayer = playerList.value?.find(
(p) => p._id == data.playerId
);
const targetPlayer = playerList.value?.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot add fuckU Markers : player not found");
}
@@ -193,9 +185,7 @@ const handlers = {
},
"player.removeFuckUMarker": async (data: any, game: Game) => {
console.log(`adding fuckUMarkers : ` + data.operation);
const targetPlayer = playerList.value?.find(
(p) => p._id == data.playerId
);
const targetPlayer = playerList.value?.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot remove fuckU Markers : player not found");
}
@@ -203,9 +193,7 @@ const handlers = {
},
"player.addDiscardMarker": async (data: any, game: Game) => {
console.log(`adding discardMarkers : ` + data.operation);
const targetPlayer = playerList.value?.find(
(p) => p._id == data.playerId
);
const targetPlayer = playerList.value?.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot add discard Markers : player not found");
}
@@ -213,9 +201,7 @@ const handlers = {
},
"player.removeDiscardMarker": async (data: any, game: Game) => {
console.log(`adding fuckUMarkers : ` + data.operation);
const targetPlayer = playerList.value?.find(
(p) => p._id == data.playerId
);
const targetPlayer = playerList.value?.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot remove discard Markers : player not found");
}
@@ -231,39 +217,37 @@ const handlers = {
game.teams.splice(index, 1);
},
consumeEffect: async (data: any) => {
console.warn("consumeEffect not implemented")
console.warn("consumeEffect not implemented");
},
endGame: async () => {
alert("game ended")
}
}
alert("game ended");
},
};
onMounted(async () => {
awaitGameReady.then(() => {
console.log(game.value)
if (game.value == null) {
navigateTo("/lobby");
onBeforeMount(async () => {
if (game.value == null) {
navigateTo("/lobby");
throw new Error("game not found");
}
focusedPlayer.value = selfPlayer.value ?? playerList.value![0];
eventSource = subscribe(`/games/${gameId}/subscribe`);
eventSource.addEventListener("message", async (message) => {
const current_game = game.value;
if (!current_game) {
throw new Error("game not found");
}
focusedPlayer.value = (selfPlayer.value ?? playerList.value![0])
eventSource = subscribe(`/games/${gameId}/subscribe`);
eventSource.addEventListener("message", async (message) => {
const current_game = game.value;
if (!current_game) {
throw new Error("game not found");
}
const data = JSON.parse(message.data);
queueAction(async () => {
await handlers[data.type as keyof typeof handlers](data, current_game)
})
const data = JSON.parse(message.data);
queueAction(async () => {
await handlers[data.type as keyof typeof handlers](data, current_game);
});
})
});
});
const playerList = computed(() =>
game.value && game.value.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
const playerList = computed(
() =>
game.value &&
game.value.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
);
const selfPlayer = computed(() =>
authStore.logged
@@ -275,7 +259,7 @@ const isTurn = computed(
game.value &&
selfPlayer.value &&
game.value.teams.find((t) => t.players.includes(selfPlayer.value!))?._id ==
game.value.currentTurn.currentTeam
game.value.currentTurn.currentTeam
);
const turnChangeAnimationSpeed = computed(
@@ -305,24 +289,10 @@ function showWinnerScreen() {
isWinner.value = true;
}
// const clientStore = useClientSideStore()
// const restack_discarded_champion = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CHAMPION, target)
// const restack_discarded_card = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CARD, target)
// const sacrifice = (target: Card) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
function focusPlayer(p: Player) {
focusedPlayer.value = p;
}
// watch<Team>(() => goStore.current_game!.current_turn, (new_team, old_team) => {
// if (focusedPlayer.value.team != old_team) { return }
// const first_player = goStore.current_game?.players.find(p => p.team == goStore.current_game?.current_turn)
// if (!first_player) {
// return
// }
// focusPlayer(first_player)
// })
function useTokenClick(t: Effect) {
console.log(t);
useToken(game.value!._id, t._id);
@@ -338,54 +308,139 @@ onUnmounted(() => {
<template>
<div @contextmenu.prevent="false" id="game_board">
<AnimatedBackground @ready="loaded = true" :isTurn="isTurn ?? false"></AnimatedBackground>
<!-- <AnimatedBackground :isTurn="false"></AnimatedBackground> -->
<template v-if="game && focusedPlayer && playerList">
<Transition name="slide-left" mode="out-in">
<div id="stack_discard" :key="focusedPlayer?._id">
<div id="stack">
<CardsGrouped :container="focusedPlayer.stack" :wrapped="true" :hidden="true">
</CardsGrouped>
</div>
<div id="discard" :class="{ invisible: discard_unwrapped }" @click="() => (discard_unwrapped = true)">
<CardsGrouped :container="focusedPlayer.discard" :wrapped="true" @card-click="showDiscard"
:no-effects="true">
</CardsGrouped>
</div>
</div>
</Transition>
<MarketCards :game="game" :market="game.market" :fire_gems="game.fireGems" :marketStack="game.marketStack">
</MarketCards>
<div>
<TokenComponent :token="t" v-for="t in game.currentTurn.tokens" @useToken="useTokenClick"></TokenComponent>
</div>
<div id="player_list_container">
<div id="player_list">
<PlayerListElement v-for="p in playerList" :player="p" :game="game" @click="focusPlayer(p)"
:focused="focusedPlayer == p" :self="selfPlayer == p">
</PlayerListElement>
</div>
</div>
<div id="current_player">
<AnimatedBackground
@ready="loaded = true"
:isTurn="isTurn ?? false"
></AnimatedBackground>
<ClientOnly>
<template v-if="game && focusedPlayer && playerList">
<Transition name="slide-left" mode="out-in">
<PlayerSlot v-if="focusedPlayer" :player="focusedPlayer" :game="game" :key="focusedPlayer._id"></PlayerSlot>
<div id="stack_discard" :key="focusedPlayer?._id">
<div id="stack">
<GameCardGrouped
:container="focusedPlayer.stack"
:wrapped="true"
:hidden="true"
>
</GameCardGrouped>
</div>
<GameOverlayDiscard
:game="game"
:player="focusedPlayer"
></GameOverlayDiscard>
</div>
</Transition>
</div>
<DiscardOverlay v-show="discard_unwrapped" :game="game" :player="focusedPlayer" :discard="focusedPlayer.discard"
@close="discard_unwrapped = false"></DiscardOverlay>
<ResultDialog :isVisible="showResults" :won="isWinner" :players="playerList" @close="showResults = false">
</ResultDialog>
<GlobalOverlay></GlobalOverlay>
<SelfView v-if="selfPlayer" :container="selfPlayer.hand"></SelfView>
</template>
<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>
</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"
: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>
</Transition>
</div>
<GameOverlayResultDialog
:isVisible="showResults"
:won="isWinner"
:players="playerList"
@close="showResults = false"
>
</GameOverlayResultDialog>
<GamePlayerSelf
v-if="selfPlayer"
:container="selfPlayer.hand"
></GamePlayerSelf>
</template>
</ClientOnly>
<!-- <LoadingScreen v-if="!loaded"></LoadingScreen> -->
<footer>
<GameOverlayOptionsDialog></GameOverlayOptionsDialog>
<div id="turn_buttons" v-if="game">
<button
id="end_turn"
class="end_turn_button turn_icon"
:class="{ hidden: !isTurn }"
@click="endTurn(game._id)"
v-if="game.started && isTurn"
>
END TURN
</button>
<button
class="end_turn_button turn_icon"
@click="startGame(game._id)"
v-if="!game.started && game.owner.userId == authStore.selfUser.userId"
>
Start game
</button>
<button
class="end_turn_button turn_icon"
@click="joinGame(game._id)"
v-if="
game.started &&
authStore.logged &&
playerList?.every((p) => p.user.userId != authStore.selfUser.userId)
"
>
Join game
</button>
<!-- <div
class="warning"
:class="{
'warning-fire-gem': warningText == 'P\'tite Fire gem?',
visible: warning,
}"
>
/!\<br />{{ warningText }}
</div> -->
</div>
<button @click="navigateTo('/lobby')">Return to lobby</button>
</footer>
</div>
</template>
<style scoped>
footer {
margin: 10px;
display: flex;
flex-direction: row;
grid-area: footer;
height: 50px;
/* background: red; */
justify-content: space-between;
}
#game_status {
position: fixed;
z-index: 10;
@@ -398,11 +453,12 @@ onUnmounted(() => {
#game_board {
display: grid;
grid-template-columns: 1fr min-content 1fr;
grid-template-rows: min-content min-content 1fr;
grid-template-rows: min-content min-content 1fr min-content;
grid-template-areas:
"stack_discard market turn_stats"
"player_list player_list player_list"
"current_player current_player current_player";
"current_player current_player current_player"
"footer footer footer";
height: 100%;
}
+98 -91
View File
@@ -1,14 +1,20 @@
<script setup lang="ts">
// import { playSound } from '~/helpers/audioHelpers';
import { playSound } from "~/helpers/audioHelpers";
import { createGame, getLobby, subscribe } from "~/netcode";
import type { Game, Player } from "~/netcode/interfaces";
import { createGame, subscribe } from "~/netcode";
import type { Game, Player, User } from "~/netcode/interfaces";
const authStore = useAuthStore();
let eventSource: EventSource;
let games = ref<Game[]>([]);
const config = useRuntimeConfig();
const req = await useFetch<Array<Game>>(config.public.endpoint + "/games", {
credentials: "include",
server: false,
});
let games = req.data;
const erika_alive = ref(true);
const router = useRouter();
@@ -19,23 +25,21 @@ function killErika() {
}
onBeforeMount(async () => {
games.value = (await getLobby()).value;
console.log(games.value);
eventSource = subscribe("/games/subscribe");
eventSource.addEventListener("message", (message) => {
const data = JSON.parse(message.data);
if (data.type == "create") {
games.value.push(data);
games.value?.push(data);
} else if (data.type == "delete") {
const index = games.value.findIndex((g) => g._id == data._id);
if (index == -1) {
const index = games.value?.findIndex((g) => g._id == data._id);
if (index == -1 || !index) {
throw new Error("Couldn't delete game");
}
games.value.splice(index, 1);
games.value?.splice(index, 1);
} else if (data.type == "joinGame") {
games.value.find((g) => g._id == data.game)?.teams.push(data.team);
games.value?.find((g) => g._id == data.game)?.teams.push(data.team);
} else if (data.type == "start") {
const targetGame = games.value.find((g) => g._id == data.game);
const targetGame = games.value?.find((g) => g._id == data.game);
if (targetGame) {
targetGame.started = true;
}
@@ -50,58 +54,77 @@ onUnmounted(() => {
<template>
<div class="background">
<div class="title">Entrez dans le Royaume de Héron !</div>
<div class="middle-row">
<div class="create-game-zone">
<button @click="createGame">Create Game</button>
</div>
<div class="games-list-zone">
<div v-for="g in games">
<button
v-if="g"
@click="router.push({ path: `/game`, query:{id:g._id} })"
:class="
authStore.selfUser.userId == g.owner.userId ? 'own_game' : ''
"
>
<template
<header>
<ClientOnly>
<GamePlayerIcon
v-if="authStore.logged"
:user="(authStore.selfUser as User)"
/>
<button v-else @click="navigateTo('/login')">Login</button>
</ClientOnly>
</header>
<main>
<div class="title">Entrez dans le Royaume de Héron !</div>
<ClientOnly>
<template v-if="authStore.logged">
<div class="create-game-zone">
<button
@click="createGame"
v-if="
g.teams.every((t) =>
t.players.every(
(p) => p.user.userId !== authStore.selfUser.userId
)
)
games?.some((g) => g.owner.userId !== authStore.selfUser.userId)
"
>
SPECTATE
</template>
<template v-else>
<template v-if="!g.started"> JOIN BACK </template>
<template v-else> CONTINUE </template>
</template>
Create Game
</button>
</div>
</template>
<br />
{{
g.teams.reduce(
(prev, curr) => prev.concat(curr.players),
[] as Player[]
).length
}}
current players:<br /><span>{{
g.teams
.reduce(
<div class="games-list-zone">
<div v-for="g in games">
<button
v-if="g"
@click="router.push({ path: `/game`, query: { id: g._id } })"
:class="
authStore.selfUser.userId == g.owner.userId ? 'own_game' : ''
"
>
<template
v-if="
g.teams.every((t) =>
t.players.every(
(p) => p.user.userId !== authStore.selfUser.userId
)
)
"
>
SPECTATE
</template>
<template v-else>
<template v-if="!g.started"> JOIN BACK </template>
<template v-else> CONTINUE </template>
</template>
<br />
{{
g.teams.reduce(
(prev, curr) => prev.concat(curr.players),
[] as Player[]
)
.map((p) => p.user.username)
.join(",")
}}</span>
</button>
).length
}}
current players:<br /><span>{{
g.teams
.reduce(
(prev, curr) => prev.concat(curr.players),
[] as Player[]
)
.map((p) => p.user.username)
.join(",")
}}</span>
</button>
</div>
</div>
</div>
</div>
</ClientOnly>
</main>
<div class="overlay" v-if="erika_alive">
<div class="erika-zone">
@@ -142,9 +165,9 @@ onUnmounted(() => {
background-color: #f0f0f0;
background-image: url("https://www.pockettactics.com/wp-content/sites/pockettactics/2023/01/fire-emblem-engage-review-29.jpg");
background-size: cover;
display: flex;
flex-direction: column;
justify-content: center;
display: grid;
grid-template-rows: 100px 1fr;
grid-template-columns: 1fr;
align-items: center;
height: 100vh;
width: 100vw;
@@ -155,39 +178,36 @@ onUnmounted(() => {
font-size: 64px;
color: white;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
margin-bottom: 100px;
}
.middle-row {
main {
display: flex;
flex-direction: column;
gap: 100px;
margin-bottom: 100px;
}
.create-game-zone {
.create-game-zone,
header {
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
justify-content: flex-start;
gap: 10px;
}
header {
flex-direction: row;
backdrop-filter: blur(50px);
height: 100%;
}
.create-game-zone {
flex-direction: column;
}
.create-game-zone button {
background-color: #4caf50;
border: none;
color: white;
text-align: center;
text-decoration: none;
display: inline-block;
font-family: "Roboto", sans-serif;
font-size: 16px;
margin: auto;
cursor: pointer;
border-radius: 12px;
padding: 10px 24px;
height: 50px;
transition-duration: 0.4s;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.games-list-zone {
@@ -198,21 +218,8 @@ onUnmounted(() => {
.games-list-zone button {
background-color: rgba(0, 0, 0, 0.8);
border: none;
color: white;
text-align: center;
text-decoration: none;
display: inline-block;
font-family: "Roboto", sans-serif;
font-size: 16px;
margin: auto;
cursor: pointer;
border-radius: 12px;
padding: 20px 24px;
height: 120px;
transition-duration: 0.4s;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.games-list-zone button.own_game {
+12
View File
@@ -0,0 +1,12 @@
<script setup lang="ts">
const route = useRoute();
const config = useRuntimeConfig();
const code = route.query.code;
if (!code) {
navigateTo(config.public.discordLoginEndpoint, { external: true });
} else {
navigateTo("/login");
}
</script>
+33 -33
View File
@@ -31,10 +31,10 @@ const router = useRouter();
<p>&#8203;</p>
<p>Et quoi de mieux pour faire ça que des cartes colorées?</p>
<div class="card-row">
<CardPreview :card_id="6" :tilted="false"></CardPreview>
<CardPreview :card_id="19" :tilted="false"></CardPreview>
<CardPreview :card_id="36" :tilted="false"></CardPreview>
<CardPreview :card_id="46" :tilted="false"></CardPreview>
<GameCard :cardId="6" :tilted="false"></GameCard>
<GameCard :cardId="19" :tilted="false"></GameCard>
<GameCard :cardId="36" :tilted="false"></GameCard>
<GameCard :cardId="46" :tilted="false"></GameCard>
</div>
<p>&#8203;</p>
<hr />
@@ -60,7 +60,7 @@ const router = useRouter();
"deck" et ce qui nous permet de le "build".
</p>
<div class="card-row">
<CardPreview :card_id="undefined" :tilted="false"></CardPreview>
<GameCard :cardId="undefined" :tilted="false"></GameCard>
</div>
<p>&#8203;</p>
<p>
@@ -87,10 +87,10 @@ const router = useRouter();
<li>1x Dégats (2)</li>
</ul>
<div class="card-row">
<CardPreview :card_id="56" :tilted="false"></CardPreview>
<CardPreview :card_id="59" :tilted="false"></CardPreview>
<CardPreview :card_id="58" :tilted="false"></CardPreview>
<CardPreview :card_id="57" :tilted="false"></CardPreview>
<GameCard :cardId="56" :tilted="false"></GameCard>
<GameCard :cardId="59" :tilted="false"></GameCard>
<GameCard :cardId="58" :tilted="false"></GameCard>
<GameCard :cardId="57" :tilted="false"></GameCard>
</div>
<p>
La pioche est formée au hasard: on peut donc avoir une main de départ
@@ -112,15 +112,15 @@ const router = useRouter();
champions.
</p>
<div class="card-row">
<CardPreview :card_id="56" :tilted="false"></CardPreview>
<CardPreview :card_id="52" :tilted="false"></CardPreview>
<CardPreview :card_id="43" :tilted="false"></CardPreview>
<GameCard :cardId="56" :tilted="false"></GameCard>
<GameCard :cardId="52" :tilted="false"></GameCard>
<GameCard :cardId="43" :tilted="false"></GameCard>
</div>
<p>&#8203;</p>
<h3>Objets & actions</h3>
<div class="card-row">
<CardPreview :card_id="56" :tilted="false"></CardPreview>
<CardPreview :card_id="52" :tilted="false"></CardPreview>
<GameCard :cardId="56" :tilted="false"></GameCard>
<GameCard :cardId="52" :tilted="false"></GameCard>
</div>
<p>
Par simplicité, disons que les objets et les actions sont pareil: on
@@ -129,7 +129,7 @@ const router = useRouter();
</p>
<h3>Champions (& gardes)</h3>
<div class="card-row">
<CardPreview :card_id="43" :tilted="false"></CardPreview>
<GameCard :cardId="43" :tilted="false"></GameCard>
</div>
<p>
Les champions sont un peu plus complexes: ils restent sur le terrain,
@@ -150,7 +150,7 @@ const router = useRouter();
de dégats pendant un tour, et un peu le tour d'après pour les tuer.
</p>
<div class="card-row">
<CardPreview :card_id="49" :tilted="false"></CardPreview>
<GameCard :cardId="49" :tilted="false"></GameCard>
</div>
<p>
Par ailleurs, certains champions sont des Gardes
@@ -180,10 +180,10 @@ const router = useRouter();
exclusivement à accomplir ce but: acheter des cartes.
</p>
<div class="card-row">
<CardPreview :card_id="6" :tilted="false"></CardPreview>
<CardPreview :card_id="19" :tilted="false"></CardPreview>
<CardPreview :card_id="36" :tilted="false"></CardPreview>
<CardPreview :card_id="46" :tilted="false"></CardPreview>
<GameCard :cardId="6" :tilted="false"></GameCard>
<GameCard :cardId="19" :tilted="false"></GameCard>
<GameCard :cardId="36" :tilted="false"></GameCard>
<GameCard :cardId="46" :tilted="false"></GameCard>
</div>
<p>
Un marché est mis à disposition de tous les joueurs, mettant à
@@ -208,9 +208,9 @@ const router = useRouter();
</p>
<p>&#8203;</p>
<div class="card-row">
<CardPreview :card_id="13" :tilted="false"></CardPreview>
<CardPreview :card_id="3" :tilted="false"></CardPreview>
<CardPreview :card_id="1" :tilted="false"></CardPreview>
<GameCard :cardId="13" :tilted="false"></GameCard>
<GameCard :cardId="3" :tilted="false"></GameCard>
<GameCard :cardId="1" :tilted="false"></GameCard>
</div>
<p>&#8203;</p>
<p>
@@ -222,9 +222,9 @@ const router = useRouter();
</p>
<p>&#8203;</p>
<div class="card-row">
<CardPreview :card_id="39" :tilted="false"></CardPreview>
<CardPreview :card_id="34" :tilted="false"></CardPreview>
<CardPreview :card_id="40" :tilted="false"></CardPreview>
<GameCard :cardId="39" :tilted="false"></GameCard>
<GameCard :cardId="34" :tilted="false"></GameCard>
<GameCard :cardId="40" :tilted="false"></GameCard>
</div>
<p>&#8203;</p>
<p>
@@ -237,9 +237,9 @@ const router = useRouter();
</p>
<p>&#8203;</p>
<div class="card-row">
<CardPreview :card_id="52" :tilted="false"></CardPreview>
<CardPreview :card_id="46" :tilted="false"></CardPreview>
<CardPreview :card_id="47" :tilted="false"></CardPreview>
<GameCard :cardId="52" :tilted="false"></GameCard>
<GameCard :cardId="46" :tilted="false"></GameCard>
<GameCard :cardId="47" :tilted="false"></GameCard>
</div>
<p>&#8203;</p>
<p>
@@ -252,9 +252,9 @@ const router = useRouter();
</p>
<p>&#8203;</p>
<div class="card-row">
<CardPreview :card_id="24" :tilted="false"></CardPreview>
<CardPreview :card_id="25" :tilted="false"></CardPreview>
<CardPreview :card_id="19" :tilted="false"></CardPreview>
<GameCard :cardId="24" :tilted="false"></GameCard>
<GameCard :cardId="25" :tilted="false"></GameCard>
<GameCard :cardId="19" :tilted="false"></GameCard>
</div>
<p>&#8203;</p>
<p>
@@ -272,7 +272,7 @@ const router = useRouter();
Les effets de cartes peuvent se déclencher pour 3 raisons différentes.
</p>
<div class="card-row">
<CardPreview :card_id="14" :tilted="false"></CardPreview>
<GameCard :cardId="14" :tilted="false"></GameCard>
</div>
<p>Sur cette carte, on peut voir les trois en même temps.</p>
<p>La 1ère ligne s'active tout le temps.</p>
+11
View File
@@ -0,0 +1,11 @@
<script setup lang="ts">
const config = useRuntimeConfig();
config.public.welcomeMessage;
</script>
<template>
<div>
{{ config.public.welcomeMessage }}
<GameCard :cardId="6" :tilted="false"></GameCard>
</div>
</template>
+116 -59
View File
@@ -30,17 +30,18 @@
*/
/* global define */
(function(root, factory) { // eslint-disable-line
if (typeof define === 'function' && define.amd) {
(function (root, factory) {
// eslint-disable-line
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define([], function() {
define([], function () {
return factory.call(root);
});
} else {
// Browser globals
root.webglUtils = factory.call(root);
}
}(this, function() {
})(this, function () {
"use strict";
const topWindow = this;
@@ -53,8 +54,8 @@
}
if (!isInIFrame()) {
console.log("%c%s", 'color:blue;font-weight:bold;', 'for more about webgl-utils.js see:'); // eslint-disable-line
console.log("%c%s", 'color:blue;font-weight:bold;', 'http://webgl2fundamentals.org/webgl/lessons/webgl-boilerplate.html'); // eslint-disable-line
// console.log("%c%s", 'color:blue;font-weight:bold;', 'for more about webgl-utils.js see:'); // eslint-disable-line
// console.log("%c%s", 'color:blue;font-weight:bold;', 'http://webgl2fundamentals.org/webgl/lessons/webgl-boilerplate.html'); // eslint-disable-line
}
/**
@@ -72,23 +73,27 @@
}
const errorRE = /ERROR:\s*\d+:(\d+)/gi;
function addLineNumbersWithError(src, log = '') {
function addLineNumbersWithError(src, log = "") {
// Note: Error message formats are not defined by any spec so this may or may not work.
const matches = [...log.matchAll(errorRE)];
const lineNoToErrorMap = new Map(matches.map((m, ndx) => {
const lineNo = parseInt(m[1]);
const next = matches[ndx + 1];
const end = next ? next.index : log.length;
const msg = log.substring(m.index, end);
return [lineNo - 1, msg];
}));
return src.split('\n').map((line, lineNo) => {
const err = lineNoToErrorMap.get(lineNo);
return `${lineNo + 1}: ${line}${err ? `\n\n^^^ ${err}` : ''}`;
}).join('\n');
const lineNoToErrorMap = new Map(
matches.map((m, ndx) => {
const lineNo = parseInt(m[1]);
const next = matches[ndx + 1];
const end = next ? next.index : log.length;
const msg = log.substring(m.index, end);
return [lineNo - 1, msg];
})
);
return src
.split("\n")
.map((line, lineNo) => {
const err = lineNoToErrorMap.get(lineNo);
return `${lineNo + 1}: ${line}${err ? `\n\n^^^ ${err}` : ""}`;
})
.join("\n");
}
/**
* Error Callback
* @callback ErrorCallback
@@ -120,7 +125,12 @@
if (!compiled) {
// Something went wrong during compilation; get the error
const lastError = gl.getShaderInfoLog(shader);
errFn(`Error compiling shader: ${lastError}\n${addLineNumbersWithError(shaderSource, lastError)}`);
errFn(
`Error compiling shader: ${lastError}\n${addLineNumbersWithError(
shaderSource,
lastError
)}`
);
gl.deleteShader(shader);
return null;
}
@@ -139,18 +149,24 @@
* @memberOf module:webgl-utils
*/
function createProgram(
gl, shaders, opt_attribs, opt_locations, opt_errorCallback) {
gl,
shaders,
opt_attribs,
opt_locations,
opt_errorCallback
) {
const errFn = opt_errorCallback || error;
const program = gl.createProgram();
shaders.forEach(function(shader) {
shaders.forEach(function (shader) {
gl.attachShader(program, shader);
});
if (opt_attribs) {
opt_attribs.forEach(function(attrib, ndx) {
opt_attribs.forEach(function (attrib, ndx) {
gl.bindAttribLocation(
program,
opt_locations ? opt_locations[ndx] : ndx,
attrib);
program,
opt_locations ? opt_locations[ndx] : ndx,
attrib
);
});
}
gl.linkProgram(program);
@@ -158,18 +174,20 @@
// Check the link status
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
// something went wrong with the link
const lastError = gl.getProgramInfoLog(program);
errFn(`Error in program linking: ${lastError}\n${
shaders.map(shader => {
// something went wrong with the link
const lastError = gl.getProgramInfoLog(program);
errFn(
`Error in program linking: ${lastError}\n${shaders
.map((shader) => {
const src = addLineNumbersWithError(gl.getShaderSource(shader));
const type = gl.getShaderParameter(shader, gl.SHADER_TYPE);
return `${glEnumToString(gl, type)}:\n${src}`;
}).join('\n')
}`);
})
.join("\n")}`
);
gl.deleteProgram(program);
return null;
gl.deleteProgram(program);
return null;
}
return program;
}
@@ -184,12 +202,16 @@
* @return {WebGLShader} The created shader.
*/
function createShaderFromScript(
gl, scriptId, opt_shaderType, opt_errorCallback) {
gl,
scriptId,
opt_shaderType,
opt_errorCallback
) {
let shaderSource = "";
let shaderType;
const shaderScript = document.getElementById(scriptId);
if (!shaderScript) {
throw ("*** Error: unknown script element" + scriptId);
throw "*** Error: unknown script element" + scriptId;
}
shaderSource = shaderScript.text;
@@ -198,20 +220,23 @@
shaderType = gl.VERTEX_SHADER;
} else if (shaderScript.type === "x-shader/x-fragment") {
shaderType = gl.FRAGMENT_SHADER;
} else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) {
throw ("*** Error: unknown shader type");
} else if (
shaderType !== gl.VERTEX_SHADER &&
shaderType !== gl.FRAGMENT_SHADER
) {
throw "*** Error: unknown shader type";
}
}
return loadShader(
gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
opt_errorCallback);
gl,
shaderSource,
opt_shaderType ? opt_shaderType : shaderType,
opt_errorCallback
);
}
const defaultShaderType = [
"VERTEX_SHADER",
"FRAGMENT_SHADER",
];
const defaultShaderType = ["VERTEX_SHADER", "FRAGMENT_SHADER"];
/**
* Creates a program from 2 script tags.
@@ -229,13 +254,30 @@
* @memberOf module:webgl-utils
*/
function createProgramFromScripts(
gl, shaderScriptIds, opt_attribs, opt_locations, opt_errorCallback) {
gl,
shaderScriptIds,
opt_attribs,
opt_locations,
opt_errorCallback
) {
const shaders = [];
for (let ii = 0; ii < shaderScriptIds.length; ++ii) {
shaders.push(createShaderFromScript(
gl, shaderScriptIds[ii], gl[defaultShaderType[ii]], opt_errorCallback));
shaders.push(
createShaderFromScript(
gl,
shaderScriptIds[ii],
gl[defaultShaderType[ii]],
opt_errorCallback
)
);
}
return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
return createProgram(
gl,
shaders,
opt_attribs,
opt_locations,
opt_errorCallback
);
}
/**
@@ -254,13 +296,30 @@
* @memberOf module:webgl-utils
*/
function createProgramFromSources(
gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {
gl,
shaderSources,
opt_attribs,
opt_locations,
opt_errorCallback
) {
const shaders = [];
for (let ii = 0; ii < shaderSources.length; ++ii) {
shaders.push(loadShader(
gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback));
shaders.push(
loadShader(
gl,
shaderSources[ii],
gl[defaultShaderType[ii]],
opt_errorCallback
)
);
}
return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
return createProgram(
gl,
shaders,
opt_attribs,
opt_locations,
opt_errorCallback
);
}
/**
@@ -273,10 +332,10 @@
*/
function resizeCanvasToDisplaySize(canvas, multiplier) {
multiplier = multiplier || 1;
const width = canvas.clientWidth * multiplier | 0;
const height = canvas.clientHeight * multiplier | 0;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
const width = (canvas.clientWidth * multiplier) | 0;
const height = (canvas.clientHeight * multiplier) | 0;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
return true;
}
@@ -289,6 +348,4 @@
createProgramFromSources: createProgramFromSources,
resizeCanvasToDisplaySize: resizeCanvasToDisplaySize,
};
}));
});
+2 -2
View File
@@ -42,7 +42,7 @@ export async function compileCards(app: App<any>) {
try {
let template: string;
if (process.dev && !process.client) {
template = (await import(k + "?raw" /* @vite-ignore */)).default;
template = (await import(/* @vite-ignore */ k + "?raw")).default;
} else {
template = await (await fetch(url)).text();
}
@@ -56,7 +56,7 @@ export async function compileCards(app: App<any>) {
defineAsyncComponent({
loader: async () =>
defineComponent({
props: ["used_effects_indexes", "card"],
props: ["used_effects_indexes", "cardId"],
methods: {
effectClick(effect: number) {
this.$emit("effectClick", effect);
+2 -1
View File
@@ -14,7 +14,8 @@ export const useSettingsStore = defineStore("settingstore", {
shaderQuality: 3,
shaderFramerate: 10,
shaderPixelated: true,
enableDragging: false,
saveDiscardSize: false,
discardFullscreen: false,
}),
actions: {
switchCardStyles() {