This commit is contained in:
@@ -8,8 +8,6 @@ const nuxtApp = useNuxtApp();
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
await authStore.whoami();
|
||||
|
||||
console.log("compiling cards...");
|
||||
const app = getCurrentInstance()?.appContext.app;
|
||||
if (!app) {
|
||||
@@ -23,6 +21,9 @@ console.log("compiling done");
|
||||
useHead({
|
||||
title: "Héron Realms",
|
||||
});
|
||||
onMounted(()=>{
|
||||
authStore.whoami();
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="root_dom_div">
|
||||
|
||||
@@ -3,7 +3,6 @@ const settingsStore = useSettingsStore();
|
||||
|
||||
import gsap from "gsap";
|
||||
import ShaderWorker from "~/assets/workers/ShaderWorker?worker";
|
||||
import type { Game } from "~/netcode/interfaces";
|
||||
|
||||
const emit = defineEmits<{
|
||||
ready: [];
|
||||
@@ -25,6 +24,14 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const _colorStart = reactive([0, 0.3, 0]);
|
||||
const _colorEnd = reactive([0, 1, 0]);
|
||||
|
||||
if (props.isTurn) {
|
||||
Object.assign(_colorStart, [0, 0.3, 0]);
|
||||
Object.assign(_colorEnd, [0, 1, 0]);
|
||||
} else {
|
||||
Object.assign(_colorStart, [0, 0.2, 0.3]);
|
||||
Object.assign(_colorEnd, [0, 1, 1]);
|
||||
}
|
||||
|
||||
function initShader() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!canvas.value) {
|
||||
@@ -127,20 +134,20 @@ watch(
|
||||
gsap.to(_colorStart, { duration: 0.5, 0: 0, 1: 0.2, 2: 0.3 });
|
||||
gsap.to(_colorEnd, { duration: 0.5, 0: 0, 1: 1, 2: 1 });
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
}
|
||||
);
|
||||
|
||||
watch([_colorEnd, _colorStart], () => {
|
||||
recolorShader();
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(() => {
|
||||
worker = new ShaderWorker();
|
||||
recolorShader();
|
||||
await initShader();
|
||||
initShader().then(() => {
|
||||
emit("ready");
|
||||
});
|
||||
window.addEventListener("resize", resizeShader, true);
|
||||
emit("ready");
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -150,13 +157,8 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas
|
||||
ref="canvas"
|
||||
id="background"
|
||||
:class="{ pixelated: settingsStore.shaderPixelated }"
|
||||
width="480"
|
||||
height="270"
|
||||
></canvas>
|
||||
<canvas ref="canvas" id="background" :class="{ pixelated: settingsStore.shaderPixelated }" width="480"
|
||||
height="270"></canvas>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -214,7 +214,7 @@ function discardCardClick(card: Card) {
|
||||
@discardCardClick="discardCardClick"
|
||||
>
|
||||
</CardsGrouped>
|
||||
|
||||
<br>
|
||||
<!-- <div class="cards_container"> -->
|
||||
<!-- HAND -->
|
||||
<!-- <template v-if="isSelf">
|
||||
|
||||
@@ -51,11 +51,12 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
// return navigateTo('/login')
|
||||
// }
|
||||
// }
|
||||
|
||||
console.log(to.path)
|
||||
if (to.path.startsWith("/game")) {
|
||||
if (Array.isArray(to.params.id)) {
|
||||
return navigateTo("/lobby");
|
||||
}
|
||||
console.log(to.query.id)
|
||||
// if (Array.isArray(to.query.id)) {
|
||||
// return navigateTo("/lobby");
|
||||
// }
|
||||
return;
|
||||
}
|
||||
if (to.path == "/lobby") {
|
||||
|
||||
+6
-5
@@ -25,25 +25,26 @@ export async function getLobby() {
|
||||
|
||||
export async function whoami() {
|
||||
const config = useRuntimeConfig();
|
||||
const { data, pending, error, refresh } = await useFetch<User>(
|
||||
const req = await useFetch<User>(
|
||||
config.public.endpoint + "/whoami",
|
||||
{
|
||||
credentials: "include",
|
||||
server: false,
|
||||
}
|
||||
);
|
||||
if (error.value?.statusCode && [401].includes(error.value.statusCode)) {
|
||||
await req.execute();
|
||||
if (req.error.value && req.error.value.statusCode == 401) {
|
||||
// await navigateTo(config.public.discordLoginEndpoint, {
|
||||
// external: true,
|
||||
// });
|
||||
throw new Error("You are not logged in");
|
||||
}
|
||||
if (error.value) {
|
||||
if (req.error.value) {
|
||||
throw new Error(
|
||||
"Something went wrong while trying to get user information"
|
||||
);
|
||||
}
|
||||
|
||||
return data;
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function createGame() {
|
||||
|
||||
+535
@@ -0,0 +1,535 @@
|
||||
<script setup lang="ts">
|
||||
import LoadingScreen from "~/components/LoadingScreen.vue";
|
||||
import { subscribe, useToken } from "~/netcode";
|
||||
import { delay } from "~/netcode/events";
|
||||
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();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const route = useRoute();
|
||||
const gameId = route.query.id;
|
||||
|
||||
|
||||
const {
|
||||
data: game,
|
||||
pending,
|
||||
error,
|
||||
refresh,
|
||||
execute
|
||||
} = await useFetch<Game>(config.public.endpoint + `/games/${gameId}`, {
|
||||
credentials: "include",
|
||||
server: false
|
||||
});
|
||||
|
||||
const awaitGameReady = execute()
|
||||
|
||||
const containers = computed<Record<string, Container>>(() => ({
|
||||
[game.value!.fireGems._id]: game.value!.fireGems,
|
||||
[game.value!.market._id]: game.value!.market,
|
||||
[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)
|
||||
),
|
||||
}));
|
||||
|
||||
let eventSource: EventSource;
|
||||
|
||||
const actions = ref<(() => Promise<any>)[]>([]);
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
awaitGameReady.then(() => {
|
||||
console.log(game.value)
|
||||
if (game.value == null) {
|
||||
navigateTo("/lobby");
|
||||
throw new Error("game not found");
|
||||
}
|
||||
focusedPlayer.value = (selfPlayer.value ?? playerList.value![0])
|
||||
|
||||
eventSource = subscribe(`/games/${gameId}/subscribe`);
|
||||
eventSource.addEventListener("message", async (message) => {
|
||||
const current_game = game.value;
|
||||
if (!current_game) {
|
||||
throw new Error("game not found");
|
||||
}
|
||||
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) {
|
||||
playSound("/sounds/distribute.mp3");
|
||||
fromContainer.cards.push(card);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
}
|
||||
});
|
||||
queueAction(async () => {
|
||||
// await containerEvents.get(data.container)?.endAnimation();
|
||||
});
|
||||
} else if (data.type == "shuffleContainer") {
|
||||
queueAction(async () => {
|
||||
playSound("/sounds/shuffle.mp3");
|
||||
const container = containers.value[data.container as string];
|
||||
container.shuffling = true;
|
||||
await delay(settingsStore.animationSpeed * 2);
|
||||
container.shuffling = false;
|
||||
await delay(settingsStore.animationSpeed * 2);
|
||||
});
|
||||
} else if (data.type == "distributeCards") {
|
||||
// containerEvents.get(data.from)?.getCardsPosition(data.cards)
|
||||
|
||||
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 || c._id == card._id
|
||||
);
|
||||
fromContainer.cards.splice(index, 1);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
});
|
||||
|
||||
queueAction(async () => {
|
||||
playSound("/sounds/distribute.mp3");
|
||||
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 = data.currentTurn;
|
||||
await delay(settingsStore.animationSpeed);
|
||||
const playingPlayer = current_game.teams.find(
|
||||
(t) => t._id == current_game.currentTurn.currentTeam
|
||||
)?.players[0];
|
||||
if (playingPlayer) {
|
||||
focusPlayer(playingPlayer);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
}
|
||||
});
|
||||
} else if (data.type == "lockCard") {
|
||||
queueAction(async () => {
|
||||
playSound("/sounds/lock.mp3");
|
||||
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");
|
||||
current_game.teams.push(data.team);
|
||||
});
|
||||
} else if (data.type == "lockEffect") {
|
||||
queueAction(async () => {
|
||||
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);
|
||||
current_game.currentTurn.gold = data.gold;
|
||||
});
|
||||
} else if (data.type == "currentTurn.updateDamage") {
|
||||
queueAction(async () => {
|
||||
console.log(`adding damage amount : ` + data.operation);
|
||||
current_game.currentTurn.damage = data.damage;
|
||||
});
|
||||
} else if (data.type == "currentTurn.addToken") {
|
||||
queueAction(async () => {
|
||||
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);
|
||||
if (!team) {
|
||||
throw new Error("Team not found");
|
||||
}
|
||||
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");
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
const playerList = computed(() =>
|
||||
game.value && 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 isTurn = computed(
|
||||
() =>
|
||||
game.value &&
|
||||
selfPlayer.value &&
|
||||
game.value.teams.find((t) => t.players.includes(selfPlayer.value!))?._id ==
|
||||
game.value.currentTurn.currentTeam
|
||||
);
|
||||
|
||||
const turnChangeAnimationSpeed = computed(
|
||||
() => settingsStore.animationSpeed * 2 + "ms"
|
||||
);
|
||||
// TODO : current turn sound
|
||||
|
||||
// TODO : winner screen
|
||||
|
||||
// TODO : join sound
|
||||
|
||||
const showResults = ref(false);
|
||||
const isWinner = ref(false);
|
||||
const loaded = ref(false);
|
||||
const discard_unwrapped = ref(false);
|
||||
const focusedPlayer = ref<Player | undefined>(undefined);
|
||||
|
||||
function showLoserScreen() {
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true;
|
||||
isWinner.value = false;
|
||||
}
|
||||
|
||||
function showWinnerScreen() {
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true;
|
||||
isWinner.value = true;
|
||||
}
|
||||
|
||||
// const clientStore = useClientSideStore()
|
||||
// const restack_discarded_champion = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CHAMPION, target)
|
||||
// const restack_discarded_card = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CARD, target)
|
||||
// const sacrifice = (target: Card) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
|
||||
|
||||
function focusPlayer(p: Player) {
|
||||
focusedPlayer.value = p;
|
||||
}
|
||||
|
||||
// watch<Team>(() => goStore.current_game!.current_turn, (new_team, old_team) => {
|
||||
// if (focusedPlayer.value.team != old_team) { return }
|
||||
// const first_player = goStore.current_game?.players.find(p => p.team == goStore.current_game?.current_turn)
|
||||
// if (!first_player) {
|
||||
// return
|
||||
// }
|
||||
// focusPlayer(first_player)
|
||||
// })
|
||||
|
||||
function useTokenClick(t: Effect) {
|
||||
console.log(t);
|
||||
useToken(game.value!._id, t._id);
|
||||
}
|
||||
|
||||
function showDiscard() {
|
||||
discard_unwrapped.value = true;
|
||||
}
|
||||
onUnmounted(() => {
|
||||
eventSource.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @contextmenu.prevent="false" id="game_board">
|
||||
<AnimatedBackground @ready="loaded = true" :isTurn="isTurn ?? false"></AnimatedBackground>
|
||||
<!-- <AnimatedBackground :isTurn="false"></AnimatedBackground> -->
|
||||
<template v-if="game && focusedPlayer && playerList">
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<div id="stack_discard" :key="focusedPlayer?._id">
|
||||
<div id="stack">
|
||||
<CardsGrouped :container="focusedPlayer.stack" :wrapped="true" :hidden="true">
|
||||
</CardsGrouped>
|
||||
</div>
|
||||
|
||||
<div id="discard" :class="{ invisible: discard_unwrapped }" @click="() => (discard_unwrapped = true)">
|
||||
<CardsGrouped :container="focusedPlayer.discard" :wrapped="true" @card-click="showDiscard"
|
||||
:no-effects="true">
|
||||
</CardsGrouped>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<MarketCards :game="game" :market="game.market" :fire_gems="game.fireGems" :marketStack="game.marketStack">
|
||||
</MarketCards>
|
||||
<div>
|
||||
<TokenComponent :token="t" v-for="t in game.currentTurn.tokens" @useToken="useTokenClick"></TokenComponent>
|
||||
</div>
|
||||
<div id="player_list_container">
|
||||
<div id="player_list">
|
||||
<PlayerListElement v-for="p in playerList" :player="p" :game="game" @click="focusPlayer(p)"
|
||||
:focused="focusedPlayer == p" :self="selfPlayer == p">
|
||||
</PlayerListElement>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="current_player">
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<PlayerSlot v-if="focusedPlayer" :player="focusedPlayer" :game="game" :key="focusedPlayer._id"></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" :players="playerList" @close="showResults = false">
|
||||
</ResultDialog>
|
||||
<GlobalOverlay></GlobalOverlay>
|
||||
<SelfView v-if="selfPlayer" :container="selfPlayer.hand"></SelfView>
|
||||
</template>
|
||||
<!-- <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, 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 turn_stats"
|
||||
"player_list player_list player_list"
|
||||
"current_player current_player current_player";
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#player_list {
|
||||
display: flex;
|
||||
flex-direction: inherit;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#current_player {
|
||||
grid-area: current_player;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
/* background: url('/img/bg-darkgrass.png'); */
|
||||
}
|
||||
|
||||
#stack_discard {
|
||||
grid-area: stack_discard;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#stack,
|
||||
#discard {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#discard {
|
||||
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;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
#discard_unwrapped_bg.unwrapped {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#market {
|
||||
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;
|
||||
|
||||
/* background-image: url("/img/bg-darkgrass.png"); */
|
||||
}
|
||||
|
||||
#player_list {
|
||||
/* border: 5px solid #000; */
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.slide-left-enter-active,
|
||||
.slide-left-leave-active {
|
||||
transition: all v-bind("settingsStore.animationSpeed * 2 + 'ms'") ease-out;
|
||||
}
|
||||
|
||||
.slide-left-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.slide-left-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
</style>
|
||||
@@ -1,566 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import LoadingScreen from "~/components/LoadingScreen.vue";
|
||||
import { subscribe, useToken } from "~/netcode";
|
||||
import { delay } from "~/netcode/events";
|
||||
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();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const route = useRoute();
|
||||
const gameId = route.params.id;
|
||||
|
||||
const {
|
||||
data: game,
|
||||
pending,
|
||||
error,
|
||||
refresh,
|
||||
} = await useFetch<Game>(config.public.endpoint + `/games/${gameId}`, {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (game.value == null) {
|
||||
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)
|
||||
),
|
||||
}));
|
||||
|
||||
let eventSource: EventSource;
|
||||
|
||||
const actions = ref<(() => Promise<any>)[]>([]);
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
eventSource = subscribe(`/games/${gameId}/subscribe`);
|
||||
eventSource.addEventListener("message", async (message) => {
|
||||
const current_game = game.value;
|
||||
if (!current_game) {
|
||||
throw new Error("game not found");
|
||||
}
|
||||
const data = JSON.parse(message.data);
|
||||
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) {
|
||||
playSound("/sounds/distribute.mp3");
|
||||
fromContainer.cards.push(card);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
}
|
||||
});
|
||||
queueAction(async () => {
|
||||
// await containerEvents.get(data.container)?.endAnimation();
|
||||
});
|
||||
} else if (data.type == "shuffleContainer") {
|
||||
queueAction(async () => {
|
||||
playSound("/sounds/shuffle.mp3");
|
||||
const container = containers.value[data.container as string];
|
||||
container.shuffling = true;
|
||||
await delay(settingsStore.animationSpeed * 2);
|
||||
container.shuffling = false;
|
||||
await delay(settingsStore.animationSpeed * 2);
|
||||
});
|
||||
} else if (data.type == "distributeCards") {
|
||||
// containerEvents.get(data.from)?.getCardsPosition(data.cards)
|
||||
|
||||
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 || c._id == card._id
|
||||
);
|
||||
fromContainer.cards.splice(index, 1);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
});
|
||||
|
||||
queueAction(async () => {
|
||||
playSound("/sounds/distribute.mp3");
|
||||
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 = data.currentTurn;
|
||||
await delay(settingsStore.animationSpeed);
|
||||
const playingPlayer = current_game.teams.find(
|
||||
(t) => t._id == current_game.currentTurn.currentTeam
|
||||
)?.players[0];
|
||||
if (playingPlayer) {
|
||||
focusPlayer(playingPlayer);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
}
|
||||
});
|
||||
} else if (data.type == "lockCard") {
|
||||
queueAction(async () => {
|
||||
playSound("/sounds/lock.mp3");
|
||||
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");
|
||||
current_game.teams.push(data.team);
|
||||
});
|
||||
} else if (data.type == "lockEffect") {
|
||||
queueAction(async () => {
|
||||
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);
|
||||
current_game.currentTurn.gold = data.gold;
|
||||
});
|
||||
} else if (data.type == "currentTurn.updateDamage") {
|
||||
queueAction(async () => {
|
||||
console.log(`adding damage amount : ` + data.operation);
|
||||
current_game.currentTurn.damage = data.damage;
|
||||
});
|
||||
} else if (data.type == "currentTurn.addToken") {
|
||||
queueAction(async () => {
|
||||
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);
|
||||
if (!team) {
|
||||
throw new Error("Team not found");
|
||||
}
|
||||
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");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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 isTurn = computed(
|
||||
() =>
|
||||
selfPlayer.value &&
|
||||
game.value &&
|
||||
game.value.teams.find((t) => t.players.includes(selfPlayer.value!))?._id ==
|
||||
game.value.currentTurn.currentTeam
|
||||
);
|
||||
|
||||
const turnChangeAnimationSpeed = computed(
|
||||
() => settingsStore.animationSpeed * 2 + "ms"
|
||||
);
|
||||
// TODO : current turn sound
|
||||
|
||||
// TODO : winner screen
|
||||
|
||||
// TODO : join sound
|
||||
|
||||
const showResults = ref(false);
|
||||
const isWinner = ref(false);
|
||||
const loaded = ref(false);
|
||||
const discard_unwrapped = ref(false);
|
||||
const focusedPlayer = ref<Player>(selfPlayer.value ?? playerList.value[0]);
|
||||
|
||||
function showLoserScreen() {
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true;
|
||||
isWinner.value = false;
|
||||
}
|
||||
|
||||
function showWinnerScreen() {
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true;
|
||||
isWinner.value = true;
|
||||
}
|
||||
|
||||
// const clientStore = useClientSideStore()
|
||||
// const restack_discarded_champion = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CHAMPION, target)
|
||||
// const restack_discarded_card = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CARD, target)
|
||||
// const sacrifice = (target: Card) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
|
||||
|
||||
function focusPlayer(p: Player) {
|
||||
focusedPlayer.value = p;
|
||||
}
|
||||
|
||||
// watch<Team>(() => goStore.current_game!.current_turn, (new_team, old_team) => {
|
||||
// if (focusedPlayer.value.team != old_team) { return }
|
||||
// const first_player = goStore.current_game?.players.find(p => p.team == goStore.current_game?.current_turn)
|
||||
// if (!first_player) {
|
||||
// return
|
||||
// }
|
||||
// focusPlayer(first_player)
|
||||
// })
|
||||
|
||||
function useTokenClick(t: Effect) {
|
||||
console.log(t);
|
||||
useToken(game.value!._id, t._id);
|
||||
}
|
||||
|
||||
function showDiscard() {
|
||||
discard_unwrapped.value = true;
|
||||
}
|
||||
onUnmounted(() => {
|
||||
eventSource.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @contextmenu.prevent="false" id="game_board" v-if="game">
|
||||
<AnimatedBackground
|
||||
@ready="loaded = true"
|
||||
:isTurn="isTurn ?? false"
|
||||
></AnimatedBackground>
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<div id="stack_discard" :key="focusedPlayer._id">
|
||||
<div id="stack">
|
||||
<CardsGrouped
|
||||
:container="focusedPlayer.stack"
|
||||
:wrapped="true"
|
||||
:hidden="true"
|
||||
>
|
||||
</CardsGrouped>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="discard"
|
||||
:class="{ invisible: discard_unwrapped }"
|
||||
@click="() => (discard_unwrapped = true)"
|
||||
>
|
||||
<CardsGrouped
|
||||
:container="focusedPlayer.discard"
|
||||
:wrapped="true"
|
||||
@card-click="showDiscard"
|
||||
:no-effects="true"
|
||||
>
|
||||
</CardsGrouped>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<MarketCards
|
||||
:game="game"
|
||||
:market="game.market"
|
||||
:fire_gems="game.fireGems"
|
||||
:marketStack="game.marketStack"
|
||||
></MarketCards>
|
||||
<div>
|
||||
<TokenComponent
|
||||
:token="t"
|
||||
v-for="t in game.currentTurn.tokens"
|
||||
@useToken="useTokenClick"
|
||||
></TokenComponent>
|
||||
</div>
|
||||
<div id="player_list_container">
|
||||
<div id="player_list">
|
||||
<PlayerListElement
|
||||
v-for="p in playerList"
|
||||
:player="p"
|
||||
:game="game"
|
||||
@click="focusPlayer(p)"
|
||||
:focused="focusedPlayer == p"
|
||||
:self="selfPlayer == p"
|
||||
>
|
||||
</PlayerListElement>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="current_player">
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<PlayerSlot
|
||||
v-if="focusedPlayer"
|
||||
:player="focusedPlayer"
|
||||
:game="game!"
|
||||
:key="focusedPlayer._id"
|
||||
></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"
|
||||
: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, 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 turn_stats"
|
||||
"player_list player_list player_list"
|
||||
"current_player current_player current_player";
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#player_list {
|
||||
display: flex;
|
||||
flex-direction: inherit;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#current_player {
|
||||
grid-area: current_player;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
/* background: url('/img/bg-darkgrass.png'); */
|
||||
}
|
||||
|
||||
#stack_discard {
|
||||
grid-area: stack_discard;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#stack,
|
||||
#discard {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#discard {
|
||||
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;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
#discard_unwrapped_bg.unwrapped {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#market {
|
||||
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;
|
||||
|
||||
/* background-image: url("/img/bg-darkgrass.png"); */
|
||||
}
|
||||
|
||||
#player_list {
|
||||
/* border: 5px solid #000; */
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.slide-left-enter-active,
|
||||
.slide-left-leave-active {
|
||||
transition: all v-bind("settingsStore.animationSpeed * 2 + 'ms'") ease-out;
|
||||
}
|
||||
|
||||
.slide-left-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.slide-left-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
</style>
|
||||
+1
-1
@@ -61,7 +61,7 @@ onUnmounted(() => {
|
||||
<div v-for="g in games">
|
||||
<button
|
||||
v-if="g"
|
||||
@click="router.push({ path: `/game/${g._id}` })"
|
||||
@click="router.push({ path: `/game`, query:{id:g._id} })"
|
||||
:class="
|
||||
authStore.selfUser.userId == g.owner.userId ? 'own_game' : ''
|
||||
"
|
||||
|
||||
+3
-1
@@ -13,11 +13,13 @@ export const useAuthStore = defineStore("auth", {
|
||||
this.logged = true;
|
||||
return data;
|
||||
});
|
||||
|
||||
Object.assign(this.selfUser, data.value);
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
},
|
||||
oauth2_register_discord: () => {
|
||||
throw Error("not implemented");
|
||||
},
|
||||
},
|
||||
persist: true
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user