feat: card lock
This commit is contained in:
@@ -1,100 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
const settingsStore = useSettingsStore()
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
import gsap from 'gsap';
|
||||
import ShaderWorker from '~/assets/workers/ShaderWorker?worker'
|
||||
import type { Game } from '~/netcode/interfaces';
|
||||
import gsap from "gsap";
|
||||
import ShaderWorker from "~/assets/workers/ShaderWorker?worker";
|
||||
import type { Game } from "~/netcode/interfaces";
|
||||
|
||||
const emit = defineEmits(["ready"])
|
||||
const worker = new ShaderWorker()
|
||||
const emit = defineEmits<{
|
||||
ready: [];
|
||||
}>();
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
const canvas_resize_temp = ref<HTMLCanvasElement | null>(null)
|
||||
const worker = new ShaderWorker();
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||
const canvas_resize_temp = ref<HTMLCanvasElement | null>(null);
|
||||
|
||||
export interface Props {
|
||||
colorStart?: [number, number, number]
|
||||
colorEnd?: [number, number, number]
|
||||
colorStart?: [number, number, number];
|
||||
colorEnd?: [number, number, number];
|
||||
}
|
||||
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
colorStart: () => [0, .3, 0],
|
||||
colorEnd: () => [0, 1, 0]
|
||||
})
|
||||
|
||||
const _colorStart = reactive(props.colorStart)
|
||||
const _colorEnd = reactive(props.colorEnd)
|
||||
colorStart: () => [0, 0.3, 0],
|
||||
colorEnd: () => [0, 1, 0],
|
||||
});
|
||||
|
||||
const _colorStart = reactive(props.colorStart);
|
||||
const _colorEnd = reactive(props.colorEnd);
|
||||
|
||||
function initShader() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!canvas.value) {
|
||||
reject("canvas is null");
|
||||
return
|
||||
}
|
||||
const offscreen = canvas.value?.transferControlToOffscreen()
|
||||
worker.postMessage({
|
||||
command: "initShader",
|
||||
canvas: offscreen,
|
||||
screen: { height: window.innerHeight * devicePixelRatio, width: window.innerWidth * devicePixelRatio, devicePixelRatio },
|
||||
shaderOptions: { quality: settingsStore.shaderQuality, framerate: settingsStore.shaderFramerate },
|
||||
}, [offscreen])
|
||||
if (settingsStore.shaderFramerate == 0) {
|
||||
killShader()
|
||||
}
|
||||
resizeShader()
|
||||
worker.onmessage = (e) => {
|
||||
if (e.data == "ready") {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!canvas.value) {
|
||||
reject("canvas is null");
|
||||
return;
|
||||
}
|
||||
const offscreen = canvas.value?.transferControlToOffscreen();
|
||||
worker.postMessage(
|
||||
{
|
||||
command: "initShader",
|
||||
canvas: offscreen,
|
||||
screen: {
|
||||
height: window.innerHeight * devicePixelRatio,
|
||||
width: window.innerWidth * devicePixelRatio,
|
||||
devicePixelRatio,
|
||||
},
|
||||
shaderOptions: {
|
||||
quality: settingsStore.shaderQuality,
|
||||
framerate: settingsStore.shaderFramerate,
|
||||
},
|
||||
},
|
||||
[offscreen]
|
||||
);
|
||||
if (settingsStore.shaderFramerate == 0) {
|
||||
killShader();
|
||||
}
|
||||
resizeShader();
|
||||
worker.onmessage = (e) => {
|
||||
if (e.data == "ready") {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function recolorShader() {
|
||||
worker.postMessage({
|
||||
command: 'recolorShader',
|
||||
colorStart: [..._colorStart],
|
||||
colorEnd: [..._colorEnd]
|
||||
})
|
||||
worker.postMessage({
|
||||
command: "recolorShader",
|
||||
colorStart: [..._colorStart],
|
||||
colorEnd: [..._colorEnd],
|
||||
});
|
||||
}
|
||||
|
||||
function resizeShader() {
|
||||
worker.postMessage({
|
||||
command: 'resizeShader',
|
||||
screen: { height: window.innerHeight * devicePixelRatio, width: window.innerWidth * devicePixelRatio, devicePixelRatio },
|
||||
shaderOptions: { quality: settingsStore.shaderQuality, framerate: settingsStore.shaderFramerate },
|
||||
})
|
||||
worker.postMessage({
|
||||
command: "resizeShader",
|
||||
screen: {
|
||||
height: window.innerHeight * devicePixelRatio,
|
||||
width: window.innerWidth * devicePixelRatio,
|
||||
devicePixelRatio,
|
||||
},
|
||||
shaderOptions: {
|
||||
quality: settingsStore.shaderQuality,
|
||||
framerate: settingsStore.shaderFramerate,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function killShader() {
|
||||
worker.postMessage({
|
||||
command: 'killShader'
|
||||
})
|
||||
worker.postMessage({
|
||||
command: "killShader",
|
||||
});
|
||||
}
|
||||
|
||||
function resumeShader() {
|
||||
worker.postMessage({
|
||||
command: 'resumeShader',
|
||||
shaderOptions: { quality: settingsStore.shaderQuality, framerate: settingsStore.shaderFramerate },
|
||||
})
|
||||
worker.postMessage({
|
||||
command: "resumeShader",
|
||||
shaderOptions: {
|
||||
quality: settingsStore.shaderQuality,
|
||||
framerate: settingsStore.shaderFramerate,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
watch(() => [settingsStore.shaderFramerate], () => {
|
||||
watch(
|
||||
() => [settingsStore.shaderFramerate],
|
||||
() => {
|
||||
if (settingsStore.shaderFramerate == 0) {
|
||||
killShader()
|
||||
return
|
||||
killShader();
|
||||
return;
|
||||
}
|
||||
resumeShader()
|
||||
resumeShader();
|
||||
}
|
||||
);
|
||||
|
||||
})
|
||||
|
||||
watch(() => [settingsStore.shaderQuality], () => {
|
||||
resizeShader()
|
||||
})
|
||||
watch(
|
||||
() => [settingsStore.shaderQuality],
|
||||
() => {
|
||||
resizeShader();
|
||||
}
|
||||
);
|
||||
|
||||
// watch(() => [props.game.currentTurn], () => {
|
||||
// if (props.game.teams.find() != goStore.self?.team) {
|
||||
@@ -106,38 +129,43 @@ watch(() => [settingsStore.shaderQuality], () => {
|
||||
// }
|
||||
// }, { immediate: true })
|
||||
|
||||
|
||||
watch([_colorEnd, _colorStart], () => {
|
||||
recolorShader()
|
||||
})
|
||||
recolorShader();
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
recolorShader()
|
||||
await initShader()
|
||||
window.addEventListener("resize", resizeShader, true)
|
||||
emit("ready")
|
||||
})
|
||||
recolorShader();
|
||||
await initShader();
|
||||
window.addEventListener("resize", resizeShader, true);
|
||||
emit("ready");
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", resizeShader)
|
||||
killShader()
|
||||
})
|
||||
window.removeEventListener("resize", resizeShader);
|
||||
killShader();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas ref="canvas" id="background" :class="{ pixelated: settingsStore.shaderPixelated }" width="480"
|
||||
height="270">test</canvas>
|
||||
<canvas
|
||||
ref="canvas"
|
||||
id="background"
|
||||
:class="{ pixelated: settingsStore.shaderPixelated }"
|
||||
width="480"
|
||||
height="270"
|
||||
>test</canvas
|
||||
>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#background {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#background.pixelated {
|
||||
image-rendering: pixelated;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
</style>
|
||||
+12
-46
@@ -1,6 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
// import goStore from '~/netcode';
|
||||
// import { useSettingsStore } from '~/stores/settingsStore';
|
||||
|
||||
export interface Props {
|
||||
uuid?: string
|
||||
@@ -90,9 +88,6 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -121,47 +116,6 @@ onMounted(() => {
|
||||
height: var(--max-height);
|
||||
}
|
||||
|
||||
.card_image.outlined {
|
||||
outline: 2px solid red;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
@keyframes unlocked-float {
|
||||
from {
|
||||
transform: none;
|
||||
filter: drop-shadow(calc(var(--margin-bottom)*.3) var(--margin-bottom) 1px rgba(0, 0, 0, .60));
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(calc(var(--margin-bottom)*-.25));
|
||||
filter: drop-shadow(calc(var(--margin-bottom)*.3) calc(var(--margin-bottom)*1.5) 1px rgba(0, 0, 0, .60));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes unlocked-rotate {
|
||||
from {
|
||||
rotate: -2deg;
|
||||
}
|
||||
|
||||
to {
|
||||
rotate: 2deg;
|
||||
}
|
||||
}
|
||||
|
||||
.card.unlocked {
|
||||
margin-top: 0px;
|
||||
margin-bottom: var(--margin-bottom);
|
||||
filter: drop-shadow(calc(var(--margin-bottom)*.3) var(--margin-bottom) 1px rgba(0, 0, 0, .60));
|
||||
animation: 2s infinite alternate ease-in-out unlocked-float, 3.1s infinite alternate ease-in-out unlocked-rotate;
|
||||
animation-delay: var(--random-seed);
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
.card:not(.unlocked):hover {
|
||||
animation-play-state: paused;
|
||||
rotate: none !important;
|
||||
}
|
||||
|
||||
.card.wrapped {
|
||||
height: 6px;
|
||||
margin: 0;
|
||||
@@ -171,6 +125,14 @@ onMounted(() => {
|
||||
filter: drop-shadow(.5px .5px .5px gray) drop-shadow(-.5px -.5px .5px gray);
|
||||
}
|
||||
|
||||
.card.unlocked {
|
||||
margin-top: 0px;
|
||||
margin-bottom: var(--margin-bottom);
|
||||
transform: translateY(calc(var(--margin-bottom)*-.25));
|
||||
filter: drop-shadow(calc(var(--margin-bottom)*.3) var(--margin-bottom) 1px rgba(0, 0, 0, .60));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card.zoom {
|
||||
transform: scale(200%);
|
||||
z-index: 10;
|
||||
@@ -194,6 +156,10 @@ onMounted(() => {
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.card.unlocked>* {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#contextMenuButtons>* {
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
|
||||
+87
-71
@@ -1,133 +1,149 @@
|
||||
<script setup lang="ts">
|
||||
import { containerEvents, delay } from '~/netcode/events';
|
||||
import type { Container } from '~/netcode/interfaces';
|
||||
import { containerEvents, delay } from "~/netcode/events";
|
||||
import type { Container, PlayingTurn } from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
container: Container
|
||||
wrapped?: boolean
|
||||
hidden?: boolean
|
||||
container: Container;
|
||||
playingTurn?: PlayingTurn;
|
||||
wrapped?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
wrapped: () => false,
|
||||
hidden: () => false
|
||||
})
|
||||
wrapped: () => false,
|
||||
hidden: () => false,
|
||||
});
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const emit = defineEmits(["cardClick"]);
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const cards = computed(() => {
|
||||
if (props.hidden) {
|
||||
return props.container.cards.map((_v, i) => ({ _id: i, cardId: undefined }))
|
||||
}
|
||||
return props.container.cards
|
||||
})
|
||||
if (props.hidden) {
|
||||
return props.container.cards.map((_v, i) => ({
|
||||
_id: i,
|
||||
cardId: undefined,
|
||||
}));
|
||||
}
|
||||
return props.container.cards;
|
||||
});
|
||||
|
||||
const animationSpeed = computed(() => settingsStore.animationSpeed + 'ms')
|
||||
const shuffleAnimationSpeed = computed(() => settingsStore.animationSpeed * 2 + 'ms')
|
||||
function populateContainer() {
|
||||
const animationSpeed = computed(() => settingsStore.animationSpeed + "ms");
|
||||
const shuffleAnimationSpeed = computed(
|
||||
() => settingsStore.animationSpeed * 2 + "ms"
|
||||
);
|
||||
function populateContainer() {}
|
||||
|
||||
}
|
||||
|
||||
const rotate = ref(false)
|
||||
const rotate = ref(false);
|
||||
async function shuffleContainer() {
|
||||
console.log("rotate")
|
||||
rotate.value = true
|
||||
await delay(settingsStore.animationSpeed * 2)
|
||||
rotate.value = false
|
||||
await delay(settingsStore.animationSpeed * 2)
|
||||
rotate.value = true;
|
||||
await delay(settingsStore.animationSpeed * 2);
|
||||
rotate.value = false;
|
||||
await delay(settingsStore.animationSpeed * 2);
|
||||
}
|
||||
|
||||
function getCardsPosition(cards: string) {}
|
||||
|
||||
function getCardsPosition(cards: string) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
const animationCallbacks = ref<Array<() => void>>([])
|
||||
const animationCallbacks = ref<Array<() => void>>([]);
|
||||
|
||||
function startAnimation() {
|
||||
animationCallbacks.value.forEach(p => p())
|
||||
animationCallbacks.value = []
|
||||
animationCallbacks.value.forEach((p) => p());
|
||||
animationCallbacks.value = [];
|
||||
}
|
||||
|
||||
function endAnimation() {
|
||||
animationCallbacks.value.forEach(p => p())
|
||||
animationCallbacks.value.forEach((p) => p());
|
||||
}
|
||||
|
||||
containerEvents.set(props.container._id, { populateContainer, shuffleContainer, getCardsPosition, startAnimation, endAnimation })
|
||||
|
||||
containerEvents.set(props.container._id, {
|
||||
populateContainer,
|
||||
shuffleContainer,
|
||||
getCardsPosition,
|
||||
startAnimation,
|
||||
endAnimation,
|
||||
});
|
||||
|
||||
async function onEnter(_el: any, done: () => void) {
|
||||
animationCallbacks.value.push(done)
|
||||
animationCallbacks.value.push(done);
|
||||
}
|
||||
|
||||
async function onLeave(_el: any, done: () => void) {
|
||||
animationCallbacks.value.push(done)
|
||||
animationCallbacks.value.push(done);
|
||||
}
|
||||
|
||||
async function onBeforeEnter() {
|
||||
|
||||
}
|
||||
async function onBeforeEnter() {}
|
||||
|
||||
onUnmounted(() => {
|
||||
containerEvents.delete(props.container._id)
|
||||
})
|
||||
containerEvents.delete(props.container._id);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="cards_container" :class="{ wrapped, rotate }">
|
||||
<TransitionGroup name="list" @enter="onEnter" @leave="onLeave" @before-enter="onBeforeEnter">
|
||||
<CardPreview :card_id="c.cardId" :wrapped="wrapped" v-for="c in cards" :key="c._id"
|
||||
:uuid="c._id.toString()">
|
||||
<slot></slot>
|
||||
</CardPreview>
|
||||
</TransitionGroup>
|
||||
<!-- <img :class="`card_image`" :src="'/cards/background.png'" draggable="false"> -->
|
||||
</div>
|
||||
<div class="cards_container" :class="{ wrapped, rotate }">
|
||||
<TransitionGroup
|
||||
name="list"
|
||||
@enter="onEnter"
|
||||
@leave="onLeave"
|
||||
@before-enter="onBeforeEnter"
|
||||
>
|
||||
<CardPreview
|
||||
@click="$emit('cardClick', c._id)"
|
||||
v-for="c in cards"
|
||||
:card_id="c.cardId"
|
||||
:wrapped="wrapped"
|
||||
:key="c._id"
|
||||
:uuid="c._id.toString()"
|
||||
:locked="
|
||||
!playingTurn || playingTurn?.cardsLocked.includes(c._id.toString())
|
||||
"
|
||||
>
|
||||
</CardPreview>
|
||||
</TransitionGroup>
|
||||
<!-- <img :class="`card_image`" :src="'/cards/background.png'" draggable="false"> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all v-bind('animationSpeed') ease;
|
||||
transition: all v-bind("animationSpeed") ease;
|
||||
}
|
||||
|
||||
|
||||
.list-leave-to,
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
.rotate {
|
||||
rotate: 360deg;
|
||||
rotate: 360deg;
|
||||
}
|
||||
|
||||
.cards_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
min-width: 200px;
|
||||
transition: rotate v-bind('shuffleAnimationSpeed') ease;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
min-width: 200px;
|
||||
transition: rotate v-bind("shuffleAnimationSpeed") ease;
|
||||
}
|
||||
|
||||
.cards_container.wrapped {
|
||||
flex-direction: column-reverse;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.cards_container.wrapped:before {
|
||||
height: calc(250px - 10px);
|
||||
display: block;
|
||||
content: "";
|
||||
width: 0;
|
||||
height: calc(250px - 10px);
|
||||
display: block;
|
||||
content: "";
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.empty {
|
||||
max-height: 10px;
|
||||
max-height: 10px;
|
||||
}
|
||||
|
||||
.empty>.card_image {
|
||||
filter: brightness(0.5);
|
||||
max-height: 250px;
|
||||
.empty > .card_image {
|
||||
filter: brightness(0.5);
|
||||
max-height: 250px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,30 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
const displayOptions = ref(false)
|
||||
|
||||
const displayOptions = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="`card_overlay`" ref="card">
|
||||
<img class="settings_button" src="/images/settings.png" alt="settings" @click="displayOptions = true" />
|
||||
</div>
|
||||
<OptionsDialog :isVisible="displayOptions" @close="displayOptions = false" />
|
||||
<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;
|
||||
/* 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;
|
||||
pointer-events: all;
|
||||
width: 30px;
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
+40
-47
@@ -1,16 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { Container } from '~/netcode/interfaces';
|
||||
|
||||
|
||||
import type { Container } from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
market: Container
|
||||
fire_gems: Container
|
||||
market: Container;
|
||||
fire_gems: Container;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// const selfPlayer = computed(() => authStore.logged ? playerList.value?.find(p => p.user.userId == authStore.selfUser.userId) : undefined)
|
||||
|
||||
@@ -19,58 +17,53 @@ const authStore = useAuthStore()
|
||||
|
||||
// const buyableFiregem = computed(() => goStore?.current_game?.gem_stack.at(-1))
|
||||
|
||||
|
||||
function onBeforeEnter(el: Element) {
|
||||
(el as HTMLElement).style.visibility = "hidden"
|
||||
const element = el as HTMLElement
|
||||
element.style.top = element.offsetTop + "px"
|
||||
element.style.left = element.offsetLeft + "px"
|
||||
element.style.zIndex = "999"
|
||||
function marketCardClick(e: string) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
function onEnter(el: any, done: () => unknown) {
|
||||
console.log("enter")
|
||||
// setTimeout(() => {
|
||||
// el.style.visibility = null
|
||||
// done()
|
||||
// }, 1000)
|
||||
done()
|
||||
|
||||
function fireGemCardClick(e: string) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="market">
|
||||
<CardPreview :tilted="false" :card_id="undefined"></CardPreview>
|
||||
<div class="separator"></div>
|
||||
<CardsGrouped :container="market"></CardsGrouped>
|
||||
<div class="separator"></div>
|
||||
<CardsGrouped :container="fire_gems" :wrapped="true"></CardsGrouped>
|
||||
</div>
|
||||
<div id="market">
|
||||
<CardPreview :tilted="false" :card_id="undefined"></CardPreview>
|
||||
<div class="separator"></div>
|
||||
<CardsGrouped
|
||||
:container="market"
|
||||
@cardClick="marketCardClick"
|
||||
></CardsGrouped>
|
||||
<div class="separator"></div>
|
||||
<CardsGrouped
|
||||
:container="fire_gems"
|
||||
:wrapped="true"
|
||||
@cardClick="fireGemCardClick"
|
||||
></CardsGrouped>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#market {
|
||||
width: min-content;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2;
|
||||
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, .60));
|
||||
backdrop-filter: blur(5px) brightness(80%);
|
||||
background: rgba(155, 62, 62, 0.46);
|
||||
width: min-content;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2;
|
||||
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(155, 62, 62, 0.46);
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,23 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
export interface Props {
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
const _devicePixelRatio = computed(() => devicePixelRatio)
|
||||
const _window = computed(() => window)
|
||||
const _devicePixelRatio = computed(() => devicePixelRatio);
|
||||
const _window = computed(() => window);
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const emit = defineEmits(["close"]);
|
||||
|
||||
const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
emit("close");
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -30,7 +27,9 @@ const close = () => {
|
||||
<span>Toggle Card Style</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<button @click="settingsStore.switchCardStyles">Toggle Card style</button>
|
||||
<button @click="settingsStore.switchCardStyles">
|
||||
Toggle Card style
|
||||
</button>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span>{{ settingsStore.cardStyle }}</span>
|
||||
@@ -39,7 +38,13 @@ const close = () => {
|
||||
<span>Animation Speed</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="range" min="50" max="250" step="25" v-model="settingsStore.animationSpeed">
|
||||
<input
|
||||
type="range"
|
||||
min="50"
|
||||
max="250"
|
||||
step="25"
|
||||
v-model="settingsStore.animationSpeed"
|
||||
/>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span>{{ settingsStore.animationSpeed }}ms</span>
|
||||
@@ -51,19 +56,44 @@ const close = () => {
|
||||
<span>Shader Quality</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="range" min="1" max="10" v-model="settingsStore.shaderQuality"
|
||||
:disabled="settingsStore.shaderFramerate == 0">
|
||||
<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>
|
||||
<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">
|
||||
<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>
|
||||
@@ -73,7 +103,7 @@ const close = () => {
|
||||
<span>Pixelated</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="checkbox" v-model="settingsStore.shaderPixelated">
|
||||
<input type="checkbox" v-model="settingsStore.shaderPixelated" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,11 +122,10 @@ const close = () => {
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
display: grid;
|
||||
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
background-image: url('../images/bg-grass.png');
|
||||
background-image: url("../img/bg-grass.png");
|
||||
padding: 0 20px;
|
||||
border-radius: 5px;
|
||||
width: 800px;
|
||||
@@ -111,7 +140,7 @@ const close = () => {
|
||||
|
||||
.dialog-box h3 {
|
||||
font-size: 30px;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
|
||||
+219
-190
@@ -1,31 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { endTurn } from "~/netcode";
|
||||
import type { Game, Player } from '~/netcode/interfaces';
|
||||
import { useClientSideStore } from "~/stores/turn";
|
||||
import { endTurn, lockCard } from "~/netcode";
|
||||
import type { Game, Player } from "~/netcode/interfaces";
|
||||
import { useTurnStore } from "~/stores/turnStore";
|
||||
|
||||
export interface Props {
|
||||
player: Player;
|
||||
game: Game,
|
||||
hide_stack_and_discard?: boolean;
|
||||
player: Player;
|
||||
game: Game;
|
||||
hide_stack_and_discard?: boolean;
|
||||
}
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const authStore = useAuthStore();
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
hide_stack_and_discard: () => false,
|
||||
})
|
||||
const playerList = computed(() => props.game.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[]))
|
||||
const selfPlayer = computed(() => authStore.logged ? playerList.value?.find(p => p.user.userId == authStore.selfUser.userId) : undefined)
|
||||
hide_stack_and_discard: () => false,
|
||||
});
|
||||
const playerList = computed(() =>
|
||||
props.game.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
|
||||
);
|
||||
const selfPlayer = computed(() =>
|
||||
authStore.logged
|
||||
? playerList.value?.find((p) => p.user.userId == authStore.selfUser.userId)
|
||||
: undefined
|
||||
);
|
||||
|
||||
const currentTeam = computed(() => props.game.teams.find(t => t._id == props.game.currentTurn))
|
||||
const currentTeam = computed(() =>
|
||||
props.game.teams.find((t) => t._id == props.game.currentTurn.currentTeam)
|
||||
);
|
||||
|
||||
const isPlayerTurn = computed(() => currentTeam.value?.players.some(p => p.user.userId == authStore.selfUser.userId));
|
||||
const isPlayerTurn = computed(() =>
|
||||
currentTeam.value?.players.some(
|
||||
(p) => p.user.userId == authStore.selfUser.userId
|
||||
)
|
||||
);
|
||||
|
||||
const isSelf = computed(() => props.player.user.userId === authStore.selfUser.userId);
|
||||
const isSelfTurn = computed(() => isSelf && isPlayerTurn)
|
||||
const isSelf = computed(
|
||||
() => props.player.user.userId === authStore.selfUser.userId
|
||||
);
|
||||
const isSelfTurn = computed(() => isSelf && isPlayerTurn);
|
||||
|
||||
|
||||
const warningText = ref("Effets restants!")
|
||||
const clientStore = useClientSideStore()
|
||||
const warningText = ref("Effets restants!");
|
||||
const turnStore = useTurnStore();
|
||||
|
||||
// const make_discard = (target: Player) =>
|
||||
// clientStore.findUseEffect(CardEffects.MAKE_DISCARD, target);
|
||||
@@ -35,8 +48,7 @@ const clientStore = useClientSideStore()
|
||||
// const restack_discarded_card = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CARD, target)
|
||||
// const prepare_champion = (target: CardParser) => clientStore.findUseEffect(CardEffects.PREPARE, target)
|
||||
|
||||
|
||||
let warning = ref(false)
|
||||
let warning = ref(false);
|
||||
|
||||
// function has_actions_remaining(): boolean {
|
||||
// return (goStore.self?.turn?.damage_reserve ? goStore.self?.turn?.damage_reserve > 0 : false)
|
||||
@@ -52,27 +64,34 @@ let warning = ref(false)
|
||||
// }
|
||||
|
||||
function checkEndTurn() {
|
||||
// TODO: Add a confirmation dialog if resources are left
|
||||
// if (
|
||||
// (!warning.value)
|
||||
// && has_actions_remaining()) {
|
||||
// // Set a warning flag to true
|
||||
// // Display an alert somehow using this warning flag
|
||||
// warning.value = true
|
||||
// }
|
||||
// else if (!warning.value && can_buy_fire_gem()) {
|
||||
// warningText.value = "P'tite Fire gem?"
|
||||
// warning.value = true
|
||||
// }
|
||||
// else {
|
||||
// warningText.value = "Effets restants!"
|
||||
// warning.value = false
|
||||
// endTurn()
|
||||
// }
|
||||
// TODO: Add a confirmation dialog if resources are left
|
||||
// if (
|
||||
// (!warning.value)
|
||||
// && has_actions_remaining()) {
|
||||
// // Set a warning flag to true
|
||||
// // Display an alert somehow using this warning flag
|
||||
// warning.value = true
|
||||
// }
|
||||
// else if (!warning.value && can_buy_fire_gem()) {
|
||||
// warningText.value = "P'tite Fire gem?"
|
||||
// warning.value = true
|
||||
// }
|
||||
// else {
|
||||
// warningText.value = "Effets restants!"
|
||||
// warning.value = false
|
||||
// endTurn()
|
||||
// }
|
||||
|
||||
endTurn(props.game._id)
|
||||
endTurn(props.game._id);
|
||||
}
|
||||
|
||||
function boardCardClick(cardUUID: string) {
|
||||
if (props.game.currentTurn.cardsLocked.includes(cardUUID)) {
|
||||
//Card already locked
|
||||
return;
|
||||
}
|
||||
lockCard(props.game._id, cardUUID);
|
||||
}
|
||||
// function damage_team_all(team: Team) {
|
||||
// for (let i = 0; i < (goStore.self?.turn?.damage_reserve ?? 0); i++) {
|
||||
// damage_team(team)
|
||||
@@ -82,25 +101,34 @@ function checkEndTurn() {
|
||||
// function check_for_guard(player: Player) {
|
||||
// return player.board.some(c => c.guard === true)
|
||||
// }
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="player" :class="{ turn: isPlayerTurn, self: isSelf }">
|
||||
<div id="player_banner" class="big">test</div>
|
||||
<div id="turn_buttons">
|
||||
<button id="end_turn" class="end_turn_button turn_icon" :class="{ hidden: !isSelfTurn }"
|
||||
@click="checkEndTurn">END TURN</button>
|
||||
<div class="player" :class="{ turn: isPlayerTurn, self: isSelf }">
|
||||
<div id="player_banner" class="big">test</div>
|
||||
<div id="turn_buttons">
|
||||
<button
|
||||
id="end_turn"
|
||||
class="end_turn_button turn_icon"
|
||||
:class="{ hidden: !isSelfTurn }"
|
||||
@click="checkEndTurn"
|
||||
>
|
||||
END TURN
|
||||
</button>
|
||||
|
||||
<div class="warning"
|
||||
:class="{ 'warning-fire-gem': warningText == 'P\'tite Fire gem?', 'visible': warning }">
|
||||
/!\<br>{{ warningText }}
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<div id="current_player_board">
|
||||
<!-- <div class="other_player_buttons_container">
|
||||
<button @click.stop="damage_team(props.player.team)"
|
||||
v-if="isSelfTurn && isPlayerEnemy && goStore.self?.turn && props.player.team" class="damage_button"
|
||||
:disabled="(goStore.self?.turn.damage_reserve ?? 0) <= 0 || check_for_guard(props.player)">
|
||||
@@ -118,14 +146,19 @@ function checkEndTurn() {
|
||||
make Discard
|
||||
</button>
|
||||
</div> -->
|
||||
<!-- <br /> -->
|
||||
<!-- <br /> -->
|
||||
|
||||
<CardsGrouped :container="player.board" :wrapped="false">
|
||||
</CardsGrouped>
|
||||
<CardsGrouped
|
||||
:container="player.board"
|
||||
:wrapped="false"
|
||||
:playingTurn="props.game.currentTurn"
|
||||
@cardClick="boardCardClick"
|
||||
>
|
||||
</CardsGrouped>
|
||||
|
||||
<!-- <div class="cards_container"> -->
|
||||
<!-- HAND -->
|
||||
<!-- <template v-if="isSelf">
|
||||
<!-- <div class="cards_container"> -->
|
||||
<!-- HAND -->
|
||||
<!-- <template v-if="isSelf">
|
||||
<CardPreview :card_id="c?.card_id" v-for="c in props.player.hand" @click="c && playCard(c)">
|
||||
<button
|
||||
v-if="props.player.turn && c && isSelf && (props.player.turn.discard_markers + props.player.turn.fuck_u_markers) > 0"
|
||||
@@ -135,12 +168,12 @@ function checkEndTurn() {
|
||||
</CardPreview>
|
||||
</template> -->
|
||||
|
||||
<!-- BOARD -->
|
||||
<!-- <CardPreview :card_id="c.cardId" v-for="c in props.player.board.cards"
|
||||
<!-- BOARD -->
|
||||
<!-- <CardPreview :card_id="c.cardId" v-for="c in props.player.board.cards"
|
||||
@click="console.log/*lockCard(c)*/"
|
||||
:locked="/*props.player.turn && c.uuid in props.player.turn.cards_locked*/ false"> -->
|
||||
|
||||
<!-- <template v-if="c && isSelf && isPlayerTurn && props.player.turn">
|
||||
<!-- <template v-if="c && isSelf && isPlayerTurn && props.player.turn">
|
||||
// Card Locked
|
||||
<template v-if="c.uuid in props.player.turn.cards_locked">
|
||||
<template v-for="e in clientStore.maskedServerEffects">
|
||||
@@ -193,237 +226,233 @@ function checkEndTurn() {
|
||||
STUN
|
||||
</button>
|
||||
</template> -->
|
||||
<!-- </CardPreview> -->
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
<!-- </CardPreview> -->
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.warning {
|
||||
background-color: rgba(255, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
width: 100px;
|
||||
font-size: 0.85em;
|
||||
margin-left: 5px;
|
||||
visibility: hidden
|
||||
background-color: rgba(255, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
width: 100px;
|
||||
font-size: 0.85em;
|
||||
margin-left: 5px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.warning.visible {
|
||||
visibility: visible
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.warning-fire-gem {
|
||||
background-color: rgba(255, 165, 0, 0.7);
|
||||
background-color: rgba(255, 165, 0, 0.7);
|
||||
}
|
||||
|
||||
#turn_buttons {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
#end_turn {
|
||||
z-index: 10;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#end_turn.hidden {
|
||||
visibility: hidden
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.stack,
|
||||
.discard,
|
||||
.cards_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap-reverse;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap-reverse;
|
||||
}
|
||||
|
||||
.stack {
|
||||
grid-area: stack;
|
||||
margin-left: 75px;
|
||||
grid-area: stack;
|
||||
margin-left: 75px;
|
||||
}
|
||||
|
||||
.discard {
|
||||
margin-top: 40px;
|
||||
grid-area: discard;
|
||||
justify-content: start;
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
margin-left: 75px;
|
||||
margin-top: 40px;
|
||||
grid-area: discard;
|
||||
justify-content: start;
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
margin-left: 75px;
|
||||
}
|
||||
|
||||
.discard.invisible {
|
||||
visibility: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.player {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: min-content 1fr;
|
||||
/* border-top: 5px solid black; */
|
||||
grid-template-areas:
|
||||
"player_stats"
|
||||
"player";
|
||||
transition: all 1s;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: min-content 1fr;
|
||||
/* border-top: 5px solid black; */
|
||||
grid-template-areas:
|
||||
"player_stats"
|
||||
"player";
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
#current_player_board {
|
||||
grid-area: player;
|
||||
grid-area: player;
|
||||
}
|
||||
|
||||
.damage_button {
|
||||
background-color: #ff4d4d;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 5px 25px;
|
||||
margin: 5px 0 0 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
|
||||
background-color: #ff4d4d;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 5px 25px;
|
||||
margin: 5px 0 0 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.damage_button:disabled {
|
||||
background-color: rgba(255, 77, 77, 0.5);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
cursor: not-allowed;
|
||||
background-color: rgba(255, 77, 77, 0.5);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.damage_button:hover {
|
||||
background-color: #e60000;
|
||||
background-color: #e60000;
|
||||
}
|
||||
|
||||
.damage_button:disabled:hover {
|
||||
background-color: rgba(230, 0, 0, 0.5);
|
||||
background-color: rgba(230, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.damage_button:active {
|
||||
transform: translateY(2px);
|
||||
box-shadow: none;
|
||||
transform: translateY(2px);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.all_damage_button {
|
||||
padding: 5px 5px;
|
||||
font-size: 14px;
|
||||
padding: 5px 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.make_discard_button {
|
||||
background-color: #55b42f;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 5px 15px;
|
||||
margin: 5px 0 0 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
|
||||
background-color: #55b42f;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 5px 15px;
|
||||
margin: 5px 0 0 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.make_discard_button:disabled {
|
||||
background-color: rgba(85, 180, 47, 0.5);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-transform: uppercase;
|
||||
cursor: not-allowed;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
|
||||
background-color: rgba(85, 180, 47, 0.5);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-transform: uppercase;
|
||||
cursor: not-allowed;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.make_discard_button:hover {
|
||||
background-color: #03883a;
|
||||
background-color: #03883a;
|
||||
}
|
||||
|
||||
.make_discard_button:disabled:hover {
|
||||
background-color: rgba(3, 136, 58, 0.5);
|
||||
background-color: rgba(3, 136, 58, 0.5);
|
||||
}
|
||||
|
||||
.make_discard_button:active {
|
||||
transform: translateY(2px);
|
||||
box-shadow: none;
|
||||
transform: translateY(2px);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.turn_icon {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
#player_info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
#player_banner.big {
|
||||
padding: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
max-width: min-content;
|
||||
border: 5px solid black;
|
||||
border-radius: 10px;
|
||||
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, .60));
|
||||
backdrop-filter: blur(5px) brightness(80%) grayscale(80%);
|
||||
padding: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
max-width: min-content;
|
||||
border: 5px solid black;
|
||||
border-radius: 10px;
|
||||
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6));
|
||||
backdrop-filter: blur(5px) brightness(80%) grayscale(80%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#player_banner img {
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.turn_icon img {
|
||||
height: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.turn_icon_number {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.end_turn_button {
|
||||
background-color: #4CAF50;
|
||||
/* Green background color */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 13px 10px;
|
||||
margin: 0 0 0 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
|
||||
background-color: #4caf50;
|
||||
/* Green background color */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 13px 10px;
|
||||
margin: 0 0 0 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.end_turn_button:hover {
|
||||
background-color: #45a049;
|
||||
/* Darker green on hover */
|
||||
background-color: #45a049;
|
||||
/* Darker green on hover */
|
||||
}
|
||||
|
||||
.end_turn_button:active {
|
||||
transform: translateY(2px);
|
||||
/* Add a slight press effect on click */
|
||||
box-shadow: none;
|
||||
/* Remove shadow on click */
|
||||
transform: translateY(2px);
|
||||
/* Add a slight press effect on click */
|
||||
box-shadow: none;
|
||||
/* Remove shadow on click */
|
||||
}
|
||||
|
||||
.suicide_effect {
|
||||
background: linear-gradient(to bottom right, #666666, #ccc);
|
||||
color: red;
|
||||
background: linear-gradient(to bottom right, #666666, #ccc);
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
+27
-33
@@ -1,65 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import type { Player } from '~/netcode/Player';
|
||||
|
||||
import type { Player } from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
player: Player
|
||||
player: Player;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const props = defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="turn_icon">
|
||||
<img src="/images/champion-shield.png" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">
|
||||
{{ player.team?.health }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="turn_icon" v-if="showTurnData">
|
||||
<img src="/images/gold.png" class="icon_img" draggable="false" />
|
||||
<div class="turn_icon">
|
||||
<img src="/img/champion-shield.png" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">
|
||||
{{ player.team?.health }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="turn_icon" v-if="showTurnData">
|
||||
<img src="/umg/gold.png" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">
|
||||
{{ player.turn?.gold_reserve }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="turn_icon" v-if="showTurnData">
|
||||
<img src="/images/damage.png" class="icon_img" draggable="false" />
|
||||
<img src="/umg/damage.png" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">
|
||||
{{ player.turn?.damage_reserve }}
|
||||
</span>
|
||||
</div> -->
|
||||
<div class="turn_icon">
|
||||
<img src="/images/fuck.svg" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">{{ (player.turn?.discard_markers ?? 0) + (player.turn?.fuck_u_markers ?? 0)
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="turn_icon">
|
||||
<img src="/img/fuck.svg" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">{{
|
||||
(player.turn?.discard_markers ?? 0) + (player.turn?.fuck_u_markers ?? 0)
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.turn_icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
img.icon_img {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
span.icon_img {
|
||||
font-size: 50px;
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.turn_icon_number {
|
||||
position: absolute;
|
||||
text-shadow: white 0 0 1px;
|
||||
position: absolute;
|
||||
text-shadow: white 0 0 1px;
|
||||
}
|
||||
</style>
|
||||
|
||||
+24
-26
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { Player } from '~/netcode/Player';
|
||||
|
||||
import type { Player } from "~/netcode/Player";
|
||||
|
||||
export interface Props {
|
||||
isVisible: Boolean;
|
||||
@@ -10,15 +9,14 @@ export interface Props {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
won: () => false,
|
||||
players: () => []
|
||||
})
|
||||
players: () => [],
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const emit = defineEmits(["close"]);
|
||||
|
||||
const close = () => {
|
||||
reloadNuxtApp()
|
||||
}
|
||||
|
||||
reloadNuxtApp();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -30,24 +28,24 @@ const close = () => {
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<PlayerIcon :player="players[0]"></PlayerIcon>
|
||||
<p v-if="won">
|
||||
Vous avez gagné!
|
||||
</p>
|
||||
<p v-if="won">Vous avez gagné!</p>
|
||||
<p v-else>
|
||||
Vous avez perdu face à ce magnifique individu qui a très clairement beaucoup plus de bitches que vous! Cela
|
||||
dit, je pense qu'il n'y a pas de bonnes
|
||||
ou de mauvaises situations, juste des situations différentes. Vous avez perdu, mais vous avez gagné en
|
||||
expérience! C'est ça le plus important
|
||||
dans la vie, n'est-ce pas? Apprendre de ses erreurs et devenir meilleur! Alors, ne soyez pas triste, soyez
|
||||
heureux d'avoir eu l'opportunité
|
||||
d'apprendre quelque chose aujourd'hui! Et puis, vous savez ce qu'on dit, "ce qui ne nous tue pas nous rend
|
||||
plus fort"! Alors, allez-y, soyez fort!
|
||||
Et n'oubliez pas, la prochaine fois, vous gagnerez! Vous verrez! Vous êtes un gagnant, vous êtes un champion!
|
||||
Vous êtes le meilleur! Vous êtes
|
||||
le roi du monde!
|
||||
<img src="/images/so_back.png" alt="Une image décrivant le meme 'We are so back'" />
|
||||
Vous avez perdu face à ce magnifique individu qui a très clairement
|
||||
beaucoup plus de bitches que vous! Cela dit, je pense qu'il n'y a pas
|
||||
de bonnes ou de mauvaises situations, juste des situations
|
||||
différentes. Vous avez perdu, mais vous avez gagné en expérience!
|
||||
C'est ça le plus important dans la vie, n'est-ce pas? Apprendre de ses
|
||||
erreurs et devenir meilleur! Alors, ne soyez pas triste, soyez heureux
|
||||
d'avoir eu l'opportunité d'apprendre quelque chose aujourd'hui! Et
|
||||
puis, vous savez ce qu'on dit, "ce qui ne nous tue pas nous rend plus
|
||||
fort"! Alors, allez-y, soyez fort! Et n'oubliez pas, la prochaine
|
||||
fois, vous gagnerez! Vous verrez! Vous êtes un gagnant, vous êtes un
|
||||
champion! Vous êtes le meilleur! Vous êtes le roi du monde!
|
||||
<img
|
||||
src="/img/so_back.png"
|
||||
alt="Une image décrivant le meme 'We are so back'"
|
||||
/>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,7 +66,7 @@ const close = () => {
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
background-image: url('/images/bg-grass.png');
|
||||
background-image: url("/img/bg-grass.png");
|
||||
padding: 0 20px;
|
||||
border-radius: 5px;
|
||||
width: 800px;
|
||||
@@ -84,7 +82,7 @@ const close = () => {
|
||||
|
||||
.dialog-box h3 {
|
||||
font-size: 30px;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
|
||||
@@ -64,3 +64,14 @@ export async function endTurn(game: string) {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function lockCard(game: string, cardId: string) {
|
||||
const config = useRuntimeConfig();
|
||||
await $fetch<User>(
|
||||
new URL(`games/${game}/turn/lockCard/${cardId}`, config.public.endpoint).href,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Submodule netcode/interfaces deleted from 9248bf70af
@@ -2,12 +2,20 @@ export interface GameObject {
|
||||
_id: string;
|
||||
}
|
||||
|
||||
export interface PlayingTurn {
|
||||
currentTeam: string;
|
||||
cardsLocked: string[];
|
||||
effectsUsed: string[];
|
||||
damage: number;
|
||||
gold: number;
|
||||
}
|
||||
|
||||
export interface Game extends GameObject {
|
||||
fireGems: Container;
|
||||
market: Container;
|
||||
marketStack: Container;
|
||||
teams: Team[];
|
||||
currentTurn: string;
|
||||
currentTurn: PlayingTurn;
|
||||
}
|
||||
|
||||
export interface Team extends GameObject {
|
||||
|
||||
+249
-191
@@ -1,113 +1,156 @@
|
||||
<script setup lang="ts">
|
||||
import LoadingScreen from '~/components/LoadingScreen.vue';
|
||||
import { subscribe } from '~/netcode';
|
||||
import { containerEvents, delay } from '~/netcode/events';
|
||||
import { type Container, type Game, type Player } from '~/netcode/interfaces';
|
||||
import LoadingScreen from "~/components/LoadingScreen.vue";
|
||||
import { subscribe } from "~/netcode";
|
||||
import { containerEvents, delay } from "~/netcode/events";
|
||||
import { type Container, type Game, type Player } from "~/netcode/interfaces";
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const settingsStore = useSettingsStore()
|
||||
const config = useRuntimeConfig();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const route = useRoute()
|
||||
const gameId = route.params.id
|
||||
const route = useRoute();
|
||||
const gameId = route.params.id;
|
||||
|
||||
const { data: game, pending, error, refresh } = await useFetch<Game>(
|
||||
new URL(`/games/${gameId}`, config.public.endpoint).href,
|
||||
{
|
||||
credentials: "include",
|
||||
},
|
||||
)
|
||||
const {
|
||||
data: game,
|
||||
pending,
|
||||
error,
|
||||
refresh,
|
||||
} = await useFetch<Game>(
|
||||
new URL(`/games/${gameId}`, config.public.endpoint).href,
|
||||
{
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
|
||||
if (game.value == null) {
|
||||
navigateTo("/lobby");
|
||||
throw new Error("game not found")
|
||||
navigateTo("/lobby");
|
||||
throw new Error("game not found");
|
||||
}
|
||||
|
||||
const containers = computed<Record<string, Container>>(() => ({
|
||||
[game.value!.fireGems._id]: game.value!.fireGems,
|
||||
[game.value!.market._id]: game.value!.market,
|
||||
[game.value!.marketStack._id]: game.value!.marketStack,
|
||||
...Object.fromEntries(
|
||||
game.value!.teams.reduce(
|
||||
(acc, t) => acc.concat(t.players), [] as Player[]
|
||||
).map(
|
||||
p => [
|
||||
[p.board._id, p.board],
|
||||
[p.discard._id, p.discard],
|
||||
[p.hand._id, p.hand],
|
||||
[p.stack._id, p.stack]
|
||||
]
|
||||
).flat(1)
|
||||
)
|
||||
}))
|
||||
[game.value!.fireGems._id]: game.value!.fireGems,
|
||||
[game.value!.market._id]: game.value!.market,
|
||||
[game.value!.marketStack._id]: game.value!.marketStack,
|
||||
...Object.fromEntries(
|
||||
game
|
||||
.value!.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
|
||||
.map((p) => [
|
||||
[p.board._id, p.board],
|
||||
[p.discard._id, p.discard],
|
||||
[p.hand._id, p.hand],
|
||||
[p.stack._id, p.stack],
|
||||
])
|
||||
.flat(1)
|
||||
),
|
||||
}));
|
||||
|
||||
const eventSource = subscribe(`/games/${gameId}/subscribe`)
|
||||
const eventSource = subscribe(`/games/${gameId}/subscribe`);
|
||||
|
||||
const actions = ref<(() => Promise<any>)[]>([])
|
||||
const actions = ref<(() => Promise<any>)[]>([]);
|
||||
|
||||
function queueAction(callback: (() => Promise<any>)) {
|
||||
actions.value.push(callback)
|
||||
if (actions.value.length == 1) {
|
||||
execAction(callback)
|
||||
}
|
||||
function queueAction(callback: () => Promise<any>) {
|
||||
actions.value.push(callback);
|
||||
if (actions.value.length == 1) {
|
||||
execAction(callback);
|
||||
}
|
||||
}
|
||||
|
||||
async function execAction(callback: (() => Promise<any>)) {
|
||||
await callback()
|
||||
actions.value.shift()
|
||||
if (actions.value.length > 0) {
|
||||
execAction(actions.value[0])
|
||||
}
|
||||
async function execAction(callback: () => Promise<any>) {
|
||||
await callback();
|
||||
actions.value.shift();
|
||||
if (actions.value.length > 0) {
|
||||
execAction(actions.value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
eventSource.addEventListener("message", async (message) => {
|
||||
const data = JSON.parse(message.data)
|
||||
console.log(data)
|
||||
if (data.type == 'start') {
|
||||
throw new Error("not implemented")
|
||||
} else if (data.type == "populateContainer") {
|
||||
throw new Error("not implemented")
|
||||
} else if (data.type == "shuffleContainer") {
|
||||
queueAction(async () => {
|
||||
await containerEvents.get(data.container)?.shuffleContainer()
|
||||
})
|
||||
} else if (data.type == "distributeCards") {
|
||||
// containerEvents.get(data.from)?.getCardsPosition(data.cards)
|
||||
const current_game = game.value;
|
||||
if (!current_game) {
|
||||
throw new Error("game not found");
|
||||
}
|
||||
const data = JSON.parse(message.data);
|
||||
console.log(data);
|
||||
if (data.type == "start") {
|
||||
throw new Error("not implemented");
|
||||
} else if (data.type == "populateContainer") {
|
||||
queueAction(async () => {
|
||||
await containerEvents.get(data.container)?.startAnimation();
|
||||
});
|
||||
const fromContainer = containers.value[data.container.id as string];
|
||||
queueAction(async () => {
|
||||
for (const card of data.container.cards) {
|
||||
fromContainer.cards.push(card);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
}
|
||||
});
|
||||
queueAction(async () => {
|
||||
await containerEvents.get(data.container)?.endAnimation();
|
||||
});
|
||||
} else if (data.type == "shuffleContainer") {
|
||||
queueAction(async () => {
|
||||
await containerEvents.get(data.container)?.shuffleContainer();
|
||||
});
|
||||
} else if (data.type == "distributeCards") {
|
||||
// containerEvents.get(data.from)?.getCardsPosition(data.cards)
|
||||
|
||||
queueAction(async () => {
|
||||
containerEvents.get(data.from)?.startAnimation()
|
||||
containerEvents.get(data.to)?.startAnimation()
|
||||
})
|
||||
queueAction(async () => {
|
||||
containerEvents.get(data.from)?.startAnimation();
|
||||
containerEvents.get(data.to)?.startAnimation();
|
||||
});
|
||||
|
||||
const fromContainer = containers.value[data.from as string]
|
||||
const toContainer = containers.value[data.to as string]
|
||||
for (const card of data.cards) {
|
||||
queueAction(async () => {
|
||||
const index = fromContainer.cards.findIndex(c => c._id == card._id)
|
||||
fromContainer.cards.splice(index, 1)
|
||||
await delay(settingsStore.animationSpeed)
|
||||
})
|
||||
const fromContainer = containers.value[data.from as string];
|
||||
const toContainer = containers.value[data.to as string];
|
||||
for (const card of data.cards) {
|
||||
queueAction(async () => {
|
||||
const index = fromContainer.cards.findIndex((c) => c._id == card._id);
|
||||
fromContainer.cards.splice(index, 1);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
});
|
||||
|
||||
queueAction(async () => {
|
||||
toContainer.cards.push(card)
|
||||
await delay(settingsStore.animationSpeed)
|
||||
})
|
||||
}
|
||||
queueAction(async () => {
|
||||
containerEvents.get(data.from)?.endAnimation()
|
||||
containerEvents.get(data.to)?.endAnimation()
|
||||
await delay(settingsStore.animationSpeed * 2)
|
||||
})
|
||||
|
||||
} else if (data.type == "endTurn") {
|
||||
throw new Error("not implemented")
|
||||
queueAction(async () => {
|
||||
toContainer.cards.push(card);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
});
|
||||
}
|
||||
queueAction(async () => {
|
||||
containerEvents.get(data.from)?.endAnimation();
|
||||
containerEvents.get(data.to)?.endAnimation();
|
||||
await delay(settingsStore.animationSpeed * 2);
|
||||
});
|
||||
} else if (data.type == "endTurn") {
|
||||
queueAction(async () => {
|
||||
current_game.currentTurn.cardsLocked = [];
|
||||
current_game.currentTurn.effectsUsed = [];
|
||||
current_game.currentTurn.damage = 0;
|
||||
current_game.currentTurn.gold = 0;
|
||||
await delay(settingsStore.animationSpeed);
|
||||
});
|
||||
} else if (data.type == "lockCard") {
|
||||
queueAction(async () => {
|
||||
current_game.currentTurn.cardsLocked.push(data.card);
|
||||
});
|
||||
} else if (data.type == "useEffect") {
|
||||
queueAction(async () => {
|
||||
current_game.currentTurn.effectsUsed.push(data.effect);
|
||||
});
|
||||
} else if (data.type == "currentTurn.updateGold") {
|
||||
queueAction(async () => {
|
||||
console.log(`adding gold amount : ` + data.operation);
|
||||
current_game.currentTurn.gold = data.gold;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const authStore = useAuthStore();
|
||||
|
||||
|
||||
const playerList = computed(() => game.value!.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[]))
|
||||
const selfPlayer = computed(() => authStore.logged ? playerList.value?.find(p => p.user.userId == authStore.selfUser.userId) : undefined)
|
||||
const playerList = computed(() =>
|
||||
game.value!.teams.reduce((acc, t) => acc.concat(t.players), [] as Player[])
|
||||
);
|
||||
const selfPlayer = computed(() =>
|
||||
authStore.logged
|
||||
? playerList.value?.find((p) => p.user.userId == authStore.selfUser.userId)
|
||||
: undefined
|
||||
);
|
||||
|
||||
// TODO : current turn sound
|
||||
|
||||
@@ -115,24 +158,24 @@ const selfPlayer = computed(() => authStore.logged ? playerList.value?.find(p =>
|
||||
|
||||
// TODO : join sound
|
||||
|
||||
const showResults = ref(false)
|
||||
const isWinner = ref(false)
|
||||
const loaded = ref(false)
|
||||
const showResults = ref(false);
|
||||
const isWinner = ref(false);
|
||||
const loaded = ref(false);
|
||||
|
||||
function showLoserScreen() {
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true
|
||||
isWinner.value = false
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true;
|
||||
isWinner.value = false;
|
||||
}
|
||||
|
||||
function showWinnerScreen() {
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true
|
||||
isWinner.value = true
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true;
|
||||
isWinner.value = true;
|
||||
}
|
||||
|
||||
const discard_unwrapped = ref(false)
|
||||
const focusedPlayer = ref<Player>(selfPlayer.value ?? playerList.value[0])
|
||||
const discard_unwrapped = ref(false);
|
||||
const focusedPlayer = ref<Player>(selfPlayer.value ?? playerList.value[0]);
|
||||
|
||||
// const clientStore = useClientSideStore()
|
||||
// const restack_discarded_champion = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CHAMPION, target)
|
||||
@@ -140,7 +183,7 @@ const focusedPlayer = ref<Player>(selfPlayer.value ?? playerList.value[0])
|
||||
// const sacrifice = (target: Card) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
|
||||
|
||||
function focusPlayer(p: Player) {
|
||||
focusedPlayer.value = p
|
||||
focusedPlayer.value = p;
|
||||
}
|
||||
|
||||
// watch<Team>(() => goStore.current_game!.current_turn, (new_team, old_team) => {
|
||||
@@ -153,159 +196,174 @@ function focusPlayer(p: Player) {
|
||||
// })
|
||||
|
||||
onUnmounted(() => {
|
||||
eventSource.close()
|
||||
})
|
||||
|
||||
eventSource.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @contextmenu.prevent="false" id="game_board" v-if="game">
|
||||
<AnimatedBackground @ready="loaded = true"></AnimatedBackground>
|
||||
<div id="stack_discard">
|
||||
<div id="stack">
|
||||
<CardsGrouped :container="focusedPlayer.stack" :wrapped="true" :hidden="true">
|
||||
</CardsGrouped>
|
||||
</div>
|
||||
<div @contextmenu.prevent="false" id="game_board" v-if="game">
|
||||
<AnimatedBackground @ready="loaded = true"></AnimatedBackground>
|
||||
<div id="stack_discard">
|
||||
<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">
|
||||
</CardsGrouped>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="discard"
|
||||
:class="{ invisible: discard_unwrapped }"
|
||||
@click="() => (discard_unwrapped = true)"
|
||||
>
|
||||
<CardsGrouped :container="focusedPlayer.discard" :wrapped="true">
|
||||
</CardsGrouped>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MarketCards :market="game.market" :fire_gems="game.fireGems"></MarketCards>
|
||||
<MarketCards :market="game.market" :fire_gems="game.fireGems"></MarketCards>
|
||||
|
||||
<!-- <div id="player_list_container">
|
||||
<!-- <div id="player_list_container">
|
||||
<div id="player_list">
|
||||
<PlayerListElement v-for="p in playerList" :player="p" @click="focusPlayer(p)" :focused="focusedPlayer">
|
||||
</PlayerListElement>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div id="current_player">
|
||||
<PlayerSlot v-if="focusedPlayer" :player="focusedPlayer" :game="game!"></PlayerSlot>
|
||||
</div>
|
||||
<ResultDialog :isVisible="showResults" :won="isWinner" :players="playerList" @close="showResults = false">
|
||||
</ResultDialog>
|
||||
<GlobalOverlay></GlobalOverlay>
|
||||
<SelfView v-if="selfPlayer" :container="selfPlayer.hand"></SelfView>
|
||||
<LoadingScreen v-if="!loaded"></LoadingScreen>
|
||||
<div id="current_player">
|
||||
<PlayerSlot
|
||||
v-if="focusedPlayer"
|
||||
:player="focusedPlayer"
|
||||
:game="game!"
|
||||
></PlayerSlot>
|
||||
</div>
|
||||
<ResultDialog
|
||||
:isVisible="showResults"
|
||||
:won="isWinner"
|
||||
:players="playerList"
|
||||
@close="showResults = false"
|
||||
>
|
||||
</ResultDialog>
|
||||
<GlobalOverlay></GlobalOverlay>
|
||||
<SelfView v-if="selfPlayer" :container="selfPlayer.hand"></SelfView>
|
||||
<LoadingScreen v-if="!loaded"></LoadingScreen>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#game_status {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(255, 255, 255, .75);
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
|
||||
#game_board {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content 1fr;
|
||||
grid-template-rows: min-content min-content 1fr;
|
||||
grid-template-areas:
|
||||
"stack_discard market empty"
|
||||
"player_list player_list player_list"
|
||||
"current_player current_player current_player";
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content 1fr;
|
||||
grid-template-rows: min-content min-content 1fr;
|
||||
grid-template-areas:
|
||||
"stack_discard market empty"
|
||||
"player_list player_list player_list"
|
||||
"current_player current_player current_player";
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#player_list,
|
||||
#current_player {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
/* background: url('/images/bg-darkgrass.png'); */
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
/* background: url('/img/bg-darkgrass.png'); */
|
||||
}
|
||||
|
||||
#current_player {
|
||||
grid-area: current_player;
|
||||
grid-area: current_player;
|
||||
}
|
||||
|
||||
#stack_discard {
|
||||
grid-area: stack_discard;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
grid-area: stack_discard;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#stack,
|
||||
#discard {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#discard {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.discard_overlay {
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
/* background: blue; */
|
||||
pointer-events: none;
|
||||
gap: 2px;
|
||||
flex-wrap: wrap-reverse;
|
||||
align-content: flex-end;
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
padding-bottom: inherit;
|
||||
padding-left: 40px;
|
||||
height: calc(100% - 64px);
|
||||
padding-top: 64px;
|
||||
width: calc(100% -40px);
|
||||
margin-left: 0;
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
/* background: blue; */
|
||||
pointer-events: none;
|
||||
gap: 2px;
|
||||
flex-wrap: wrap-reverse;
|
||||
align-content: flex-end;
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
padding-bottom: inherit;
|
||||
padding-left: 40px;
|
||||
height: calc(100% - 64px);
|
||||
padding-top: 64px;
|
||||
width: calc(100% -40px);
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#discard_unwrapped_bg {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 8;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
pointer-events: all;
|
||||
padding: inherit;
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 8;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
pointer-events: all;
|
||||
padding: inherit;
|
||||
}
|
||||
|
||||
#discard_unwrapped_bg.unwrapped {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#market {
|
||||
grid-area: market;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
grid-area: market;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#player_list_container {
|
||||
grid-area: player_list;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
grid-area: player_list;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
/* background-image: url("/images/bg-darkgrass.png"); */
|
||||
/* background-image: url("/img/bg-darkgrass.png"); */
|
||||
}
|
||||
|
||||
#player_list {
|
||||
/* border: 5px solid #000; */
|
||||
display: flex;
|
||||
/* border: 5px solid #000; */
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
#player_list .card {
|
||||
--max-height: 90px;
|
||||
--max-height: 90px;
|
||||
}
|
||||
</style>
|
||||
+216
-201
@@ -1,273 +1,288 @@
|
||||
<script setup lang="ts">
|
||||
// import { playSound } from '~/helpers/audioHelpers';
|
||||
import { playSound } from "~/helpers/audioHelpers";
|
||||
import { createGame, getLobby, subscribe } from "~/netcode"
|
||||
import { createGame, getLobby, subscribe } from "~/netcode";
|
||||
import type { Player } from "~/netcode/interfaces";
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const games = await getLobby()
|
||||
const games = await getLobby();
|
||||
|
||||
const eventSource = subscribe("/games/subscribe")
|
||||
const eventSource = subscribe("/games/subscribe");
|
||||
|
||||
eventSource.addEventListener("message", (message) => {
|
||||
const data = JSON.parse(message.data)
|
||||
if (data.type == "create") {
|
||||
games.value.push(data)
|
||||
} else if (data.type == "delete") {
|
||||
const index = games.value.findIndex(g => g._id == data._id)
|
||||
if (index == -1) {
|
||||
throw new Error("Couldn't delete game")
|
||||
}
|
||||
games.value.splice(index, 1)
|
||||
} else if (data.type == "update") {
|
||||
throw new Error("not implemented")
|
||||
} else if (data.type == "start") {
|
||||
throw new Error("not implemented")
|
||||
const data = JSON.parse(message.data);
|
||||
if (data.type == "create") {
|
||||
games.value.push(data);
|
||||
} else if (data.type == "delete") {
|
||||
const index = games.value.findIndex((g) => g._id == data._id);
|
||||
if (index == -1) {
|
||||
throw new Error("Couldn't delete game");
|
||||
}
|
||||
games.value.splice(index, 1);
|
||||
} else if (data.type == "update") {
|
||||
throw new Error("not implemented");
|
||||
} else if (data.type == "start") {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
const erika_alive = ref(true);
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
|
||||
function killErika() {
|
||||
playSound('/sounds/splat1.mp3')
|
||||
erika_alive.value = false;
|
||||
playSound("/sounds/splat1.mp3");
|
||||
erika_alive.value = false;
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
eventSource.close()
|
||||
})
|
||||
|
||||
eventSource.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="background">
|
||||
<div class="title">
|
||||
Entrez dans le Royaume de Héron !
|
||||
<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/${g._id}` })"
|
||||
:class="
|
||||
authStore.selfUser.userId == g.owner.userId ? 'own_game' : ''
|
||||
"
|
||||
>
|
||||
<template v-if="g.started && g.ended"> GAME OVER </template>
|
||||
<template
|
||||
v-else-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[]
|
||||
).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 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/${g._id}` })"
|
||||
:class="authStore.selfUser.userId == g.owner.userId ? 'own_game' : ''">
|
||||
<template v-if="g.started && g.ended">
|
||||
GAME OVER
|
||||
</template>
|
||||
<template
|
||||
v-else-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[]).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 class="overlay" v-if="erika_alive">
|
||||
<div class="erika-zone">
|
||||
<img @click="router.push('/rules')" class="overlay-image"
|
||||
src="https://static.wikia.nocookie.net/umineko/images/9/9d/PC_Erika.Casual.Sprite_36.png"
|
||||
alt="pétasse" />
|
||||
<div class="overlay-button">
|
||||
<button class="kill-erika-button" @click.stop="killErika()">Tuer Erika</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="speech-bubble" @click.prevent.self>
|
||||
<p>Ohoho!<br>Je suis Erika Furudo, la détective la plus forte du monde !</p>
|
||||
<p>Je vais vous montrer comment jouer à Héron Realms !</p>
|
||||
<p>Cliquez moi-dessus pour afficher les <span style="color: red">règles</span>.</p>
|
||||
<p>Ou sinon, commencez direct en créant une partie ou rejoingnant une partie existante.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overlay" v-if="erika_alive">
|
||||
<div class="erika-zone">
|
||||
<img
|
||||
@click="router.push('/rules')"
|
||||
class="overlay-image"
|
||||
src="https://static.wikia.nocookie.net/umineko/img/9/9d/PC_Erika.Casual.Sprite_36.png"
|
||||
alt="pétasse"
|
||||
/>
|
||||
<div class="overlay-button">
|
||||
<button class="kill-erika-button" @click.stop="killErika()">
|
||||
Tuer Erika
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="speech-bubble" @click.prevent.self>
|
||||
<p>
|
||||
Ohoho!<br />Je suis Erika Furudo, la détective la plus forte du monde
|
||||
!
|
||||
</p>
|
||||
<p>Je vais vous montrer comment jouer à Héron Realms !</p>
|
||||
<p>
|
||||
Cliquez moi-dessus pour afficher les
|
||||
<span style="color: red">règles</span>.
|
||||
</p>
|
||||
<p>
|
||||
Ou sinon, commencez direct en créant une partie ou rejoingnant une
|
||||
partie existante.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.background {
|
||||
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;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
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;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: 'Kanit', 'Roboto', sans-serif;
|
||||
font-size: 64px;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 100px;
|
||||
font-family: "Kanit", "Roboto", sans-serif;
|
||||
font-size: 64px;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.middle-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 100px;
|
||||
margin-bottom: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 100px;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.create-game-zone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.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);
|
||||
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.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);
|
||||
|
||||
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 {
|
||||
border: solid yellow 5px;
|
||||
border: solid yellow 5px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.erika-zone {
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
z-index: 2;
|
||||
height: 40%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
z-index: 2;
|
||||
height: 40%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.overlay-image {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.overlay-button {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 300px;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
|
||||
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 300px;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.kill-erika-button {
|
||||
display: none;
|
||||
pointer-events: all;
|
||||
background-color: #f44336;
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
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);
|
||||
display: none;
|
||||
pointer-events: all;
|
||||
background-color: #f44336;
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
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);
|
||||
}
|
||||
|
||||
.speech-bubble {
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
right: 260px;
|
||||
z-index: 1;
|
||||
background-color: rgba(225, 225, 225, 0.8);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
font-family: 'Kanit', 'Roboto', sans-serif;
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
text-align: center;
|
||||
max-width: 420px;
|
||||
line-height: 1.5;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
right: 260px;
|
||||
z-index: 1;
|
||||
background-color: rgba(225, 225, 225, 0.8);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
font-family: "Kanit", "Roboto", sans-serif;
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
text-align: center;
|
||||
max-width: 420px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.erika-zone:hover .overlay-button .kill-erika-button {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
+363
-291
@@ -1,320 +1,392 @@
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="background">
|
||||
<div class="overlay-2">
|
||||
<div class="erika-zone-2">
|
||||
<img @click="router.push({ path: '/lobby' })" class="overlay-image"
|
||||
src="https://static.wikia.nocookie.net/umineko/images/9/9d/PC_Erika.Casual.Sprite_36.png"
|
||||
alt="pétasse" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-content">
|
||||
<h1>Règles de Héron Realms</h1>
|
||||
<section>
|
||||
<p>Héron Realms, c'est un jeu de <span style="text-decoration:line-through;">héron building</span>
|
||||
deck-building.</p>
|
||||
<p>Tout le monde part d'un même deck de base.</p>
|
||||
<p>Le but du jeu est de l'enrichir pour obtenir de quoi gagner.</p>
|
||||
<p>Et comme la violence est une solution universelle, ça se traduit en battre ses adversaires.</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<hr>
|
||||
<p>​</p>
|
||||
<h2>Objectif</h2>
|
||||
<p>L'unique objectif du jeu est d'être le dernier en lice. Pour y arriver, il faut descendre les Points
|
||||
de Vie de vos adversaires à 0 en partant de 50.</p>
|
||||
<p>
|
||||
Le moyen principal d'y arriver, c'est de faire des <span style="color: red">Dégats</span> <img
|
||||
src="/images/damage.png" class="icon_img" draggable="false" />. Tout le reste des mécaniques du
|
||||
jeu sont là pour vous faciliter la tâche,
|
||||
ou pour mettre des bâtons dans les roues de vos adversaires.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr>
|
||||
<h2>Deckbuilding</h2>
|
||||
<p>Comme c'est un jeu de "deckbuilding", on va expliquer ce qu'est le "deck" et ce qui nous permet de le
|
||||
"build".</p>
|
||||
<div class="card-row">
|
||||
<CardPreview :card_id="undefined" :tilted="false"></CardPreview>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>
|
||||
Le deck d'un joueur est-ce qui constitue sa pioche, qui lui permettra ensuite de constituer sa main
|
||||
(la main est préparée à la fin du tour et permet de jouer le tour d'après).
|
||||
</p>
|
||||
<p>
|
||||
Quand la pioche est vide, elle est recyclée à partir de sa défausse.
|
||||
|
||||
</p>
|
||||
<p>
|
||||
La défausse est ce qui contient les cartes déjà jouées. Mais aussi celles qui sont achetées! Elle
|
||||
finit par être mélangée pour former la nouvelle pioche.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr>
|
||||
<h2>Deck de base</h2>
|
||||
<p>
|
||||
Tout le monde commence avec 10 cartes divisées en 4 types:
|
||||
</p>
|
||||
<ul>
|
||||
<li>7x Or (1)</li>
|
||||
<li>1x Or (2)</li>
|
||||
<li>1x Dégats (1)</li>
|
||||
<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>
|
||||
</div>
|
||||
<p>
|
||||
La pioche est formée au hasard: on peut donc avoir une main de départ avec 5 or et 2 dégats, ou 3 or
|
||||
et 4 dégats, ou 4 or et 3 dégats, etc.
|
||||
</p>
|
||||
<p>
|
||||
A partir de sa main, on peut jouer des cartes. Ces cartes peuvent déclencher des effets: on peut
|
||||
considérer qu'ils vont dans un "pool commun", et qu'on peut les utiliser dans
|
||||
l'ordre qu'on souhaite. 1 de dégats et 2 de dégats me donnent 3 ressources de dégats que je split
|
||||
comme je veux ensuite. Pareil pour l'or et pour le soin, qui sont les 3 ressources
|
||||
principales du jeu.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr>
|
||||
<h2>Types de cartes</h2>
|
||||
<p>
|
||||
Les cartes sont divisées en 3 catégories: objets, actions et 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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<h3>Objets & actions</h3>
|
||||
<div class="card-row">
|
||||
<CardPreview :card_id="56" :tilted="false"></CardPreview>
|
||||
<CardPreview :card_id="52" :tilted="false"></CardPreview>
|
||||
</div>
|
||||
<p>
|
||||
Par simplicité, disons que les objets et les actions sont pareil: on les joue, ils font un effet
|
||||
disponible durant le tour actuel, et ils sont défaussés.
|
||||
</p>
|
||||
<h3>Champions (& gardes)</h3>
|
||||
<div class="card-row">
|
||||
<CardPreview :card_id="43" :tilted="false"></CardPreview>
|
||||
</div>
|
||||
<p>
|
||||
Les champions sont un peu plus complexes: ils restent sur le terrain, et on peut les utiliser à
|
||||
chaque tour (en les mobilisant).
|
||||
</p>
|
||||
<p>Ils ont une défense <img src="/images/champion-shield.png" style="height: 30px" draggable="false" />
|
||||
indiquée en bas à droite et ils ne sont assommés (défaussés) que si on leur fait des dégats =
|
||||
défense.</p>
|
||||
<p>Ces dégats doivent être faits d'un coup. Je ne peux pas faire un peu 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>
|
||||
</div>
|
||||
<p>
|
||||
Par ailleurs, certains champions sont des Gardes <img src="/images/champion-shield-guard.png"
|
||||
style="height: 30px" draggable="false" /> (ils ont un bouclier noir), ce qui veut dire qu'on est
|
||||
obligés de les tuer pour faire des dégats à des champions non-gardes ou au joueur.
|
||||
</p>
|
||||
<p>
|
||||
(J'en profite pour préciser qu'on peut en effet, sans garde, choisir d'attaquer le joueur
|
||||
directement ou ses champions.)
|
||||
Incidentellement, ça veut dire qu'avec seulement 2 de dégats, je ne peux rien faire à un joueur avec
|
||||
un garde à 3 de défense.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr>
|
||||
<h2>Building</h2>
|
||||
<p>
|
||||
Passons au "building": on peut acheter des cartes pour les ajouter à notre défausse qui, si vous
|
||||
avez suivi, constituera la prochaine pioche.
|
||||
Avec le jeu de base, <span style="color: darkgoldenrod">l'or</span> <img src="/images/gold.png"
|
||||
class="icon_img" draggable="false" /> sert 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>
|
||||
</div>
|
||||
<p>
|
||||
Un marché est mis à disposition de tous les joueurs, mettant à disposition 5 cartes (pas ici parce
|
||||
que j'ai pas la place).
|
||||
</p>
|
||||
<p>
|
||||
Ces cartes ont chacune un coût indiqué en haut à droite.
|
||||
Il faut au moins autant d'or que ce qui est indiqué pour acheter une carte. Une carte achetée laisse
|
||||
la place à une nouvelle carte directement, qu'on peut
|
||||
décider d'acheter juste après (tant qu'on a l'argent).
|
||||
</p>
|
||||
<p>
|
||||
Il ne nous reste plus qu'à comprendre ce que font les cartes du marché, puisqu'elles sont plus
|
||||
complexes (et bien plus fortes) que votre deck de base.
|
||||
Prenons des exemples!
|
||||
</p>
|
||||
<hr>
|
||||
<h2>Couleurs</h2>
|
||||
<p>
|
||||
La première chose qui ne vous échappera pas, c'est qu'on a 4 couleurs différentes pour les cartes.
|
||||
</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>Les cartes <span style="color: darkgoldenrod">jaunes</span> sont les cartes Empire.</p>
|
||||
<p>Elles font beaucoup de soin, et activent souvent des effets de pioche.</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>Les cartes <span style="color: red">rouges</span> sont les cartes Necros.</p>
|
||||
<p>Elles sont les seules à pouvoir sacrifier des cartes pour les sortir de son deck de façon permanente.
|
||||
</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>Les cartes <span style="color: darkgreen">vertes</span> sont les cartes Sauvages.</p>
|
||||
<p>Elles font beaucoup de dégats, et forcent vos adversaires à jouer moins de cartes.</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>Les cartes <span style="color: darkblue">bleues</span> sont les cartes Guilde.</p>
|
||||
<p>Elles donnent beaucoup d'or, assomment les champions adverses et manipulent votre pioche.</p>
|
||||
<p>​</p>
|
||||
<hr>
|
||||
<h2>Déclencheurs</h2>
|
||||
<p>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>
|
||||
</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>
|
||||
<p>La 2ème ligne est une capacité Allié: il faut une autre carte de la même couleur en jeu pour pouvoir
|
||||
en profiter.</p>
|
||||
<p>La 3ème ligne est un Effet Poubelle (différent du sacrifice des cartes rouges): vous pouvez retirer
|
||||
cette carte de votre jeu pour bénéficier, en plus des autres effets, de l'effet marqué. Mais la
|
||||
carte sera défaussée à tout jamais.</p>
|
||||
<p>​</p>
|
||||
<hr>
|
||||
<h2>Effets</h2>
|
||||
<p>Une variété d'effets peuvent être inclus dans les cartes.</p>
|
||||
<p>Il y en a plein et les expliquer ici serait relou, alors je vous laisse poser la question en vocal si
|
||||
vous n'en comprenez pas un.</p>
|
||||
<p>​</p>
|
||||
<hr>
|
||||
<h2>Activer les cartes dans la version en ligne</h2>
|
||||
<p>Il suffit de cliquer sur la carte que vous voulez activer dans votre main pour la "LOCK".</p>
|
||||
<p>Une carte "LOCKED" est considérée en jeu, & peut activer des effets Allié pour d'autres cartes, etc.
|
||||
</p>
|
||||
<p>Si vous avez des choix à faire sur la carte, un menu apparaîtra pour vous laisser choisir.</p>
|
||||
<p>Pour faire des dommages, il y aura un bouton "Damage" sur vos ennemis. Pareil sur leurs champions.
|
||||
</p>
|
||||
<p>Quand vous avez un effet "Sacrifice", vous pouvez fouiller votre défausse pour choisir la carte à
|
||||
sacrifier. Vous pouvez également sacrifier une carte de la main qui n'est pas encore locked!</p>
|
||||
<h2>Zoomer sur les cartes</h2>
|
||||
<p>Pour zoomer sur une carte, maintenez votre clic droit.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="background">
|
||||
<div class="overlay-2">
|
||||
<div class="erika-zone-2">
|
||||
<img
|
||||
@click="router.push({ path: '/lobby' })"
|
||||
class="overlay-image"
|
||||
src="https://static.wikia.nocookie.net/umineko/img/9/9d/PC_Erika.Casual.Sprite_36.png"
|
||||
alt="pétasse"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-content">
|
||||
<h1>Règles de Héron Realms</h1>
|
||||
<section>
|
||||
<p>
|
||||
Héron Realms, c'est un jeu de
|
||||
<span style="text-decoration: line-through">héron building</span>
|
||||
deck-building.
|
||||
</p>
|
||||
<p>Tout le monde part d'un même deck de base.</p>
|
||||
<p>Le but du jeu est de l'enrichir pour obtenir de quoi gagner.</p>
|
||||
<p>
|
||||
Et comme la violence est une solution universelle, ça se traduit en
|
||||
battre ses adversaires.
|
||||
</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<hr />
|
||||
<p>​</p>
|
||||
<h2>Objectif</h2>
|
||||
<p>
|
||||
L'unique objectif du jeu est d'être le dernier en lice. Pour y
|
||||
arriver, il faut descendre les Points de Vie de vos adversaires à 0 en
|
||||
partant de 50.
|
||||
</p>
|
||||
<p>
|
||||
Le moyen principal d'y arriver, c'est de faire des
|
||||
<span style="color: red">Dégats</span>
|
||||
<img src="/img/damage.png" class="icon_img" draggable="false" />. Tout
|
||||
le reste des mécaniques du jeu sont là pour vous faciliter la tâche,
|
||||
ou pour mettre des bâtons dans les roues de vos adversaires.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr />
|
||||
<h2>Deckbuilding</h2>
|
||||
<p>
|
||||
Comme c'est un jeu de "deckbuilding", on va expliquer ce qu'est le
|
||||
"deck" et ce qui nous permet de le "build".
|
||||
</p>
|
||||
<div class="card-row">
|
||||
<CardPreview :card_id="undefined" :tilted="false"></CardPreview>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>
|
||||
Le deck d'un joueur est-ce qui constitue sa pioche, qui lui permettra
|
||||
ensuite de constituer sa main (la main est préparée à la fin du tour
|
||||
et permet de jouer le tour d'après).
|
||||
</p>
|
||||
<p>
|
||||
Quand la pioche est vide, elle est recyclée à partir de sa défausse.
|
||||
</p>
|
||||
<p>
|
||||
La défausse est ce qui contient les cartes déjà jouées. Mais aussi
|
||||
celles qui sont achetées! Elle finit par être mélangée pour former la
|
||||
nouvelle pioche.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr />
|
||||
<h2>Deck de base</h2>
|
||||
<p>Tout le monde commence avec 10 cartes divisées en 4 types:</p>
|
||||
<ul>
|
||||
<li>7x Or (1)</li>
|
||||
<li>1x Or (2)</li>
|
||||
<li>1x Dégats (1)</li>
|
||||
<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>
|
||||
</div>
|
||||
<p>
|
||||
La pioche est formée au hasard: on peut donc avoir une main de départ
|
||||
avec 5 or et 2 dégats, ou 3 or et 4 dégats, ou 4 or et 3 dégats, etc.
|
||||
</p>
|
||||
<p>
|
||||
A partir de sa main, on peut jouer des cartes. Ces cartes peuvent
|
||||
déclencher des effets: on peut considérer qu'ils vont dans un "pool
|
||||
commun", et qu'on peut les utiliser dans l'ordre qu'on souhaite. 1 de
|
||||
dégats et 2 de dégats me donnent 3 ressources de dégats que je split
|
||||
comme je veux ensuite. Pareil pour l'or et pour le soin, qui sont les
|
||||
3 ressources principales du jeu.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr />
|
||||
<h2>Types de cartes</h2>
|
||||
<p>
|
||||
Les cartes sont divisées en 3 catégories: objets, actions et
|
||||
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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<h3>Objets & actions</h3>
|
||||
<div class="card-row">
|
||||
<CardPreview :card_id="56" :tilted="false"></CardPreview>
|
||||
<CardPreview :card_id="52" :tilted="false"></CardPreview>
|
||||
</div>
|
||||
<p>
|
||||
Par simplicité, disons que les objets et les actions sont pareil: on
|
||||
les joue, ils font un effet disponible durant le tour actuel, et ils
|
||||
sont défaussés.
|
||||
</p>
|
||||
<h3>Champions (& gardes)</h3>
|
||||
<div class="card-row">
|
||||
<CardPreview :card_id="43" :tilted="false"></CardPreview>
|
||||
</div>
|
||||
<p>
|
||||
Les champions sont un peu plus complexes: ils restent sur le terrain,
|
||||
et on peut les utiliser à chaque tour (en les mobilisant).
|
||||
</p>
|
||||
<p>
|
||||
Ils ont une défense
|
||||
<img
|
||||
src="/img/champion-shield.png"
|
||||
style="height: 30px"
|
||||
draggable="false"
|
||||
/>
|
||||
indiquée en bas à droite et ils ne sont assommés (défaussés) que si on
|
||||
leur fait des dégats = défense.
|
||||
</p>
|
||||
<p>
|
||||
Ces dégats doivent être faits d'un coup. Je ne peux pas faire un peu
|
||||
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>
|
||||
</div>
|
||||
<p>
|
||||
Par ailleurs, certains champions sont des Gardes
|
||||
<img
|
||||
src="/img/champion-shield-guard.png"
|
||||
style="height: 30px"
|
||||
draggable="false"
|
||||
/>
|
||||
(ils ont un bouclier noir), ce qui veut dire qu'on est obligés de les
|
||||
tuer pour faire des dégats à des champions non-gardes ou au joueur.
|
||||
</p>
|
||||
<p>
|
||||
(J'en profite pour préciser qu'on peut en effet, sans garde, choisir
|
||||
d'attaquer le joueur directement ou ses champions.) Incidentellement,
|
||||
ça veut dire qu'avec seulement 2 de dégats, je ne peux rien faire à un
|
||||
joueur avec un garde à 3 de défense.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr />
|
||||
<h2>Building</h2>
|
||||
<p>
|
||||
Passons au "building": on peut acheter des cartes pour les ajouter à
|
||||
notre défausse qui, si vous avez suivi, constituera la prochaine
|
||||
pioche. Avec le jeu de base,
|
||||
<span style="color: darkgoldenrod">l'or</span>
|
||||
<img src="/img/gold.png" class="icon_img" draggable="false" /> sert
|
||||
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>
|
||||
</div>
|
||||
<p>
|
||||
Un marché est mis à disposition de tous les joueurs, mettant à
|
||||
disposition 5 cartes (pas ici parce que j'ai pas la place).
|
||||
</p>
|
||||
<p>
|
||||
Ces cartes ont chacune un coût indiqué en haut à droite. Il faut au
|
||||
moins autant d'or que ce qui est indiqué pour acheter une carte. Une
|
||||
carte achetée laisse la place à une nouvelle carte directement, qu'on
|
||||
peut décider d'acheter juste après (tant qu'on a l'argent).
|
||||
</p>
|
||||
<p>
|
||||
Il ne nous reste plus qu'à comprendre ce que font les cartes du
|
||||
marché, puisqu'elles sont plus complexes (et bien plus fortes) que
|
||||
votre deck de base. Prenons des exemples!
|
||||
</p>
|
||||
<hr />
|
||||
<h2>Couleurs</h2>
|
||||
<p>
|
||||
La première chose qui ne vous échappera pas, c'est qu'on a 4 couleurs
|
||||
différentes pour les cartes.
|
||||
</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>
|
||||
Les cartes <span style="color: darkgoldenrod">jaunes</span> sont les
|
||||
cartes Empire.
|
||||
</p>
|
||||
<p>
|
||||
Elles font beaucoup de soin, et activent souvent des effets de pioche.
|
||||
</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>
|
||||
Les cartes <span style="color: red">rouges</span> sont les cartes
|
||||
Necros.
|
||||
</p>
|
||||
<p>
|
||||
Elles sont les seules à pouvoir sacrifier des cartes pour les sortir
|
||||
de son deck de façon permanente.
|
||||
</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>
|
||||
Les cartes <span style="color: darkgreen">vertes</span> sont les
|
||||
cartes Sauvages.
|
||||
</p>
|
||||
<p>
|
||||
Elles font beaucoup de dégats, et forcent vos adversaires à jouer
|
||||
moins de cartes.
|
||||
</p>
|
||||
<p>​</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>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<p>
|
||||
Les cartes <span style="color: darkblue">bleues</span> sont les cartes
|
||||
Guilde.
|
||||
</p>
|
||||
<p>
|
||||
Elles donnent beaucoup d'or, assomment les champions adverses et
|
||||
manipulent votre pioche.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr />
|
||||
<h2>Déclencheurs</h2>
|
||||
<p>
|
||||
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>
|
||||
</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>
|
||||
<p>
|
||||
La 2ème ligne est une capacité Allié: il faut une autre carte de la
|
||||
même couleur en jeu pour pouvoir en profiter.
|
||||
</p>
|
||||
<p>
|
||||
La 3ème ligne est un Effet Poubelle (différent du sacrifice des cartes
|
||||
rouges): vous pouvez retirer cette carte de votre jeu pour bénéficier,
|
||||
en plus des autres effets, de l'effet marqué. Mais la carte sera
|
||||
défaussée à tout jamais.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr />
|
||||
<h2>Effets</h2>
|
||||
<p>Une variété d'effets peuvent être inclus dans les cartes.</p>
|
||||
<p>
|
||||
Il y en a plein et les expliquer ici serait relou, alors je vous
|
||||
laisse poser la question en vocal si vous n'en comprenez pas un.
|
||||
</p>
|
||||
<p>​</p>
|
||||
<hr />
|
||||
<h2>Activer les cartes dans la version en ligne</h2>
|
||||
<p>
|
||||
Il suffit de cliquer sur la carte que vous voulez activer dans votre
|
||||
main pour la "LOCK".
|
||||
</p>
|
||||
<p>
|
||||
Une carte "LOCKED" est considérée en jeu, & peut activer des effets
|
||||
Allié pour d'autres cartes, etc.
|
||||
</p>
|
||||
<p>
|
||||
Si vous avez des choix à faire sur la carte, un menu apparaîtra pour
|
||||
vous laisser choisir.
|
||||
</p>
|
||||
<p>
|
||||
Pour faire des dommages, il y aura un bouton "Damage" sur vos ennemis.
|
||||
Pareil sur leurs champions.
|
||||
</p>
|
||||
<p>
|
||||
Quand vous avez un effet "Sacrifice", vous pouvez fouiller votre
|
||||
défausse pour choisir la carte à sacrifier. Vous pouvez également
|
||||
sacrifier une carte de la main qui n'est pas encore locked!
|
||||
</p>
|
||||
<h2>Zoomer sur les cartes</h2>
|
||||
<p>Pour zoomer sur une carte, maintenez votre clic droit.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.background {
|
||||
background-color: #f0f0f0;
|
||||
background-image: url('/images/bg-darkgrass.png');
|
||||
background-size: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100vw;
|
||||
;
|
||||
background-color: #f0f0f0;
|
||||
background-image: url("/img/bg-darkgrass.png");
|
||||
background-size: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
background-color: rgba(255, 255, 255, .75);
|
||||
padding: 1em;
|
||||
border-radius: 1em;
|
||||
max-width: 800px;
|
||||
margin-top: 2em;
|
||||
text-align: left;
|
||||
background-color: rgba(255, 255, 255, 0.75);
|
||||
padding: 1em;
|
||||
border-radius: 1em;
|
||||
max-width: 800px;
|
||||
margin-top: 2em;
|
||||
text-align: left;
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 2em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 2em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
p {
|
||||
margin: 1em 0;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.overlay-2 {
|
||||
height: 00px;
|
||||
width: 100%;
|
||||
position: sticky;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
|
||||
|
||||
height: 00px;
|
||||
width: 100%;
|
||||
position: sticky;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.erika-zone-2 {
|
||||
position: absolute;
|
||||
top: 300px;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
z-index: 2;
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
|
||||
|
||||
position: absolute;
|
||||
top: 300px;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
z-index: 2;
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.overlay-image {
|
||||
position: sticky;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
position: sticky;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineStore } from "pinia";
|
||||
import type { Game } from "~/netcode/interfaces";
|
||||
// import goStore, { useEffect } from "~/netcode";
|
||||
// import type { Card } from "~/netcode/Card";
|
||||
// import { CardEffects, type Effect } from "~/netcode/Effect";
|
||||
@@ -10,7 +11,7 @@ import { defineStore } from "pinia";
|
||||
// useClientSideStore().activatedClientside = []
|
||||
// })
|
||||
|
||||
export const useClientSideStore = defineStore("clientside", {
|
||||
export const useTurnStore = defineStore("turn", {
|
||||
// state: () => ({
|
||||
// activatedClientside: [] as Effect[],
|
||||
// effectsHandled: new Set([
|
||||
Reference in New Issue
Block a user