Merge branch 'feat/netcode-rework' of https://git.legonzaur.fr/heronfief/front into feat/netcode-rework
release-tag / release-image (push) Successful in 28s

This commit is contained in:
2024-07-16 23:27:28 +02:00
15 changed files with 873 additions and 108 deletions
+3
View File
@@ -0,0 +1,3 @@
NUXT_PUBLIC_ENDPOINT=http://localhost:8765
NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT=https://discord.com/oauth2/authorize?client_id=
NUXT_DEV_MODE=TRUE
+1
View File
@@ -6,6 +6,7 @@ on:
push:
branches:
- main
- feat/netcode-rework
jobs:
release-image:
+215 -36
View File
@@ -10,6 +10,7 @@ export interface Props {
interactible?: boolean;
noEffects?: boolean;
used_effects_indexes?: number[];
discardable?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
@@ -18,9 +19,17 @@ const props = withDefaults(defineProps<Props>(), {
locked: () => true,
interactible: () => true,
noEffects: () => false,
discardable: () => false,
});
const emit = defineEmits(["click", "effectClick"]);
const emit = defineEmits([
"click",
"effectClick",
"drop",
"move",
"startMove",
"stopMove",
]);
const settingsStore = useSettingsStore();
@@ -28,12 +37,35 @@ const childCount = ref<number>(0);
const card = ref<HTMLElement>();
const dragStatus = ref<{
hasClickedDown: boolean;
dragging: boolean;
intervalEvent?: ReturnType<typeof setTimeout>;
}>({
hasClickedDown: false,
dragging: false,
});
const initialOffsetX = ref(0);
const initialOffsetY = ref(0);
const mousePageX = ref(0);
const mousePageY = ref(0);
const offsetX = ref(0);
const offsetY = ref(0);
const offsetWidth = ref(0);
const offsetHeight = ref(0);
const translateX = ref(0);
const translateY = ref(0);
const extension = computed(() => {
if (settingsStore.cardStyle == CardStyles.SVG) {
return ".svg";
}
return ".png";
});
function cardClick() {
if (!props.card_id) return;
emit("click");
@@ -61,8 +93,126 @@ function rightClickUp(e: MouseEvent) {
isZooming.value = false;
}
function startDrag(e: MouseEvent) {
emit("startMove");
dragStatus.value.dragging = true;
translateX.value = 0;
translateY.value = 0;
const cardElement = card.value;
if (!cardElement) {
return;
}
mousePageX.value = e.pageX;
mousePageY.value = e.pageY;
initialOffsetX.value = e.offsetX;
initialOffsetY.value = e.offsetY;
offsetX.value = cardElement.offsetLeft + e.offsetX;
offsetY.value = cardElement.offsetTop + e.offsetY;
translateX.value = mousePageX.value - offsetX.value;
translateY.value = mousePageY.value - offsetY.value;
}
function leftClickDown(e: MouseEvent) {
dragStatus.value.hasClickedDown = true;
window.addEventListener("mousemove", mouseMove, false);
}
function leftClickUpGlobal(e: MouseEvent) {
mousePageX.value = e.pageX;
mousePageY.value = e.pageY;
if (dragStatus.value.dragging && dragStatus.value.hasClickedDown) {
dragStatus.value.dragging = false;
emit("drop", { x: e.pageX, y: e.pageY, uuid: props.card?._id });
emit("stopMove");
}
dragStatus.value.hasClickedDown = false;
clearTimeout(dragStatus.value.intervalEvent);
window.removeEventListener("mousemove", mouseMove, false);
}
function leftClickUp(e: MouseEvent) {
if (dragStatus.value.dragging == false && dragStatus.value.hasClickedDown) {
dragStatus.value.hasClickedDown = false;
cardClick();
return;
}
}
const sensitivity = 0.5;
function mouseMove(e: MouseEvent) {
if (
dragStatus.value.hasClickedDown &&
!dragStatus.value.dragging &&
Math.sqrt(Math.abs(e.movementX * e.movementY)) > sensitivity
) {
startDrag(e);
}
const cardElement = card.value;
if (!cardElement) {
return;
}
mousePageX.value = e.pageX;
mousePageY.value = e.pageY;
// console.log(cardElement.offsetLeft);
offsetX.value = cardElement.offsetLeft + initialOffsetX.value;
offsetY.value = cardElement.offsetTop + initialOffsetY.value;
translateX.value = mousePageX.value - offsetX.value;
translateY.value = mousePageY.value - offsetY.value;
// emit("move", { x: e.pageX, y: e.pageY, uuid: props.card?._id });
}
defineExpose({
offsetX,
offsetY,
offsetWidth,
offsetHeight,
uuid: props.card?._id,
});
onMounted(() => {
card.value?.style.setProperty("--random-seed", -Math.random() * 10 + "s");
window.addEventListener("mouseup", leftClickUpGlobal, false);
const cardElement = card.value;
if (!cardElement) {
return;
}
offsetX.value = cardElement.offsetLeft;
offsetY.value = cardElement.offsetTop;
offsetWidth.value = cardElement.offsetWidth;
offsetHeight.value = cardElement.offsetHeight;
cardElement.offsetWidth;
});
// onRenderTriggered(() => {
// const cardElement = card.value;
// if (!cardElement) {
// return;
// }
// offsetX.value = cardElement.offsetLeft + initialOffsetX.value;
// offsetY.value = cardElement.offsetTop + initialOffsetY.value;
// translateX.value = mousePageX.value - offsetX.value;
// translateY.value = mousePageY.value - offsetY.value;
// });
onUpdated(() => {
const cardElement = card.value;
if (!cardElement) {
return;
}
offsetX.value = cardElement.offsetLeft + initialOffsetX.value;
offsetY.value = cardElement.offsetTop + initialOffsetY.value;
translateX.value = mousePageX.value - offsetX.value;
translateY.value = mousePageY.value - offsetY.value;
});
onUnmounted(() => {
window.removeEventListener("mouseup", leftClickUpGlobal, false);
// window.addEventListener("mousemove", mouseMove, false);
});
</script>
@@ -71,25 +221,22 @@ onMounted(() => {
ref="card"
class="card"
:class="{
dragging: dragStatus.dragging,
active: childCount > 0,
wrapped,
unlocked: !props.locked,
zoom: isZooming,
no_effects: noEffects || !props.locked,
discardable,
}"
@contextmenu.prevent="false"
@click.left="cardClick"
@mousedown.right="rightClickDown"
@mouseup.right="rightClickUp"
@mousedown.left="leftClickDown"
@mouseup.left="leftClickUp"
@mouseenter="mouseenter"
@mouseleave="mouseleave"
>
<div
id="contextMenuButtons"
:ref="(el) => { childCount = ((el as Element)?.childElementCount ?? 0) }"
>
<slot></slot>
</div>
<template v-if="props.card_id">
<!-- <svg
width="180"
@@ -124,7 +271,7 @@ onMounted(() => {
"
draggable="false"
/>
<div></div>
<div id="overlay"><slot></slot></div>
</template>
<img
@@ -137,19 +284,15 @@ onMounted(() => {
</template>
<style scoped>
.card {
--max-height: 250px;
--max-width: 179px;
--margin-bottom: calc(var(--max-height) * 0.05);
position: relative;
display: flex;
justify-content: center;
margin-bottom: 0px;
margin-top: var(--margin-bottom);
transition: all 0.075s cubic-bezier(1, 1.81, 0.59, 1.42);
pointer-events: all;
max-height: var(--max-height);
max-width: var(--max-width);
.card.dragging {
transform: translateX(v-bind("translateX + 'px'"))
translateY(v-bind("translateY + 'px'"));
cursor: grabbing;
z-index: 999;
}
.card.dragging > svg {
transform: scale(110%);
}
.card.active {
@@ -172,15 +315,6 @@ onMounted(() => {
drop-shadow(-0.5px -0.5px 0.5px gray);
}
.card.unlocked {
margin-top: 0px;
margin-bottom: var(--margin-bottom);
transform: translateY(calc(var(--margin-bottom) * -0.25));
filter: drop-shadow(
calc(var(--margin-bottom) * 0.3) var(--margin-bottom) 1px rgba(0, 0, 0, 0.6)
);
}
.card.zoom {
transform: scale(200%);
z-index: 10;
@@ -207,10 +341,6 @@ onMounted(() => {
pointer-events: none;
}
.card.no_effects {
cursor: pointer;
}
#contextMenuButtons > * {
flex: 1;
border-radius: 12px;
@@ -253,4 +383,53 @@ onMounted(() => {
filter: grayscale(0.6);
pointer-events: none;
}
.svg_effect text {
cursor: pointer;
}
.card {
--max-height: 250px;
--max-width: 179px;
--margin-bottom: calc(var(--max-height) * 0.05);
position: relative;
display: flex;
justify-content: center;
margin-bottom: 0px;
margin-top: var(--margin-bottom);
transition: all 0.15s cubic-bezier(0.175, 0.885, 0.32, 1.275);
pointer-events: all;
max-height: var(--max-height);
max-width: var(--max-width);
/* cursor: grab; */
}
.card:not(svg) {
cursor: grab;
}
#overlay {
position: absolute;
top: 20%;
width: calc(100% - 8%);
height: 49%;
}
.card.unlocked {
margin-top: 0px;
margin-bottom: var(--margin-bottom);
transform: translateY(calc(var(--margin-bottom) * -0.25));
filter: drop-shadow(
calc(var(--margin-bottom) * 0.3) var(--margin-bottom) 1px rgba(0, 0, 0, 0.6)
);
cursor: pointer;
}
.card.discardable {
cursor: not-allowed;
}
.card.discardable svg {
pointer-events: none;
}
</style>
+92 -6
View File
@@ -1,6 +1,14 @@
<script setup lang="ts">
import { delay } from "~/netcode/events";
import type { Card, Container, PlayingTurn } from "~/netcode/interfaces";
import {
CardEffects,
CardType,
type Card,
type Container,
type Effect,
type PlayingTurn,
} from "~/netcode/interfaces";
import TokenComponent from "./tokens/TokenComponent.vue";
export interface Props {
container: Container;
@@ -8,15 +16,27 @@ export interface Props {
wrapped?: boolean;
hidden?: boolean;
noEffects?: boolean;
tokens?: Effect[];
championsKillable?: boolean;
discardable?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
wrapped: () => false,
hidden: () => false,
noEffects: () => false,
tokens: () => [],
championsKillable: () => false,
discardable: () => false,
});
const emit = defineEmits(["cardClick", "effectClick"]);
const emit = defineEmits([
"cardClick",
"effectClick",
"tokenClick",
"damageChampionClick",
"discardCardClick",
]);
const settingsStore = useSettingsStore();
@@ -34,6 +54,7 @@ const animationSpeed = computed(() => settingsStore.animationSpeed + "ms");
const shuffleAnimationSpeed = computed(
() => settingsStore.animationSpeed * 2 + "ms"
);
function populateContainer() {}
// const rotate = ref(false);
@@ -87,6 +108,28 @@ function endAnimation() {
onUnmounted(() => {
// containerEvents.delete(props.container._id);
});
const interval = ref<ReturnType<typeof setInterval>>();
// function startMove() {
// interval.value = setInterval(() => {
// props.container.cards.sort((a, b) => Math.random() - 0.5);
// }, 1000);
// }
// function stopMove() {
// clearInterval(interval.value);
// }
function tokenClick(token: Effect, card: Card) {
emit("tokenClick", { token, card });
}
function damageChampionClick(card: Card) {
emit("damageChampionClick", card);
}
function discardCardClick(card: Card) {
emit("discardCardClick", card);
}
</script>
<template>
@@ -98,22 +141,64 @@ onUnmounted(() => {
<CardPreview
@click="$emit('cardClick', c._id)"
@effectClick="$emit('effectClick', c._id, $event)"
v-for="c in cards"
:card_id="c.cardId"
v-for="(c, i) in cards"
:card_id="c?.cardId"
:wrapped="wrapped"
:key="c._id"
:key="c?._id || i"
:card="c"
:locked="
!playingTurn || playingTurn?.cardsLocked.includes(c._id.toString())
"
:used_effects_indexes="c.effects?.reduce( (prev,e, i)=>{
:used_effects_indexes="c?.effects?.reduce( (prev,e, i)=>{
if(playingTurn?.lockedEffects.includes(e._id)){
prev.push(i)
}
return prev
},[] as number[])"
:noEffects="noEffects"
:discardable="discardable"
>
<template v-for="t in tokens">
<TokenComponent
:token="t"
@useToken="tokenClick(t, c)"
v-if="
(t.effect == CardEffects.SACRIFICE &&
playingTurn &&
!playingTurn?.cardsLocked.includes(c._id.toString())) ||
(t.effect == CardEffects.PREPARE &&
c.cardType == CardType.CHAMPION) ||
(t.effect == CardEffects.STACK_NEXT_ACTION_BOUGHT &&
c.cardType == CardType.ACTION) ||
(t.effect == CardEffects.STUN &&
c.cardType == CardType.CHAMPION &&
championsKillable) ||
t.effect == CardEffects.PLAY_NEXT_CARD_BOUGHT ||
t.effect == CardEffects.STACK_NEXT_CARD_BOUGHT ||
t.effect == CardEffects.RESTACK_DISCARDED_CARD ||
(t.effect == CardEffects.RESTACK_DISCARDED_ACTION &&
c.cardType == CardType.ACTION) ||
(t.effect == CardEffects.RESTACK_DISCARDED_CHAMPION &&
c.cardType == CardType.CHAMPION)
"
></TokenComponent>
</template>
<button
v-if="championsKillable && c.cardType == CardType.CHAMPION"
@click.stop="damageChampionClick(c)"
@mousedown.left.stop=""
>
Damage
</button>
<button
v-if="
discardable && !playingTurn?.cardsLocked.includes(c._id.toString())
"
@mousedown.left.stop=""
@click.stop="discardCardClick(c)"
>
Discard
</button>
</CardPreview>
</TransitionGroup>
<!-- <img :class="`card_image`" :src="'/cards/background.png'" draggable="false"> -->
@@ -147,6 +232,7 @@ onUnmounted(() => {
flex-direction: row;
justify-content: space-evenly;
min-width: 200px;
flex-wrap: wrap;
transition: rotate v-bind("shuffleAnimationSpeed") ease;
}
+106
View File
@@ -0,0 +1,106 @@
<script setup lang="ts">
import { buyCard, buyGem, useToken } from "~/netcode";
import {
CardEffects,
type Card,
type Container,
type Effect,
type Game,
type Player,
} from "~/netcode/interfaces";
import TokenComponent from "./tokens/TokenComponent.vue";
export interface Props {
game: Game;
discard: Container;
player: Player;
}
const props = defineProps<Props>();
const authStore = useAuthStore();
const emit = defineEmits(["close"]);
function marketCardClick(e: string) {
buyCard(props.game._id, e);
}
function fireGemCardClick(e: string) {
buyGem(props.game._id);
}
const discardTokens = computed(() =>
props.game.currentTurn.tokens.filter((t) =>
[
CardEffects.RESTACK_DISCARDED_ACTION,
CardEffects.RESTACK_DISCARDED_CARD,
CardEffects.RESTACK_DISCARDED_CHAMPION,
CardEffects.SACRIFICE,
].includes(t.effect)
)
);
function tokenClick({ card, token }: { card: Card; token: Effect }) {
useToken(props.game._id, token._id, card._id);
}
</script>
<template>
<div class="overlay">
<button @click="$emit('close')">Close</button>
<span>Discard of {{ player.user.username }}</span>
<div class="discard_unwrapped" @click.stop>
<CardsGrouped
:container="discard"
:wrapped="false"
:hidden="false"
:tokens="discardTokens"
:noEffects="true"
:playingTurn="game.currentTurn"
@tokenClick="tokenClick"
></CardsGrouped>
</div>
</div>
</template>
<style scoped>
span {
background: white;
}
.overlay {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
position: fixed;
z-index: 200;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>
<style>
.discard_unwrapped {
width: max-content;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
/* position: relative; */
gap: 2px;
margin: 10px;
padding-left: 10px;
padding-right: 10px;
padding-bottom: 10px;
border-radius: 10px;
/* filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6)); */
/* backdrop-filter: blur(5px) brightness(80%); */
background: rgba(60, 47, 117, 0.46);
/* left: 50%;
top: 50%;
transform: translate(-50%, -50%); */
}
</style>
+46 -10
View File
@@ -1,6 +1,13 @@
<script setup lang="ts">
import { buyCard } from "~/netcode";
import type { Container, Game } from "~/netcode/interfaces";
import { buyCard, buyGem, useToken } from "~/netcode";
import {
CardEffects,
type Card,
type Container,
type Effect,
type Game,
} from "~/netcode/interfaces";
import TokenComponent from "./tokens/TokenComponent.vue";
export interface Props {
game: Game;
@@ -25,12 +32,35 @@ function marketCardClick(e: string) {
}
function fireGemCardClick(e: string) {
console.log(e);
buyGem(props.game._id);
}
const marketTokens = computed(() =>
props.game.currentTurn.tokens.filter((t) =>
[
CardEffects.STACK_NEXT_CARD_BOUGHT,
CardEffects.STACK_NEXT_ACTION_BOUGHT,
CardEffects.PLAY_NEXT_CARD_BOUGHT,
].includes(t.effect)
)
);
const gemsTokens = computed(() =>
props.game.currentTurn.tokens.filter((t) =>
[
CardEffects.STACK_NEXT_CARD_BOUGHT,
CardEffects.PLAY_NEXT_CARD_BOUGHT,
].includes(t.effect)
)
);
function tokenClick({ card, token }: { card: Card; token: Effect }) {
useToken(props.game._id, token._id, card._id);
}
</script>
<template>
<div id="market">
<div class="market">
<CardsGrouped
:container="marketStack"
:wrapped="true"
@@ -41,34 +71,40 @@ function fireGemCardClick(e: string) {
:container="market"
@cardClick="marketCardClick"
:noEffects="true"
></CardsGrouped>
:tokens="marketTokens"
@tokenClick="tokenClick"
>
</CardsGrouped>
<div class="separator"></div>
<CardsGrouped
:container="fire_gems"
:wrapped="true"
@cardClick="fireGemCardClick"
:noEffects="true"
:tokens="gemsTokens"
@tokenClick="tokenClick"
></CardsGrouped>
</div>
</template>
<style scoped>
#market {
width: min-content;
.market {
width: max-content;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
z-index: 2;
position: relative;
/* 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%);
isolation: initial;
/* 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);
}
+38 -1
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import type { Game, Player } from "~/netcode/interfaces";
import { damagePlayer, makeDiscard } from "~/netcode";
import { CardEffects, type Game, type Player } from "~/netcode/interfaces";
const emit = defineEmits(["click"]);
@@ -15,6 +16,24 @@ const props = withDefaults(defineProps<Props>(), {
isTurn: () => false,
self: () => false,
});
const authStore = useAuthStore();
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
);
function makeDiscardClick() {
makeDiscard(props.game._id, props.player._id);
}
function damage() {
damagePlayer(props.game._id, props.player._id, 1);
}
</script>
<template>
@@ -48,6 +67,24 @@ const props = withDefaults(defineProps<Props>(), {
<PlayerStats :game="props.game" :player="props.player"> </PlayerStats>
<!-- </template> -->
</div>
<button
v-if="
game.currentTurn.tokens.some(
(t) => t.effect == CardEffects.MAKE_DISCARD
) &&
props.player._id != selfPlayer?._id &&
!isTurn
"
@click.stop="makeDiscardClick"
>
Make Discard
</button>
<button
v-if="props.player._id != selfPlayer?._id && !isTurn"
@click.stop="damage"
>
Damage
</button>
</div>
</div>
</template>
+43 -3
View File
@@ -1,6 +1,21 @@
<script setup lang="ts">
import { endTurn, joinGame, lockCard, lockEffect, startGame } from "~/netcode";
import type { Game, Player } from "~/netcode/interfaces";
import {
damageChampion,
discardCard,
endTurn,
joinGame,
lockCard,
lockEffect,
startGame,
useToken,
} from "~/netcode";
import {
CardEffects,
type Card,
type Effect,
type Game,
type Player,
} from "~/netcode/interfaces";
import { useTurnStore } from "~/stores/turnStore";
export interface Props {
@@ -37,8 +52,15 @@ const isSelf = computed(
);
const isSelfTurn = computed(() => isSelf && isPlayerTurn);
const cardTokens = computed(() =>
props.game.currentTurn.tokens.filter(
(t) =>
[CardEffects.PREPARE, CardEffects.STUN].includes(t.effect) ||
(CardEffects.SACRIFICE == t.effect && isSelf)
)
);
const warningText = ref("Effets restants!");
const turnStore = useTurnStore();
// const make_discard = (target: Player) =>
// clientStore.findUseEffect(CardEffects.MAKE_DISCARD, target);
@@ -114,6 +136,16 @@ function effectClick(cardUUID: string, effectIndex: number) {
// function check_for_guard(player: Player) {
// return player.board.some(c => c.guard === true)
// }
function tokenClick({ card, token }: { card: Card; token: Effect }) {
useToken(props.game._id, token._id, card._id, props.player._id);
}
function damageChampionClick(card: Card) {
damageChampion(props.game._id, props.player._id, card._id);
}
function discardCardClick(card: Card) {
discardCard(props.game._id, card._id);
}
</script>
<template>
@@ -172,6 +204,14 @@ function effectClick(cardUUID: string, effectIndex: number) {
:playingTurn="props.game.currentTurn"
@cardClick="boardCardClick"
@effectClick="effectClick"
:tokens="cardTokens"
@tokenClick="tokenClick"
:championsKillable="!isSelf && isSelfTurn.value"
:discardable="
(player.fuckUMarkers > 0 || player.discardMarkers > 0) && isSelf
"
@damageChampionClick="damageChampionClick"
@discardCardClick="discardCardClick"
>
</CardsGrouped>
+12 -12
View File
@@ -28,21 +28,21 @@ const isTurn = computed(
</span>
</div>
<div class="turn_icon" v-if="isTurn">
<img src="/img/gold.png" class="icon_img" draggable="false" />
<span class="turn_icon_number">
{{ game.currentTurn.gold }}
</span>
</div>
<div class="turn_icon" v-if="isTurn">
<img src="/img/damage.png" class="icon_img" draggable="false" />
<span class="turn_icon_number">
{{ game.currentTurn.damage }}
</span>
</div>
<img src="/img/gold.png" class="icon_img" draggable="false" />
<span class="turn_icon_number">
{{ game.currentTurn.gold }}
</span>
</div>
<div class="turn_icon" v-if="isTurn">
<img src="/img/damage.png" class="icon_img" draggable="false" />
<span class="turn_icon_number">
{{ game.currentTurn.damage }}
</span>
</div>
<div class="turn_icon">
<img src="/img/fuck.svg" class="icon_img" draggable="false" />
<span class="turn_icon_number">{{
(isTurn && (player.discardMarkers ?? 0) + (player.fuckUMarkers ?? 0)) || 0
(player.discardMarkers ?? 0) + (player.fuckUMarkers ?? 0)
}}</span>
</div>
</template>
+37
View File
@@ -0,0 +1,37 @@
<script setup lang="ts">
import type { Effect } from "~/netcode/interfaces";
type Props = {
token: Effect;
};
const props = defineProps<Props>();
const emit = defineEmits(["useToken"]);
function onClick(e: MouseEvent) {
emit("useToken", props.token);
}
</script>
<template>
<div @click.stop="onClick" @mousedown.left.stop="" id="token">
{{ props.token.effect }}
</div>
</template>
<style scoped>
#token {
width: 50px;
height: 50px;
background: white;
border: 1px solid white;
cursor: pointer;
box-sizing: border-box;
transition: 0.2s all;
border-radius: 15px;
}
#token:hover {
border: 1px solid red;
}
</style>
+98 -24
View File
@@ -2,7 +2,7 @@ import type { Game, User } from "./interfaces";
export function subscribe(endpoint: string) {
const config = useRuntimeConfig();
const evtSource = new EventSource(new URL(endpoint, config.public.endpoint), {
const evtSource = new EventSource(config.public.endpoint + endpoint, {
withCredentials: true,
});
@@ -11,12 +11,9 @@ export function subscribe(endpoint: string) {
export async function getLobby() {
const config = useRuntimeConfig();
const req = await useFetch<Array<Game>>(
new URL("/games", config.public.endpoint).href,
{
credentials: "include",
}
);
const req = await useFetch<Array<Game>>(config.public.endpoint + "/games", {
credentials: "include",
});
if (req.data.value == null) {
throw new Error("Lobby answer is empty");
}
@@ -26,7 +23,7 @@ export async function getLobby() {
export async function whoami() {
const config = useRuntimeConfig();
const { data, pending, error, refresh } = await useFetch<User>(
new URL("/whoami", config.public.endpoint).href,
config.public.endpoint + "/whoami",
{
credentials: "include",
}
@@ -48,13 +45,10 @@ export async function whoami() {
export async function createGame() {
const config = useRuntimeConfig();
const res = await $fetch<Game>(
new URL("/games", config.public.endpoint).href,
{
method: "POST",
credentials: "include",
}
);
const res = await $fetch<Game>(config.public.endpoint + "/games", {
method: "POST",
credentials: "include",
});
console.log(res);
navigateTo("/game/" + res._id);
}
@@ -62,7 +56,7 @@ export async function createGame() {
export async function startGame(game: string) {
const config = useRuntimeConfig();
const res = await $fetch<Game>(
new URL(`games/${game}/start`, config.public.endpoint).href,
config.public.endpoint + `/games/${game}/start`,
{
method: "POST",
credentials: "include",
@@ -73,7 +67,7 @@ export async function startGame(game: string) {
export async function joinGame(game: string) {
const config = useRuntimeConfig();
const res = await $fetch<Game>(
new URL(`games/${game}/join`, config.public.endpoint).href,
config.public.endpoint + `/games/${game}/join`,
{
method: "POST",
credentials: "include",
@@ -83,7 +77,7 @@ export async function joinGame(game: string) {
export async function endTurn(game: string) {
const config = useRuntimeConfig();
await $fetch(new URL(`games/${game}/endTurn`, config.public.endpoint).href, {
await $fetch(config.public.endpoint + `/games/${game}/endTurn`, {
method: "POST",
credentials: "include",
});
@@ -92,8 +86,7 @@ export async function endTurn(game: string) {
export async function lockCard(game: string, cardId: string) {
const config = useRuntimeConfig();
await $fetch(
new URL(`games/${game}/turn/lockCard/${cardId}`, config.public.endpoint)
.href,
config.public.endpoint + `/games/${game}/turn/lockCard/${cardId}`,
{
method: "POST",
credentials: "include",
@@ -104,8 +97,7 @@ export async function lockCard(game: string, cardId: string) {
export async function buyCard(game: string, cardId: string) {
const config = useRuntimeConfig();
await $fetch(
new URL(`games/${game}/turn/buyCard/${cardId}`, config.public.endpoint)
.href,
config.public.endpoint + `/games/${game}/turn/buyCard/${cardId}`,
{
method: "POST",
credentials: "include",
@@ -113,11 +105,93 @@ export async function buyCard(game: string, cardId: string) {
);
}
export async function buyGem(game: string) {
const config = useRuntimeConfig();
await $fetch(config.public.endpoint + `/games/${game}/turn/buyGem`, {
method: "POST",
credentials: "include",
});
}
export async function useToken(
game: string,
tokenId: string,
cardId?: string,
playerId?: string
) {
const config = useRuntimeConfig();
await $fetch(
config.public.endpoint + `/games/${game}/turn/useToken/${tokenId}`,
{
method: "POST",
credentials: "include",
body: { cardId, playerId },
}
);
}
export async function lockEffect(game: string, effectId: string) {
const config = useRuntimeConfig();
await $fetch(
new URL(`games/${game}/turn/lockEffect/${effectId}`, config.public.endpoint)
.href,
config.public.endpoint + `/games/${game}/turn/lockEffect/${effectId}`,
{
method: "POST",
credentials: "include",
}
);
}
export async function makeDiscard(
game: string,
playerId: string,
amount: number = 1
) {
const config = useRuntimeConfig();
await $fetch(
config.public.endpoint + `/games/${game}/turn/makeDiscard/${playerId}`,
{
method: "POST",
credentials: "include",
}
);
}
export async function damagePlayer(
game: string,
playerId: string,
amount: number = 1
) {
const config = useRuntimeConfig();
await $fetch(
config.public.endpoint + `/games/${game}/turn/damagePlayer/${playerId}`,
{
method: "POST",
credentials: "include",
body: { amount },
}
);
}
export async function damageChampion(
game: string,
playerId: string,
cardId: string
) {
const config = useRuntimeConfig();
await $fetch(
config.public.endpoint + `/games/${game}/turn/damageChampion/${cardId}`,
{
method: "POST",
credentials: "include",
body: { playerId },
}
);
}
export async function discardCard(game: string, cardId: string) {
const config = useRuntimeConfig();
await $fetch(
config.public.endpoint + `/games/${game}/turn/discardCard/${cardId}`,
{
method: "POST",
credentials: "include",
+57 -1
View File
@@ -1,3 +1,56 @@
export enum CardEffects {
//Base
GOLD = "gold",
DAMAGE = "damage",
HEAL = "heal",
PREPARE = "prepare",
STUN = "stun",
SACRIFICE = "sacrifice",
DRAW = "draw",
DRAW_AND_DISCARD = "draw_and_discard",
MAKE_DISCARD = "make_discard",
RESTACK_DISCARDED_CHAMPION = "restack_discarded_champion",
RESTACK_DISCARDED_CARD = "restack_discarded_card",
STACK_NEXT_ACTION_BOUGHT = "stack_next_action_bought",
STACK_NEXT_CARD_BOUGHT = "stack_next_card_bought",
PLAY_NEXT_CARD_BOUGHT = "play_next_card_bought",
//DLCs
BUY_FOR_FREE = "buy_for_free",
//Journeys
DAMAGE_ALL_CHAMPIONS = "damage_all_champions",
//Journeys: Hunters
DISCARD_X_AND_DRAW_X = "discard_x_and_draw_x",
PREPARE_ANOTHER_CHAMPION = "prepare_another_champion",
//Journey: Travelers
PREPARE_ALL_CHAMPIONS = "prepare_all_champions",
CHEAPER_CHAMPION = "cheaper_champion",
CHEAPER_CHAMPIONS_PASSIVE = "cheaper_champions_passive",
CHEAPER_ACTION = "cheaper_action",
CONTROL_OPPOSING_CHAMPION_PASSIVE = "control_opposing_champion_passive",
RESTACK_DISCARDED_ACTION = "restack_discarded_action",
//Ancestry
KEEP_IN_HAND = "keep_in_hand",
BUY_GEM_FOR_FREE = "buy_gem_for_free",
CHEAPER_SKILLS_PASSIVE = "cheaper_skills_passive",
CHEAPER_CARD_IF_HIGHER_PRICE = "cheaper_card_if_higher_price",
PICK_FACTION = "pick_faction",
}
export enum CardType {
HERO = "hero",
HERO_ABILITY = "hero_ability",
CURRENCY = "currency",
WEAPON = "weapon",
CHAMPION = "champion",
ACTION = "action",
ITEM = "item",
}
export interface GameObject {
_id: string;
}
@@ -47,8 +100,11 @@ export interface Container extends GameObject {
shuffling?: boolean;
}
export interface Effect extends GameObject {}
export interface Effect extends GameObject {
effect: CardEffects;
}
export interface Card extends GameObject {
cardId: number;
cardType: CardType;
effects: Effect[];
}
+2 -3
View File
@@ -4,9 +4,8 @@ export default defineNuxtConfig({
ssr: false,
runtimeConfig: {
public: {
endpoint: "http://localhost:8765", // can be overridden by NUXT_PUBLIC_ENDPOINT environment variable
discordLoginEndpoint:
"https://discord.com/oauth2/authorize?client_id=460020057867681792&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8765%2Flogin&scope=identify",
endpoint: process.env.NUXT_PUBLIC_ENDPOINT, // can be overridden by NUXT_PUBLIC_ENDPOINT environment variable
discordLoginEndpoint: process.env.NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT,
},
},
modules: ["@pinia/nuxt", "@pinia-plugin-persistedstate/nuxt"],
+120 -12
View File
@@ -1,9 +1,15 @@
<script setup lang="ts">
import LoadingScreen from "~/components/LoadingScreen.vue";
import { subscribe } from "~/netcode";
import { subscribe, useToken } from "~/netcode";
import { delay } from "~/netcode/events";
import { type Container, type Game, type Player } from "~/netcode/interfaces";
import {
type Container,
type Effect,
type Game,
type Player,
} from "~/netcode/interfaces";
import { playSound } from "~/helpers/audioHelpers";
import TokenComponent from "~/components/tokens/TokenComponent.vue";
const config = useRuntimeConfig();
const settingsStore = useSettingsStore();
@@ -17,19 +23,15 @@ const {
pending,
error,
refresh,
} = await useFetch<Game>(
new URL(`/games/${gameId}`, config.public.endpoint).href,
{
credentials: "include",
}
);
} = await useFetch<Game>(config.public.endpoint + `/games/${gameId}`, {
credentials: "include",
});
if (game.value == null) {
navigateTo("/lobby");
throw new Error("game not found");
}
console.log(game);
const containers = computed<Record<string, Container>>(() => ({
[game.value!.fireGems._id]: game.value!.fireGems,
[game.value!.market._id]: game.value!.market,
@@ -147,6 +149,20 @@ eventSource.addEventListener("message", async (message) => {
current_game.currentTurn.cardsLocked.push(data.card);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "destroyCard") {
const fromContainer = containers.value[data.from as string];
queueAction(async () => {
const index = fromContainer.cards.findIndex(
(c) => !c || c._id == data.card
);
fromContainer.cards.splice(index, 1);
await delay(settingsStore.animationSpeed);
});
queueAction(async () => {
playSound("/sounds/destroy.mp3");
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "joinGame") {
queueAction(async () => {
playSound("/sounds/teleport.mp3");
@@ -157,6 +173,14 @@ eventSource.addEventListener("message", async (message) => {
current_game.currentTurn.lockedEffects.push(data.effect);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "unlockEffect") {
queueAction(async () => {
const effectIndex = current_game.currentTurn.lockedEffects.indexOf(
data.effect
);
current_game.currentTurn.lockedEffects.splice(effectIndex, 1);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "currentTurn.updateGold") {
queueAction(async () => {
console.log(`adding gold amount : ` + data.operation);
@@ -172,6 +196,14 @@ eventSource.addEventListener("message", async (message) => {
current_game.currentTurn.tokens.push(data.token);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "currentTurn.useToken") {
queueAction(async () => {
const index = current_game.currentTurn.tokens.findIndex(
(t) => t._id == data.tokenId
);
current_game.currentTurn.tokens.splice(index, 1);
await delay(settingsStore.animationSpeed);
});
} else if (data.type == "team.updateHealth") {
queueAction(async () => {
const team = game.value?.teams.find((t) => t._id == data.team);
@@ -181,6 +213,56 @@ eventSource.addEventListener("message", async (message) => {
console.log(`adding health amount : ` + data.operation);
team.health = data.health;
});
} else if (data.type == "player.addFuckUMarker") {
queueAction(async () => {
console.log(`adding fuckUMarkers : ` + data.operation);
const targetPlayer = playerList.value.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot add fuckU Markers : player not found");
}
targetPlayer.fuckUMarkers = data.fuckUMarkers;
});
} else if (data.type == "player.removeFuckUMarker") {
queueAction(async () => {
console.log(`adding fuckUMarkers : ` + data.operation);
const targetPlayer = playerList.value.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot remove fuckU Markers : player not found");
}
targetPlayer.fuckUMarkers = data.fuckUMarkers;
});
} else if (data.type == "player.addDiscardMarker") {
queueAction(async () => {
console.log(`adding discardMarkers : ` + data.operation);
const targetPlayer = playerList.value.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot add discard Markers : player not found");
}
targetPlayer.discardMarkers = data.discardMarkers;
});
} else if (data.type == "player.removeDiscardMarker") {
queueAction(async () => {
console.log(`adding fuckUMarkers : ` + data.operation);
const targetPlayer = playerList.value.find((p) => p._id == data.playerId);
if (!targetPlayer) {
throw new Error("Cannot remove discard Markers : player not found");
}
targetPlayer.discardMarkers = data.discardMarkers;
});
} else if (data.type == "killTeam") {
queueAction(async () => {
const index = game.value?.teams.findIndex((t) => t._id == data.teamId);
if (!index) {
console.error("team index not found. Desyncs might happen");
await refresh();
return;
}
game.value?.teams.splice(index, 1);
});
} else if (data.type == "endGame") {
queueAction(async () => {
alert("game Ended");
});
}
});
@@ -245,6 +327,14 @@ function focusPlayer(p: Player) {
// focusPlayer(first_player)
// })
function useTokenClick(t: Effect) {
console.log(t);
useToken(game.value!._id, t._id);
}
function showDiscard() {
discard_unwrapped.value = true;
}
onUnmounted(() => {
eventSource.close();
});
@@ -272,7 +362,12 @@ onUnmounted(() => {
:class="{ invisible: discard_unwrapped }"
@click="() => (discard_unwrapped = true)"
>
<CardsGrouped :container="focusedPlayer.discard" :wrapped="true">
<CardsGrouped
:container="focusedPlayer.discard"
:wrapped="true"
@card-click="showDiscard"
:no-effects="true"
>
</CardsGrouped>
</div>
</div>
@@ -284,7 +379,13 @@ onUnmounted(() => {
:fire_gems="game.fireGems"
:marketStack="game.marketStack"
></MarketCards>
<div id="turnStats">{{ game.currentTurn.tokens }}</div>
<div>
<TokenComponent
:token="t"
v-for="t in game.currentTurn.tokens"
@useToken="useTokenClick"
></TokenComponent>
</div>
<div id="player_list_container">
<div id="player_list">
<PlayerListElement
@@ -309,6 +410,13 @@ onUnmounted(() => {
></PlayerSlot>
</Transition>
</div>
<DiscardOverlay
v-show="discard_unwrapped"
:game="game"
:player="focusedPlayer"
:discard="focusedPlayer.discard"
@close="discard_unwrapped = false"
></DiscardOverlay>
<ResultDialog
:isVisible="showResults"
:won="isWinner"
@@ -432,7 +540,7 @@ onUnmounted(() => {
.slide-left-enter-active,
.slide-left-leave-active {
transition: all v-bind("turnChangeAnimationSpeed") ease-out;
transition: all v-bind("settingsStore.animationSpeed * 2 + 'ms'") ease-out;
}
.slide-left-enter-from {
Binary file not shown.