Compare commits
126
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6490fc180 | ||
|
|
19bed27f78 | ||
|
|
29cd28953d | ||
|
|
b5d89d4fe9 | ||
|
|
0fb4877266 | ||
|
|
ddcb6c6849 | ||
|
|
afebf1d9de | ||
|
|
4e8a619cb9 | ||
|
|
cb74d78928 | ||
|
|
a3ff9596d7 | ||
|
|
d18a2ab4f2 | ||
|
|
517f1bef5d | ||
|
|
77724d5539 | ||
|
|
21c41bb9fd | ||
|
|
bb625b1fee | ||
|
|
fd3cf0ba23 | ||
|
|
4ec29cf811 | ||
|
|
b92eb4b901 | ||
|
|
e36c916e0b | ||
|
|
ca8e38624e | ||
|
|
b00fffd314 | ||
|
|
11dd342b8e | ||
|
|
9e1af6f3df | ||
|
|
5e992122d8 | ||
|
|
aa58e6c4f8 | ||
|
|
1a05229d0b | ||
|
|
62d7346617 | ||
|
|
9708abbf81 | ||
|
|
8f300394ab | ||
|
|
2c986fbd97 | ||
|
|
3a88d1c107 | ||
|
|
7690292567 | ||
|
|
5623f7c91f | ||
|
|
08fb23e729 | ||
|
|
2876f7b91e | ||
|
|
775712371d | ||
|
|
6edadc3ef3 | ||
|
|
7713faa4d0 | ||
|
|
f9197d470e | ||
|
|
de38416bcd | ||
|
|
23e5f9f432 | ||
|
|
240ab7e1d4 | ||
|
|
c68446bf25 | ||
|
|
63519429e6 | ||
|
|
5282ca3c4a | ||
|
|
f5e4b10d1b | ||
|
|
efebc79d35 | ||
|
|
ad324a9a97 | ||
|
|
4145cacf1a | ||
|
|
05130ad97e | ||
|
|
e905731bc2 | ||
|
|
9638bcf1dd | ||
|
|
5ce9770e8c | ||
|
|
174eacc417 | ||
|
|
f68304cf22 | ||
|
|
8dbb64a540 | ||
|
|
c2674be596 | ||
|
|
94613b2d20 | ||
|
|
c0ce9b4b33 | ||
|
|
4768a09405 | ||
|
|
2e8046f81c | ||
|
|
c1bbf7dce4 | ||
|
|
302890a4ac | ||
|
|
0669619543 | ||
|
|
cfbaeca52b | ||
|
|
ba48570833 | ||
|
|
ff182b66ae | ||
|
|
ad6af3724d | ||
|
|
44b840e2bc | ||
|
|
7a338fe5fc | ||
|
|
c48363e9c3 | ||
|
|
90dfe57a49 | ||
|
|
0f771b12d5 | ||
|
|
baf2fc27a9 | ||
|
|
3330704670 | ||
|
|
8d4ae03519 | ||
|
|
7fd07b5a6a | ||
|
|
3e3284d8c4 | ||
|
|
df9e7a2e13 | ||
|
|
8dc263ab50 | ||
|
|
fde462166c | ||
|
|
2e201fa14e | ||
|
|
3c5b30be9e | ||
|
|
b1c9644941 | ||
|
|
2709bb799c | ||
|
|
6bef7778e8 | ||
|
|
6cdd846d3f | ||
|
|
9e985ad471 | ||
|
|
50ea1d456f | ||
|
|
3eba056253 | ||
|
|
9ce64ab591 | ||
|
|
bceae83341 | ||
|
|
db7959d7f1 | ||
|
|
12d6de5074 | ||
|
|
6112a7d6ba | ||
|
|
0facc4694f | ||
|
|
b3df241313 | ||
|
|
694e8429d1 | ||
|
|
22d1d804f9 | ||
|
|
b1a874aaeb | ||
|
|
25cf8a55e9 | ||
|
|
866a537924 | ||
|
|
d5dd147fba | ||
|
|
221f96860f | ||
|
|
6db8410ccc | ||
|
|
28bc888cb1 | ||
|
|
1fbe9a57ee | ||
|
|
6cc91d58ce | ||
|
|
0a8313b69d | ||
|
|
90a636b58a | ||
|
|
a22c6e1352 | ||
|
|
4a56f3ed62 | ||
|
|
8a3e2babc9 | ||
|
|
7c98da1df8 | ||
|
|
c0e02defa8 | ||
|
|
60ab679756 | ||
|
|
83fe97a427 | ||
|
|
4d66dea14c | ||
|
|
6ae1ce701d | ||
|
|
7d7f22a82c | ||
|
|
aa8332cb20 | ||
|
|
aa610a58a5 | ||
|
|
851c9a5190 | ||
|
|
f8205f374a | ||
|
|
3df2aee04c | ||
|
|
dd09ab3c8c |
@@ -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
|
||||
@@ -20,10 +20,18 @@ jobs:
|
||||
lfs: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: LFS Checkout
|
||||
- name: setup private key
|
||||
shell: bash
|
||||
run: |
|
||||
git remote set-url origin https://${{ env.ENDPOINT }}/${{ env.REPO_NAME }}.git
|
||||
echo "${{ secrets.RUNNER_SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 400 ~/.ssh/id_ed25519
|
||||
echo "{{ secrets.INSTANCE_SSH_PUBLIC_KEY }} > ~/.ssh/known_hosts"
|
||||
|
||||
- name: git lfs pull
|
||||
shell: bash
|
||||
run: |
|
||||
ssh-keyscan ${{ env.ENDPOINT }} >> ~/.ssh/known_hosts
|
||||
git remote set-url origin git@${{ env.ENDPOINT }}:${{ env.REPO_NAME }}.git
|
||||
git lfs pull
|
||||
|
||||
- name: Login to DockerHub
|
||||
|
||||
@@ -22,3 +22,8 @@ logs
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
assets/images/*
|
||||
public/img/skins
|
||||
public/img/icon
|
||||
public/img/tokens
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "client: chrome",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "server: nuxt",
|
||||
"outputCapture": "std",
|
||||
"program": "${workspaceFolder}/node_modules/nuxi/bin/nuxi.mjs",
|
||||
"args": [
|
||||
"dev"
|
||||
],
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "fullstack: nuxt",
|
||||
"configurations": [
|
||||
"server: nuxt",
|
||||
"client: chrome"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+3
-1
@@ -3,5 +3,7 @@
|
||||
"**/.nuxt/": true,
|
||||
"**/node_modules": true
|
||||
},
|
||||
"vue.server.hybridMode": true
|
||||
"vue.server.hybridMode": true,
|
||||
"editor.formatOnSave": true,
|
||||
"javascript.updateImportsOnFileMove.enabled": "never"
|
||||
}
|
||||
+26
-10
@@ -1,16 +1,32 @@
|
||||
FROM node:lts-alpine as build-stage
|
||||
FROM node:lts-alpine AS install-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN --mount=type=secret,id=NUXT_PUBLIC_WEBSOCKET_ENDPOINT \
|
||||
--mount=type=secret,id=NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT \
|
||||
NUXT_PUBLIC_WEBSOCKET_ENDPOINT=$(cat /run/secrets/NUXT_PUBLIC_WEBSOCKET_ENDPOINT ) \
|
||||
NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT=$(cat /run/secrets/NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT ) \
|
||||
npx nuxi generate
|
||||
|
||||
FROM git.legonzaur.fr/heronfief/cards-gen:main AS card-svg-stage
|
||||
RUN python3 -u ./src/generate_cards_mvp.py
|
||||
|
||||
FROM git.legonzaur.fr/heronfief/tokens-gen:main AS token-svg-stage
|
||||
RUN python3 -u ./src/generate_tokens_mvp.py
|
||||
|
||||
FROM install-stage AS build-stage
|
||||
COPY --from=card-svg-stage /app/output/heron ./assets/images/cards/heron-svg
|
||||
COPY --from=card-svg-stage /app/img/skins ./public/img
|
||||
|
||||
COPY --from=token-svg-stage /app/output/heron ./assets/images/tokens
|
||||
COPY --from=token-svg-stage /app/img/icon ./public/img
|
||||
|
||||
|
||||
ARG NUXT_PUBLIC_ENDPOINT
|
||||
ARG NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT
|
||||
ARG NUXT_PUBLIC_WELCOME_MESSAGE
|
||||
|
||||
RUN npx nuxi generate
|
||||
|
||||
# étape de production
|
||||
FROM nginx:stable-alpine as production-stage
|
||||
FROM nginx:stable-alpine AS production-stage
|
||||
COPY --from=build-stage /app/.output/public /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
COPY --from=card-svg-stage /app/img/skins /usr/share/nginx/html/img/skins
|
||||
COPY --from=token-svg-stage /app/img/icon /usr/share/nginx/html/img/icon
|
||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
|
||||
@@ -1,101 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { compileCards, compileTokens } from "./services/loadCards";
|
||||
import { getCurrentInstance } from "vue";
|
||||
// import goStore, { oauth2_register_discord, setup_websocket, heron_cookie_login } from "@/netcode";
|
||||
|
||||
import goStore, { oauth2_register_discord, setup_websocket, heron_cookie_login } from "@/netcode";
|
||||
const nuxtApp = useNuxtApp();
|
||||
|
||||
await setup_websocket()
|
||||
const authStore = useAuthStore();
|
||||
|
||||
useHead
|
||||
({
|
||||
title
|
||||
: 'Héron Realms',
|
||||
})
|
||||
|
||||
const heron_session = useCookie('heron_session')
|
||||
const route = useRoute()
|
||||
|
||||
onMounted(async () => {
|
||||
const code = route.query.code
|
||||
if (!code) { return }
|
||||
if (Array.isArray(code)) { return }
|
||||
await setup_websocket()
|
||||
oauth2_register_discord(code)
|
||||
history.pushState(
|
||||
{},
|
||||
'',
|
||||
'/'
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
if (!heron_session.value) { return }
|
||||
heron_cookie_login(heron_session.value)
|
||||
})
|
||||
|
||||
function keyDownHandler(e: KeyboardEvent) {
|
||||
if (e.repeat) { return }
|
||||
console.log(e.code)
|
||||
if (e.code != "AltLeft") { return }
|
||||
goStore.zoomKeyPressed = true
|
||||
e.preventDefault()
|
||||
return false
|
||||
console.log("compiling cards...");
|
||||
const app = getCurrentInstance()?.appContext.app;
|
||||
if (!app) {
|
||||
throw new Error("Couldn't bake cards: instance is null");
|
||||
}
|
||||
await compileCards();
|
||||
await compileTokens();
|
||||
console.log("compiling done");
|
||||
|
||||
function keyUpHandler(e: KeyboardEvent) {
|
||||
if (e.repeat) { return }
|
||||
if (e.code != "AltLeft") { return }
|
||||
goStore.zoomKeyPressed = false
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
useHead({
|
||||
title: "Héron Realms",
|
||||
});
|
||||
|
||||
function contextMenuHandler(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
function mouseDownHandler(e: MouseEvent) {
|
||||
if (e.button !== 2) { return }
|
||||
goStore.zoomKeyPressed = true
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
function mouseUpHandler(e: MouseEvent) {
|
||||
if (e.button !== 2) { return }
|
||||
goStore.zoomKeyPressed = false
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
function follow_mouse(e: MouseEvent) {
|
||||
goStore.clientMouseX = e.clientX
|
||||
goStore.clientMouseY = e.clientY
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("contextmenu", contextMenuHandler)
|
||||
window.addEventListener("mousedown", mouseDownHandler)
|
||||
window.addEventListener("mouseup", mouseUpHandler)
|
||||
document.addEventListener("mousemove", follow_mouse)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("contextmenu", contextMenuHandler)
|
||||
window.removeEventListener("mousedown", mouseDownHandler)
|
||||
window.removeEventListener("mouseup", mouseUpHandler)
|
||||
document.removeEventListener("mousemove", follow_mouse)
|
||||
})
|
||||
|
||||
onDeactivated
|
||||
await authStore.whoami();
|
||||
</script>
|
||||
<template>
|
||||
<div class="root_dom_div">
|
||||
<CardOverlay v-if="goStore.zoomKeyPressed" :card_id="goStore.hoveredCard" />
|
||||
<NuxtPage />
|
||||
<NuxtLoadingIndicator> Loading... </NuxtLoadingIndicator>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--animation-curve: cubic-bezier(0.68, -0.275, 0.32, 1.275);
|
||||
}
|
||||
html {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -105,6 +44,48 @@ body {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4caf50;
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: 16px;
|
||||
width: max-content;
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
transition-duration: 0.4s;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
padding: 10px 24px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: gray;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button.yellow {
|
||||
background-color: rgba(255, 165, 0, 0.7);
|
||||
}
|
||||
|
||||
button.red {
|
||||
background-color: rgba(255, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
button:hover:enabled {
|
||||
background-color: #45a049;
|
||||
}
|
||||
button.yellow:hover {
|
||||
background-color: rgba(255, 165, 0, 0.8);
|
||||
}
|
||||
|
||||
button.red:hover {
|
||||
background-color: rgba(255, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
#__nuxt {
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<html>
|
||||
I LOVE LOADING SCREENS
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"prepare":{
|
||||
"title":"Prepare",
|
||||
"description":"Repeat a champion's effects"
|
||||
},
|
||||
"stun":{
|
||||
"title":"Stun",
|
||||
"description":"Stun (kill) an ennemy's champion"
|
||||
},
|
||||
"sacrifice":{
|
||||
"title": "Sacrifice",
|
||||
"description": "Destroy a card, forever"
|
||||
},
|
||||
"discard":{
|
||||
"title":"Discard",
|
||||
"description":"Oops ! You must put a card in your discard..."
|
||||
},
|
||||
"make_discard":{
|
||||
"title": "Make Discard",
|
||||
"description": "Give a fuck to your opponent"
|
||||
},
|
||||
"restack_discarded_champion":{
|
||||
"title":"Restack Discarded Champion",
|
||||
"description":"Take a champion from your discard and put it on top of your stack"
|
||||
},
|
||||
"restack_discarded_card":{
|
||||
"title":"Restack Discarded Card",
|
||||
"description":"Take a card from your discard and put it on top of your stack"
|
||||
},
|
||||
"stack_next_action_bought":{
|
||||
"title":"Stack Next Action Bought",
|
||||
"description":"When you buy an action, you may put it on top of your stack"
|
||||
},
|
||||
"stack_next_card_bought":{
|
||||
"title":"Stack Next Card Bought",
|
||||
"description":"When you buy a card, you may put it on top of your stack"
|
||||
},
|
||||
"play_next_card_bought":{
|
||||
"title":"Play Next Card Bought",
|
||||
"description":"When you buy a card, you may put it in play"
|
||||
},
|
||||
"draw_and_discard":{
|
||||
"title":"Draw And Discard",
|
||||
"description":"You may draw a card, then discard a card"
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,12 @@ void main(){
|
||||
|
||||
q=q*.5+.5;
|
||||
vec2 r=vec2(0.);
|
||||
r.x=fbm(st+1.*q+vec2(1.7,9.2)+.00515*u_time);
|
||||
r.y=fbm(st+1.*q+vec2(8.3,2.8)+.0126*u_time);
|
||||
r.x=fbm(st+1.*q+vec2(1.7,9.2)+.00215*u_time);
|
||||
r.y=fbm(st+1.*q+vec2(8.3,2.8)+.00526*u_time);
|
||||
r=r*.5+.5;
|
||||
|
||||
float f=fbm(st+r);
|
||||
|
||||
f = floor((f+.4)*4.)/4.;
|
||||
vec3 color=mix(u_color_start,u_color_end,f*.3);
|
||||
|
||||
gl_FragColor=vec4(color,1.);
|
||||
|
||||
+217
-188
@@ -1,214 +1,243 @@
|
||||
import noise from "~/assets/shaders/noise.frag?raw"
|
||||
import noise from "~/assets/shaders/noise.frag?raw";
|
||||
|
||||
import resolveLygia from "~/assets/workers/resolve-lygia.esm"
|
||||
import resolveLygia from "~/assets/workers/resolve-lygia.esm";
|
||||
|
||||
let interval: number
|
||||
let timeout: number
|
||||
let interval: number;
|
||||
let timeout: number;
|
||||
|
||||
const shaderOptions = { quality: 1, framerate: 1000 / 60 }
|
||||
const screen = { width: 0, height: 0, devicePixelRatio: 0 }
|
||||
const shaderOptions = { quality: 1, framerate: 1000 / 60 };
|
||||
const screen = { width: 0, height: 0, devicePixelRatio: 0 };
|
||||
|
||||
const _colorStart = [0, 0, 0]
|
||||
const _colorEnd = [0, 0, 0]
|
||||
const _colorStart = [0, 0, 0];
|
||||
const _colorEnd = [0, 0, 0];
|
||||
|
||||
let fragmentShader = resolveLygia(noise);
|
||||
|
||||
let canvas: OffscreenCanvas
|
||||
let canvas: OffscreenCanvas;
|
||||
|
||||
let render: (time: number) => void
|
||||
let render: (time: number) => void;
|
||||
|
||||
let must_resize = false
|
||||
let must_resize = false;
|
||||
|
||||
async function main(canvas: OffscreenCanvas) {
|
||||
const gl = canvas.getContext("webgl");
|
||||
if (!gl) {
|
||||
return;
|
||||
}
|
||||
// setup GLSL program
|
||||
const program = gl.createProgram()
|
||||
if (!program) { return }
|
||||
const gl = canvas.getContext("webgl");
|
||||
if (!gl) {
|
||||
return;
|
||||
}
|
||||
// setup GLSL program
|
||||
const program = gl.createProgram();
|
||||
if (!program) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fragment = gl.createShader(gl.FRAGMENT_SHADER);
|
||||
if (!fragment) { return }
|
||||
gl.shaderSource(fragment, fragmentShader)
|
||||
gl.compileShader(fragment);
|
||||
if (!gl.getShaderParameter(fragment, gl.COMPILE_STATUS)) {
|
||||
const lastError = gl.getShaderInfoLog(fragment);
|
||||
console.error(lastError)
|
||||
gl.deleteShader(fragment);
|
||||
return
|
||||
const fragment = gl.createShader(gl.FRAGMENT_SHADER);
|
||||
if (!fragment) {
|
||||
return;
|
||||
}
|
||||
gl.shaderSource(fragment, fragmentShader);
|
||||
gl.compileShader(fragment);
|
||||
if (!gl.getShaderParameter(fragment, gl.COMPILE_STATUS)) {
|
||||
const lastError = gl.getShaderInfoLog(fragment);
|
||||
console.error(lastError);
|
||||
gl.deleteShader(fragment);
|
||||
return;
|
||||
}
|
||||
|
||||
const vertex = gl.createShader(gl.VERTEX_SHADER);
|
||||
if (!vertex) {
|
||||
return;
|
||||
}
|
||||
gl.shaderSource(
|
||||
vertex,
|
||||
`attribute vec4 a_position;void main() {gl_Position = a_position;}`
|
||||
);
|
||||
gl.compileShader(vertex);
|
||||
if (!gl.getShaderParameter(vertex, gl.COMPILE_STATUS)) {
|
||||
const lastError = gl.getShaderInfoLog(vertex);
|
||||
console.error(lastError);
|
||||
gl.deleteShader(vertex);
|
||||
return;
|
||||
}
|
||||
|
||||
gl.attachShader(program, fragment);
|
||||
gl.attachShader(program, vertex);
|
||||
gl.linkProgram(program);
|
||||
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||
if (!linked) {
|
||||
return;
|
||||
}
|
||||
// const program = webglUtils.createProgramFromSources(gl, [vs, fragmentShader2]);
|
||||
|
||||
// look up where the vertex data needs to go.
|
||||
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
|
||||
|
||||
// look up uniform locations
|
||||
const offset = gl.getUniformLocation(program, "u_offset");
|
||||
const resolutionLocation = gl.getUniformLocation(program, "u_resolution");
|
||||
const colorStart = gl.getUniformLocation(program, "u_color_start");
|
||||
const quality = gl.getUniformLocation(program, "u_quality");
|
||||
const colorEnd = gl.getUniformLocation(program, "u_color_end");
|
||||
const timeLocation = gl.getUniformLocation(program, "u_time");
|
||||
|
||||
// Create a buffer to put three 2d clip space points in
|
||||
const positionBuffer = gl.createBuffer();
|
||||
|
||||
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
|
||||
// fill it with a 2 triangles that cover clipspace
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array([
|
||||
-1,
|
||||
-1, // first triangle
|
||||
1,
|
||||
-1,
|
||||
-1,
|
||||
1,
|
||||
-1,
|
||||
1, // second triangle
|
||||
1,
|
||||
-1,
|
||||
1,
|
||||
1,
|
||||
]),
|
||||
gl.STATIC_DRAW
|
||||
);
|
||||
|
||||
const gl2 = gl;
|
||||
function render(time: number) {
|
||||
if (must_resize) {
|
||||
canvas.width = (screen.width * shaderOptions.quality) / 10;
|
||||
canvas.height = (screen.height * shaderOptions.quality) / 10;
|
||||
must_resize = false;
|
||||
}
|
||||
|
||||
const vertex = gl.createShader(gl.VERTEX_SHADER);
|
||||
if (!vertex) { return }
|
||||
gl.shaderSource(vertex, `attribute vec4 a_position;void main() {gl_Position = a_position;}`)
|
||||
gl.compileShader(vertex);
|
||||
if (!gl.getShaderParameter(vertex, gl.COMPILE_STATUS)) {
|
||||
const lastError = gl.getShaderInfoLog(vertex);
|
||||
console.error(lastError)
|
||||
gl.deleteShader(vertex);
|
||||
return
|
||||
time *= 0.001; // convert to seconds
|
||||
// Tell WebGL how to convert from clip space to pixels
|
||||
gl2.viewport(0, 0, gl2.canvas.width, gl2.canvas.height);
|
||||
|
||||
// Tell it to use our program (pair of shaders)
|
||||
gl2.useProgram(program);
|
||||
|
||||
// Turn on the attribute
|
||||
gl2.enableVertexAttribArray(positionAttributeLocation);
|
||||
|
||||
// Bind the position buffer.
|
||||
gl2.bindBuffer(gl2.ARRAY_BUFFER, positionBuffer);
|
||||
|
||||
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
|
||||
gl2.vertexAttribPointer(
|
||||
positionAttributeLocation,
|
||||
2, // 2 components per iteration
|
||||
gl2.FLOAT, // the data is 32bit floats
|
||||
false, // don't normalize the data
|
||||
0, // 0 = move forward size * sizeof(type) each iteration to get the next position
|
||||
0 // start at the beginning of the buffer
|
||||
);
|
||||
|
||||
const secondRatio = screen.width / screen.height;
|
||||
|
||||
if (secondRatio < 1) {
|
||||
const half = canvas.height / 2;
|
||||
gl2.uniform2f(
|
||||
resolutionLocation,
|
||||
canvas.width,
|
||||
canvas.height * secondRatio
|
||||
);
|
||||
gl2.uniform2f(offset, 0, -half + half / (1 / secondRatio));
|
||||
} else {
|
||||
const half = canvas.width / 2;
|
||||
gl2.uniform2f(
|
||||
resolutionLocation,
|
||||
canvas.width / secondRatio,
|
||||
canvas.height
|
||||
);
|
||||
gl2.uniform2f(offset, -half + half / secondRatio, 0);
|
||||
}
|
||||
gl2.uniform1i(quality, shaderOptions.quality);
|
||||
|
||||
gl.attachShader(program, fragment)
|
||||
gl.attachShader(program, vertex)
|
||||
gl.linkProgram(program)
|
||||
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||
if (!linked) { return }
|
||||
// const program = webglUtils.createProgramFromSources(gl, [vs, fragmentShader2]);
|
||||
|
||||
// look up where the vertex data needs to go.
|
||||
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
|
||||
|
||||
// look up uniform locations
|
||||
const offset = gl.getUniformLocation(program, "u_offset");
|
||||
const resolutionLocation = gl.getUniformLocation(program, "u_resolution");
|
||||
const colorStart = gl.getUniformLocation(program, "u_color_start");
|
||||
const quality = gl.getUniformLocation(program, "u_quality");
|
||||
const colorEnd = gl.getUniformLocation(program, "u_color_end");
|
||||
const timeLocation = gl.getUniformLocation(program, "u_time");
|
||||
|
||||
// Create a buffer to put three 2d clip space points in
|
||||
const positionBuffer = gl.createBuffer();
|
||||
|
||||
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
|
||||
// fill it with a 2 triangles that cover clipspace
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
||||
-1, -1, // first triangle
|
||||
1, -1,
|
||||
-1, 1,
|
||||
-1, 1, // second triangle
|
||||
1, -1,
|
||||
1, 1,
|
||||
]), gl.STATIC_DRAW);
|
||||
|
||||
|
||||
const gl2 = gl
|
||||
function render(time: number) {
|
||||
if (must_resize) {
|
||||
canvas.width = screen.width * shaderOptions.quality / 10
|
||||
canvas.height = screen.height * shaderOptions.quality / 10
|
||||
must_resize = false;
|
||||
}
|
||||
|
||||
time *= 0.001; // convert to seconds
|
||||
// Tell WebGL how to convert from clip space to pixels
|
||||
gl2.viewport(0, 0, gl2.canvas.width, gl2.canvas.height);
|
||||
|
||||
// Tell it to use our program (pair of shaders)
|
||||
gl2.useProgram(program);
|
||||
|
||||
// Turn on the attribute
|
||||
gl2.enableVertexAttribArray(positionAttributeLocation);
|
||||
|
||||
// Bind the position buffer.
|
||||
gl2.bindBuffer(gl2.ARRAY_BUFFER, positionBuffer);
|
||||
|
||||
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
|
||||
gl2.vertexAttribPointer(
|
||||
positionAttributeLocation,
|
||||
2, // 2 components per iteration
|
||||
gl2.FLOAT, // the data is 32bit floats
|
||||
false, // don't normalize the data
|
||||
0, // 0 = move forward size * sizeof(type) each iteration to get the next position
|
||||
0, // start at the beginning of the buffer
|
||||
);
|
||||
|
||||
const secondRatio = screen.width / screen.height
|
||||
|
||||
if (secondRatio < 1) {
|
||||
const half = canvas.height / 2
|
||||
gl2.uniform2f(resolutionLocation, canvas.width, canvas.height * secondRatio);
|
||||
gl2.uniform2f(offset, 0, -half + ((half) / (1 / secondRatio)));
|
||||
} else {
|
||||
const half = canvas.width / 2
|
||||
gl2.uniform2f(resolutionLocation, canvas.width / secondRatio, canvas.height);
|
||||
gl2.uniform2f(offset, -half + ((half) / secondRatio), 0);
|
||||
}
|
||||
gl2.uniform1i(quality, shaderOptions.quality)
|
||||
|
||||
gl2.uniform3f(colorStart, _colorStart[0], _colorStart[1], _colorStart[2]);
|
||||
gl2.uniform3f(colorEnd, _colorEnd[0], _colorEnd[1], _colorEnd[2]);
|
||||
gl2.uniform1f(timeLocation, time);
|
||||
gl2.drawArrays(
|
||||
gl2.TRIANGLES,
|
||||
0, // offset
|
||||
6, // num vertices to process
|
||||
);
|
||||
if (timeout) {
|
||||
self.clearTimeout(timeout)
|
||||
timeout = self.setTimeout(() => {
|
||||
requestAnimationFrame(render)
|
||||
}, 1000 / shaderOptions.framerate)
|
||||
}
|
||||
gl2.uniform3f(colorStart, _colorStart[0], _colorStart[1], _colorStart[2]);
|
||||
gl2.uniform3f(colorEnd, _colorEnd[0], _colorEnd[1], _colorEnd[2]);
|
||||
gl2.uniform1f(timeLocation, time);
|
||||
gl2.drawArrays(
|
||||
gl2.TRIANGLES,
|
||||
0, // offset
|
||||
6 // num vertices to process
|
||||
);
|
||||
if (timeout) {
|
||||
self.clearTimeout(timeout);
|
||||
timeout = self.setTimeout(() => {
|
||||
requestAnimationFrame(render);
|
||||
}, 1000 / shaderOptions.framerate);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(render)
|
||||
requestAnimationFrame(render);
|
||||
|
||||
// self.clearInterval(interval)
|
||||
// interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate)
|
||||
self.clearTimeout(timeout)
|
||||
if (shaderOptions.framerate == 0) {
|
||||
return render
|
||||
}
|
||||
timeout = self.setTimeout(() => {
|
||||
requestAnimationFrame(render)
|
||||
}, 1000 / shaderOptions.framerate)
|
||||
return render
|
||||
// self.clearInterval(interval)
|
||||
// interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate)
|
||||
self.clearTimeout(timeout);
|
||||
if (shaderOptions.framerate == 0) {
|
||||
return render;
|
||||
}
|
||||
timeout = self.setTimeout(() => {
|
||||
requestAnimationFrame(render);
|
||||
}, 1000 / shaderOptions.framerate);
|
||||
return render;
|
||||
}
|
||||
|
||||
self.addEventListener("message", (e) => {
|
||||
const data = e.data
|
||||
if (data.command == "initShader") {
|
||||
canvas = data.canvas
|
||||
shaderOptions.quality = data.shaderOptions.quality
|
||||
shaderOptions.framerate = data.shaderOptions.framerate
|
||||
if (shaderOptions.framerate == 65) {
|
||||
shaderOptions.framerate == Infinity
|
||||
}
|
||||
screen.devicePixelRatio = data.screen.devicePixelRatio
|
||||
screen.width = data.screen.width
|
||||
screen.height = data.screen.height
|
||||
loadShader()
|
||||
} else if (data.command == "killShader") {
|
||||
console.log("killShader")
|
||||
self.clearInterval(interval)
|
||||
self.clearTimeout(timeout)
|
||||
const data = e.data;
|
||||
if (data.command == "initShader") {
|
||||
canvas = data.canvas;
|
||||
shaderOptions.quality = data.shaderOptions.quality;
|
||||
shaderOptions.framerate = data.shaderOptions.framerate;
|
||||
if (shaderOptions.framerate == 65) {
|
||||
shaderOptions.framerate == Infinity;
|
||||
}
|
||||
else if (data.command == "resumeShader") {
|
||||
self.clearInterval(interval)
|
||||
self.clearTimeout(timeout)
|
||||
shaderOptions.framerate = data.shaderOptions.framerate
|
||||
if (shaderOptions.framerate == 65) {
|
||||
shaderOptions.framerate == Infinity
|
||||
}
|
||||
timeout = self.setTimeout(() => {
|
||||
requestAnimationFrame(render)
|
||||
}, 1000 / shaderOptions.framerate)
|
||||
// interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate)
|
||||
} else if (data.command == "recolorShader") {
|
||||
_colorStart[0] = data.colorStart[0]
|
||||
_colorStart[1] = data.colorStart[1]
|
||||
_colorStart[2] = data.colorStart[2]
|
||||
_colorEnd[0] = data.colorEnd[0]
|
||||
_colorEnd[1] = data.colorEnd[1]
|
||||
_colorEnd[2] = data.colorEnd[2]
|
||||
} else if (data.command == "resizeShader") {
|
||||
shaderOptions.quality = data.shaderOptions.quality
|
||||
screen.devicePixelRatio = data.screen.devicePixelRatio
|
||||
screen.width = data.screen.width
|
||||
screen.height = data.screen.height
|
||||
must_resize = true
|
||||
screen.devicePixelRatio = data.screen.devicePixelRatio;
|
||||
screen.width = data.screen.width;
|
||||
screen.height = data.screen.height;
|
||||
loadShader();
|
||||
} else if (data.command == "killShader") {
|
||||
self.clearInterval(interval);
|
||||
self.clearTimeout(timeout);
|
||||
} else if (data.command == "resumeShader") {
|
||||
self.clearInterval(interval);
|
||||
self.clearTimeout(timeout);
|
||||
shaderOptions.framerate = data.shaderOptions.framerate;
|
||||
if (shaderOptions.framerate == 65) {
|
||||
shaderOptions.framerate == Infinity;
|
||||
}
|
||||
})
|
||||
timeout = self.setTimeout(() => {
|
||||
requestAnimationFrame(render);
|
||||
}, 1000 / shaderOptions.framerate);
|
||||
// interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate)
|
||||
} else if (data.command == "recolorShader") {
|
||||
_colorStart[0] = data.colorStart[0];
|
||||
_colorStart[1] = data.colorStart[1];
|
||||
_colorStart[2] = data.colorStart[2];
|
||||
_colorEnd[0] = data.colorEnd[0];
|
||||
_colorEnd[1] = data.colorEnd[1];
|
||||
_colorEnd[2] = data.colorEnd[2];
|
||||
} else if (data.command == "resizeShader") {
|
||||
shaderOptions.quality = data.shaderOptions.quality;
|
||||
screen.devicePixelRatio = data.screen.devicePixelRatio;
|
||||
screen.width = data.screen.width;
|
||||
screen.height = data.screen.height;
|
||||
must_resize = true;
|
||||
}
|
||||
});
|
||||
|
||||
async function loadShader() {
|
||||
if (!canvas) { return }
|
||||
// let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + shaderOptions.quality);
|
||||
const temp = await main(canvas);
|
||||
if (temp) {
|
||||
render = temp
|
||||
}
|
||||
}
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
// let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + shaderOptions.quality);
|
||||
const temp = await main(canvas);
|
||||
if (temp) {
|
||||
render = temp;
|
||||
}
|
||||
postMessage("ready");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
function getFile(url) {
|
||||
if(url == "https://lygia.xyz/generative/fbm.glsl"){
|
||||
url = "/fbm.glsl"
|
||||
}
|
||||
let httpRequest = new XMLHttpRequest();
|
||||
httpRequest.open("GET", url, false);
|
||||
httpRequest.send();
|
||||
@@ -8,6 +11,8 @@ function getFile(url) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const lygiaURL = "https://lygia.xyz"
|
||||
|
||||
function resolveLygia(lines) {
|
||||
if ( !Array.isArray(lines) ) {
|
||||
lines = lines.split(/\r?\n/);
|
||||
@@ -18,14 +23,13 @@ function resolveLygia(lines) {
|
||||
const line_trim = line.trim();
|
||||
if (line_trim.startsWith('#include \"lygia') ) {
|
||||
let include_url = line_trim.substring(15);
|
||||
include_url = "https://lygia.xyz" + include_url.replace(/\"|\;|\s/g,'');
|
||||
include_url = lygiaURL + include_url.replace(/\"|\;|\s/g,'');
|
||||
src += getFile(include_url) + '\n';
|
||||
}
|
||||
else {
|
||||
src += line + '\n';
|
||||
}
|
||||
});
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
@@ -39,7 +43,7 @@ async function resolveLygiaAsync(lines) {
|
||||
const line_trim = line.trim();
|
||||
if (line_trim.startsWith('#include "lygia')) {
|
||||
let include_url = line_trim.substring(15);
|
||||
include_url = "https://lygia.xyz" + include_url.replace(/\"|\;|\s/g, "");
|
||||
include_url = lygiaURL + include_url.replace(/\"|\;|\s/g, "");
|
||||
return fetch(include_url).then((res) => res.text());
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,133 +1,176 @@
|
||||
<script setup lang="ts">
|
||||
const settingsStore = useSettingsStore()
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
import gsap from 'gsap';
|
||||
import ShaderWorker from '~/assets/workers/ShaderWorker?worker'
|
||||
import goStore from '~/netcode';
|
||||
const worker = new ShaderWorker()
|
||||
import gsap from "gsap";
|
||||
import ShaderWorker from "~/assets/workers/ShaderWorker?worker";
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
const canvas_resize_temp = ref<HTMLCanvasElement | null>(null)
|
||||
const emit = defineEmits<{
|
||||
ready: [];
|
||||
}>();
|
||||
|
||||
let worker: Worker;
|
||||
|
||||
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]
|
||||
isTurn?: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
colorStart: () => [0, .3, 0],
|
||||
colorEnd: () => [0, 1, 0]
|
||||
})
|
||||
isTurn: () => false,
|
||||
});
|
||||
|
||||
const _colorStart = reactive(props.colorStart)
|
||||
const _colorEnd = reactive(props.colorEnd)
|
||||
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() {
|
||||
if (!canvas.value) { return }
|
||||
const offscreen = canvas.value?.transferControlToOffscreen()
|
||||
worker.postMessage({
|
||||
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])
|
||||
screen: {
|
||||
height: window.innerHeight * devicePixelRatio,
|
||||
width: window.innerWidth * devicePixelRatio,
|
||||
devicePixelRatio,
|
||||
},
|
||||
shaderOptions: {
|
||||
quality: settingsStore.shaderQuality,
|
||||
framerate: settingsStore.shaderFramerate,
|
||||
},
|
||||
},
|
||||
[offscreen]
|
||||
);
|
||||
if (settingsStore.shaderFramerate == 0) {
|
||||
killShader()
|
||||
killShader();
|
||||
}
|
||||
resizeShader()
|
||||
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(() => [goStore.current_game?.current_turn], () => {
|
||||
if (goStore.current_game?.current_turn != goStore.self?.team) {
|
||||
gsap.to(_colorStart, { duration: 0.5, 0: 0, 1: .2, 2: .3 })
|
||||
gsap.to(_colorEnd, { duration: 0.5, 0: 0, 1: 1, 2: 1 })
|
||||
watch(
|
||||
() => [props.isTurn],
|
||||
() => {
|
||||
if (props.isTurn) {
|
||||
gsap.to(_colorStart, { duration: 0.5, 0: 0, 1: 0.3, 2: 0 });
|
||||
gsap.to(_colorEnd, { duration: 0.5, 0: 0, 1: 1, 2: 0 });
|
||||
} else {
|
||||
gsap.to(_colorStart, { duration: 0.5, 0: 0, 1: .3, 2: 0 })
|
||||
gsap.to(_colorEnd, { duration: 0.5, 0: 0, 1: 1, 2: 0 })
|
||||
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()
|
||||
})
|
||||
recolorShader();
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
nextTick(() => {
|
||||
recolorShader()
|
||||
initShader()
|
||||
window.addEventListener("resize", resizeShader, true)
|
||||
})
|
||||
})
|
||||
onMounted(() => {
|
||||
worker = new ShaderWorker();
|
||||
recolorShader();
|
||||
initShader().then(() => {
|
||||
emit("ready");
|
||||
});
|
||||
window.addEventListener("resize", resizeShader, true);
|
||||
});
|
||||
|
||||
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"></canvas>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#background {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
image-rendering: smooth;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: -999999;
|
||||
}
|
||||
|
||||
#background.pixelated {
|
||||
image-rendering: pixelated;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore from '~/netcode';
|
||||
|
||||
export interface Props {
|
||||
card_id: number | undefined
|
||||
}
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const card: Ref<Element | null> = ref(null)
|
||||
|
||||
const x = computed(() => {
|
||||
const bounds = card.value?.getBoundingClientRect()
|
||||
let offset = 1
|
||||
if (!bounds) { return 0 }
|
||||
if ((bounds.width + goStore.clientMouseX) > (window.visualViewport?.width ?? Infinity)) {
|
||||
offset = - (bounds.width + 1)
|
||||
}
|
||||
return goStore.clientMouseX + offset
|
||||
})
|
||||
const y = computed(() => {
|
||||
const bounds = card.value?.getBoundingClientRect()
|
||||
let offset = 1
|
||||
if (!bounds) { return 0 }
|
||||
if ((bounds.height + goStore.clientMouseY) > (window.visualViewport?.height ?? Infinity)) {
|
||||
offset = - (bounds.height + 1)
|
||||
}
|
||||
return goStore.clientMouseY + offset
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="`card_overlay`" :style="`top: ${y}px; left: ${x}px`" ref="card">
|
||||
<img :class="`card_image`" v-if="props.card_id"
|
||||
:src="`/cards/${settingsStore.cardStyle}/` + props.card_id.toString().padStart(3, '0') + '.png'"
|
||||
draggable="false">
|
||||
|
||||
<!-- <img :class="`card_image`" v-if="!props.card_id" :src="'/cards/background.png'" draggable="false"> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card_overlay {
|
||||
/* display: none; */
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,167 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore from '~/netcode';
|
||||
import { useSettingsStore } from '~/stores/settingsStore';
|
||||
|
||||
export interface Props {
|
||||
card_id: number | undefined
|
||||
locked?: boolean
|
||||
wrapped?: boolean
|
||||
tilted?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
wrapped: () => false,
|
||||
tilted: () => true,
|
||||
locked: () => true
|
||||
})
|
||||
|
||||
|
||||
const emit = defineEmits(["click"])
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const childCount = ref<number>(0)
|
||||
|
||||
const card = ref<HTMLElement>()
|
||||
|
||||
function cardClick() {
|
||||
if (!props.card_id) return
|
||||
emit("click")
|
||||
}
|
||||
|
||||
const rotation = ref(0)
|
||||
|
||||
function mouseenter(e: Event) {
|
||||
goStore.hoveredCard = props.card_id
|
||||
}
|
||||
function mouseleave(e: Event) {
|
||||
goStore.hoveredCard = undefined
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.card_id,
|
||||
() => {
|
||||
props.wrapped ? rotation.value = (Math.random() * 4) - 2 : rotation.value = Math.random() > .5 ? 2 : -2
|
||||
},
|
||||
{ immediate: true })
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
card.value?.style.setProperty("--random-seed", -Math.random() * 10 + 's')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="card" class="card" :class="{ active: childCount > 0, wrapped, unlocked: !props.locked }"
|
||||
@contextmenu.prevent="false" @click.left="cardClick" :style="tilted ? `rotate:${rotation}deg;` : ''"
|
||||
@mouseenter="mouseenter" @mouseleave="mouseleave">
|
||||
<div id="contextMenuButtons" :ref="(el) => { childCount = ((el as Element)?.childElementCount ?? 0) }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<img :class="`card_image`" v-if="props.card_id"
|
||||
:src="`/cards/${settingsStore.cardStyle}/` + props.card_id.toString().padStart(3, '0') + '.png'"
|
||||
draggable="false">
|
||||
|
||||
<img :class="`card_image`" v-if="!props.card_id" :src="'/cards/background.png'" draggable="false">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
--max-height: 250px;
|
||||
--margin-bottom: calc(var(--max-height) * 0.05);
|
||||
max-width: 256px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin-bottom: 0px;
|
||||
margin-top: var(--margin-bottom);
|
||||
transition: filter .125s cubic-bezier(0.075, 0.82, 0.165, 1), rotate 0.075s cubic-bezier(1, 1.81, .59, 1.42);
|
||||
isolation: isolate;
|
||||
pointer-events: all;
|
||||
max-height: var(--max-height);
|
||||
}
|
||||
|
||||
.card.active {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.card_image {
|
||||
max-height: var(--max-height);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.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 {
|
||||
max-width: 10px;
|
||||
}
|
||||
|
||||
#contextMenuButtons {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
word-break: break-word;
|
||||
bottom: 0;
|
||||
width: 99%;
|
||||
transform: translateY(-0px);
|
||||
transition: 0.1s transform cubic-bezier(1, 1.81, .59, 1.42);
|
||||
}
|
||||
|
||||
.card.active:hover #contextMenuButtons {
|
||||
transform: translateY(100%);
|
||||
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
#contextMenuButtons>* {
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
min-height: 50px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,56 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { Card } from '~/netcode/Card';
|
||||
|
||||
export interface Props {
|
||||
stack: (Card | null)[]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
stack: () => [],
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="cards_container">
|
||||
<template v-if="props.stack.length > 0" v-for="c in props.stack">
|
||||
<CardPreview :card_id="c?.card_id" :wrapped="true">
|
||||
<slot></slot>
|
||||
</CardPreview>
|
||||
</template>
|
||||
<template v-if="props.stack.length == 0">
|
||||
<div class="empty">
|
||||
<img :class="`card_image`" :src="'/cards/background.png'" draggable="false">
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cards_container::before {
|
||||
width: calc(185px - 10px);
|
||||
display: block;
|
||||
content: "";
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.cards_container {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty {
|
||||
max-width: 10px;
|
||||
}
|
||||
|
||||
.empty>.card_image {
|
||||
filter: brightness(0.5);
|
||||
max-height: 250px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,144 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore from '~/netcode';
|
||||
import type { Player } from '~/netcode/Player';
|
||||
import { playSound } from '~/helpers/audioHelpers';
|
||||
|
||||
const current_players = computed<Array<Player>>(() => Array.from(goStore.current_game?.players ?? []).filter(p => p.team === goStore.current_game?.current_turn))
|
||||
|
||||
watch(
|
||||
() => goStore.current_game?.current_turn?.uuid,
|
||||
() => {
|
||||
if (goStore.current_game?.current_turn?.uuid == goStore.self?.team?.uuid) {
|
||||
playSound('/sounds/ding.mp3')
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => goStore.current_game?.losers,
|
||||
() => {
|
||||
if (goStore.self?.uuid && goStore.current_game?.losers && goStore.current_game.losers.includes(goStore.self.uuid)) {
|
||||
showLoserScreen()
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => goStore.current_game?.ended,
|
||||
() => {
|
||||
if (goStore.current_game?.ended && goStore.self?.uuid && goStore.current_game?.losers && !goStore.current_game.losers.includes(goStore.self.uuid)) {
|
||||
showWinnerScreen()
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => goStore.current_game?.players,
|
||||
(newList, oldList) => {
|
||||
if (!oldList || !newList) { return }
|
||||
if (goStore.current_game?.started) { return }
|
||||
if (newList?.length > oldList.length) {
|
||||
playSound('/sounds/teleport.mp3')
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const showResults = ref(false)
|
||||
const showOptions = ref(false)
|
||||
const isWinner = ref(false)
|
||||
|
||||
function showLoserScreen() {
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true
|
||||
isWinner.value = false
|
||||
}
|
||||
|
||||
function showWinnerScreen() {
|
||||
// playSound('/sounds/ding.mp3')
|
||||
showResults.value = true
|
||||
isWinner.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @contextmenu.prevent="false" id="game_board">
|
||||
<AnimatedBackground></AnimatedBackground>
|
||||
<div id="market">
|
||||
<MarketCards></MarketCards>
|
||||
</div>
|
||||
<div id="player_list_container">
|
||||
<div id="player_list">
|
||||
<PlayerListElement v-for="p in goStore.current_game?.players" :player="p">
|
||||
</PlayerListElement>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="current_player">
|
||||
<template v-for="p in current_players">
|
||||
<!-- <AnimatedBackground :color-start="goStore.self == p ? [.0, .3, .3] : undefined"
|
||||
:color-end="goStore.self == p ? [.0, 1, 1] : undefined" /> -->
|
||||
<PlayerSlot :player="p"></PlayerSlot>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ResultDialog :isVisible="showResults" :won="isWinner" :players="goStore.current_game?.players"
|
||||
@close="showResults = false">
|
||||
</ResultDialog>
|
||||
<GlobalOverlay></GlobalOverlay>
|
||||
<SelfView></SelfView>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#game_board {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 450px;
|
||||
grid-template-rows: min-content 1fr;
|
||||
grid-template-areas:
|
||||
"market player_list"
|
||||
"current_player player_list";
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#player_list,
|
||||
#current_player {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
/* background: url('/images/bg-darkgrass.png'); */
|
||||
}
|
||||
|
||||
#current_player {
|
||||
grid-area: current_player;
|
||||
}
|
||||
|
||||
#market {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#player_list_container {
|
||||
grid-area: player_list;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
|
||||
/* background-image: url("/images/bg-darkgrass.png"); */
|
||||
}
|
||||
|
||||
#player_list {
|
||||
min-height: 90%;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border: 5px solid #000;
|
||||
border-radius: 10px;
|
||||
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, .60));
|
||||
margin: 10px;
|
||||
backdrop-filter: blur(10px) contrast(90%) brightness(165%);
|
||||
background: rgba(26, 70, 91, 0.7);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
#player_list .card {
|
||||
--max-height: 90px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,31 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore from '~/netcode';
|
||||
|
||||
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" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card_overlay {
|
||||
/* display: none; */
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
/* top: left: */
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.settings_button {
|
||||
pointer-events: all;
|
||||
width: 30px;
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,244 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore, { netcode_create_game, join_game } from '~/netcode';
|
||||
import { playSound } from '~/helpers/audioHelpers';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const erika_alive = ref(true);
|
||||
const router = useRouter();
|
||||
|
||||
function killErika() {
|
||||
playSound('/sounds/splat1.mp3')
|
||||
erika_alive.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="background">
|
||||
<div class="title">
|
||||
Entrez dans le Royaume de Héron !
|
||||
</div>
|
||||
|
||||
<div class="middle-row">
|
||||
<div class="create-game-zone">
|
||||
<button @click="netcode_create_game">Create Game</button>
|
||||
</div>
|
||||
|
||||
<div class="games-list-zone">
|
||||
<div v-for="g in goStore.lobby?.games">
|
||||
<button v-if="g" @click="join_game(g.uuid)" :class="goStore.discordId == g.owner ? 'own_game' : ''">
|
||||
<template v-if="g.started && g.ended">
|
||||
GAME OVER
|
||||
</template>
|
||||
<template v-if="g.players.every(p => p.discord_id !== goStore.discordId)">
|
||||
<template v-if="!g.started">
|
||||
JOIN
|
||||
</template>
|
||||
<template v-if="g.started && !g.ended">
|
||||
SPECTATE
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="g.players.some(p => p.discord_id == goStore.discordId)">
|
||||
<template v-if="!g.started">
|
||||
JOIN BACK
|
||||
</template>
|
||||
<template v-if="g.started && !g.ended">
|
||||
CONTINUE
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<br>
|
||||
{{ g.players.length }} current players:<br><span>{{ g.players.map(p => p.username).join(",")
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overlay" v-if="erika_alive">
|
||||
<div class="erika-zone">
|
||||
<img @click="router.push({ path: '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>
|
||||
|
||||
|
||||
</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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.middle-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 100px;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.create-game-zone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 16px;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
padding: 10px 24px;
|
||||
height: 50px;
|
||||
transition-duration: 0.4s;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.games-list-zone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
button.own_game {
|
||||
border: solid yellow 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
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;
|
||||
|
||||
.overlay-image {
|
||||
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;
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.erika-zone:hover {
|
||||
.overlay-button {
|
||||
.kill-erika-button {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,106 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore, { netcode_create_game, join_game } from '~/netcode';
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
|
||||
async function discordLogin() {
|
||||
await navigateTo(config.public.discordLoginEndpoint, {
|
||||
external: true
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="background">
|
||||
<div class="title">
|
||||
Entrez dans le Royaume de Héron !
|
||||
</div>
|
||||
|
||||
<div class="middle-row">
|
||||
<div class="icon">
|
||||
<CardPreview :card_id="undefined"></CardPreview>
|
||||
</div>
|
||||
|
||||
<div v-if="goStore.current_game?.started">
|
||||
<b>Mode spectateur</b>
|
||||
</div>
|
||||
|
||||
<template v-if="!goStore.current_game?.started">
|
||||
<!-- <input type="text" v-model="username" />
|
||||
<button @click="register(username)">LOGIN</button> -->
|
||||
<button class="menu-button" @click="discordLogin()">Se connecter via Discord</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
Héron Realms n'est en aucun cas affilié à Hero Realms.<br>
|
||||
Le concept et certains noms de cartes sont la propriété de Wise Wizard Games.<br>
|
||||
Les illustrations sont la propriété de leurs auteurs respectifs (il y en a plein).<br>
|
||||
Ce magnifique fond d'écran est tiré de Fire Emblem: Engage.<br>
|
||||
Programmeur principal, owner du projet: Legonzaur
|
||||
</p>
|
||||
</footer>
|
||||
</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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.middle-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 100px;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.menu-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: 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);
|
||||
}
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
width: 100%;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,79 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore, { buy, useEffect } from '@/netcode';
|
||||
import { CardEffects, type Effect } from '@/netcode/Effect'
|
||||
import { CardType } from '@/netcode/Card'
|
||||
|
||||
const isTurn = computed(() => goStore.self && goStore.current_game?.started && (goStore.self.team == goStore.current_game.current_turn))
|
||||
const clientStore = useClientSideStore()
|
||||
|
||||
const buyableFiregem = computed(() => goStore?.current_game?.gem_stack.at(-1))
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="market">
|
||||
<CardPreview :tilted="false" :card_id="undefined"></CardPreview>
|
||||
<div class="separator"></div>
|
||||
<CardPreview :tilted="false" :card_id="c?.card_id" v-for="c in goStore.current_game?.market"
|
||||
@click="c && buy(c)">
|
||||
<template
|
||||
v-if="isTurn && goStore.self?.turn && c && c.cost && (goStore.self?.turn.gold_reserve ?? 0) >= c.cost">
|
||||
<button @click.stop="buy(c)">BUY</button>
|
||||
<button @click.stop="clientStore.findUseEffect(CardEffects.PLAY_NEXT_CARD_BOUGHT, c);"
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.PLAY_NEXT_CARD_BOUGHT) && e.usable)">
|
||||
BUY IN HAND</button>
|
||||
<button @click.stop="clientStore.findUseEffect(CardEffects.STACK_NEXT_CARD_BOUGHT, c)"
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.STACK_NEXT_CARD_BOUGHT) && e.usable)">
|
||||
BUY IN STACK (generic)</button>
|
||||
|
||||
<button @click.stop="clientStore.findUseEffect(CardEffects.STACK_NEXT_ACTION_BOUGHT, c)"
|
||||
v-if="c.card_type == CardType.ACTION && clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.STACK_NEXT_ACTION_BOUGHT) && e.usable)">
|
||||
BUY IN STACK (actions)</button>
|
||||
</template>
|
||||
</CardPreview>
|
||||
<div class="separator"></div>
|
||||
<CardPreview :tilted="false" :card_id="goStore.current_game?.gem_stack.at(-1)?.card_id"
|
||||
@click="buy(goStore.current_game?.gem_stack.at(-1)!)">
|
||||
<template
|
||||
v-if="isTurn && goStore.self?.turn && goStore.current_game?.gem_stack.length && goStore.current_game?.gem_stack.length > 0 && (goStore.self?.turn.gold_reserve ?? 0) >= (goStore.current_game?.gem_stack.at(-1)?.cost ?? Infinity)">
|
||||
<button @click.stop="buy(goStore.current_game.gem_stack.at(-1)!)">
|
||||
BUY
|
||||
</button>
|
||||
<button @click.stop="clientStore.findUseEffect(CardEffects.PLAY_NEXT_CARD_BOUGHT, buyableFiregem)"
|
||||
v-if="buyableFiregem && clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.PLAY_NEXT_CARD_BOUGHT) && e.usable)">
|
||||
BUY IN HAND</button>
|
||||
<button @click.stop="clientStore.findUseEffect(CardEffects.STACK_NEXT_CARD_BOUGHT, buyableFiregem)"
|
||||
v-if="buyableFiregem && clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.STACK_NEXT_CARD_BOUGHT) && e.usable)">
|
||||
BUY IN STACK(generic)
|
||||
</button>
|
||||
</template>
|
||||
</CardPreview>
|
||||
</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);
|
||||
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 50px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,130 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
export interface Props {
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
const _devicePixelRatio = computed(() => devicePixelRatio)
|
||||
const _window = computed(() => window)
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isVisible" class="dialog-overlay" @click="close">
|
||||
<div class="dialog-box" @click.stop>
|
||||
<div class="dialog-header">
|
||||
<h3>Visuals</h3>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Toggle Card Style</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<button @click="settingsStore.switchCardStyles">Toggle Card style</button>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<h3>Graphics</h3>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Shader Quality</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="range" min="1" max="10" v-model="settingsStore.shaderQuality"
|
||||
:disabled="settingsStore.shaderFramerate == 0">
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span>{{ Math.floor(_window.innerWidth * _devicePixelRatio * settingsStore.shaderQuality / 10) }}x{{
|
||||
Math.floor(_window.innerHeight *
|
||||
_devicePixelRatio * settingsStore.shaderQuality / 10) }}</span>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Shader Framerate</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="range" min="0" max="65" step="5" v-model="settingsStore.shaderFramerate">
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span v-if="settingsStore.shaderFramerate == 65">vsync</span>
|
||||
<span v-else>{{ settingsStore.shaderFramerate }} fps</span>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Pixelated</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="checkbox" v-model="settingsStore.shaderPixelated">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
display: grid;
|
||||
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
background-image: url('../images/bg-grass.png');
|
||||
padding: 0 20px;
|
||||
border-radius: 5px;
|
||||
width: 800px;
|
||||
border-radius: 30px;
|
||||
max-width: 100%;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
display: grid;
|
||||
grid-template-columns: 33% 33% 33%;
|
||||
}
|
||||
|
||||
.dialog-box h3 {
|
||||
font-size: 30px;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.dialog-header,
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 10px 0;
|
||||
grid-column-start: 2;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
grid-column-start: 3;
|
||||
}
|
||||
</style>
|
||||
@@ -1,40 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { Player } from '~/netcode/Player';
|
||||
|
||||
|
||||
export interface Props {
|
||||
player: Player;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="player-icon-container">
|
||||
<img class="avatar" v-if="props.player.image" :src="props.player.image">
|
||||
<b class="player-name">{{ props.player.username }}</b>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.player-icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.player-name {
|
||||
font-size: 20px;
|
||||
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin-left: 10px;
|
||||
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
</style>
|
||||
@@ -1,159 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore from '@/netcode';
|
||||
import type { Player } from "~/netcode/Player";
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const view = ref<"main" | "discard">("main")
|
||||
|
||||
function show(data: "main" | "discard") {
|
||||
view.value = data
|
||||
}
|
||||
|
||||
|
||||
export interface Props {
|
||||
player: Player;
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="`player_list_element ${goStore.self === props.player ? 'self' : ''}`">
|
||||
<!-- <AnimatedBackground v-if="goStore.self === props.player" :color-start="[.0, .3, .3]" :color-end="[.0, 1, 1]" /> -->
|
||||
<div class="button" id="player_list_buttons">
|
||||
<div class="main_button" @click="show('main')">
|
||||
<div class="tooltip">Board</div>
|
||||
<img :src="`/cards/${settingsStore.cardStyle}/${player.board.at(-1)?.card_id.toString().padStart(3, '0') ?? 'background'}.png`"
|
||||
draggable="false" :class="view === 'main' && 'selected'">
|
||||
<span class="button_card_number">{{ player.board.length }}</span>
|
||||
|
||||
</div>
|
||||
<div class="stack_button">
|
||||
<div class="tooltip">Stack</div>
|
||||
<img :src="'/cards/background.png'" draggable="false">
|
||||
<span class="button_card_number">{{ player.stack_pile.length }}</span>
|
||||
|
||||
</div>
|
||||
<div class="discard_button" @click="show('discard')">
|
||||
<div class="tooltip">Discard</div>
|
||||
<img :src="`/cards/${settingsStore.cardStyle}/${player.discard_pile.at(-1)?.card_id.toString().padStart(3, '0') ?? 'background'}.png`"
|
||||
draggable="false" :class="view === 'discard' && 'selected'">
|
||||
<span class="button_card_number">{{ player.discard_pile.length }}</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="view">
|
||||
<PlayerSlot :player="player" :hide_stack_and_discard="true" v-if="view == 'main'" />
|
||||
<template v-if="view == 'discard'">
|
||||
<CardPreview :tilted="false" v-for=" card in player.discard_pile " :card_id="card.card_id">
|
||||
</CardPreview>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.button>div>img:not(.selected) {
|
||||
filter: brightness(.5)
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tooltip {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
transition: .1s transform;
|
||||
background: white;
|
||||
right: -0px;
|
||||
border-top-right-radius: 1em;
|
||||
border-bottom-right-radius: 1em;
|
||||
padding-right: 1em;
|
||||
transition-property: visibility, transform;
|
||||
transition-duration: 0.5s, 0.1s;
|
||||
transition-timing-function: ease-in-out, ease-in-out;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.button>div {
|
||||
flex: 1 1 0;
|
||||
border-right: 5px solid black;
|
||||
width: 45px;
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button>div:not(.stack_button) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button div:hover .tooltip {
|
||||
visibility: visible;
|
||||
display: block;
|
||||
transform: translateX(100%);
|
||||
transition-duration: 0, 0.1s;
|
||||
}
|
||||
|
||||
.button_card_number {
|
||||
font-size: 2em;
|
||||
position: absolute;
|
||||
text-shadow: white 0 0 1px;
|
||||
}
|
||||
|
||||
|
||||
.button>div:not(:last-child) {
|
||||
border-bottom: 5px solid black;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.player_list_element {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-template-areas:
|
||||
"button player";
|
||||
border: 5px solid black;
|
||||
min-height: 200px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.player_list_element.self {
|
||||
/* background-image: url('/images/bg-grass.png'); */
|
||||
}
|
||||
|
||||
.view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
#player_list_buttons img {
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.view #contextMenuButtons button:not(.enemy_action) {
|
||||
display: none
|
||||
}
|
||||
</style>
|
||||
@@ -1,567 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore, {
|
||||
useEffect,
|
||||
playCard,
|
||||
playAllCards,
|
||||
discard,
|
||||
lockCard,
|
||||
heal,
|
||||
damage_champion,
|
||||
damage_team,
|
||||
endTurn
|
||||
|
||||
} from "~/netcode";
|
||||
import { CardType, type Card } from "~/netcode/Card";
|
||||
import type { Player } from "~/netcode/Player";
|
||||
import { CardEffects, type Effect, EffectType } from "@/netcode/Effect";
|
||||
import type { Team } from "~/netcode/Team";
|
||||
import { useClientSideStore } from "~/stores/turn";
|
||||
|
||||
export interface Props {
|
||||
player: Player;
|
||||
hide_stack_and_discard?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
hide_stack_and_discard: () => false,
|
||||
})
|
||||
|
||||
const discard_unwrapped = ref(false)
|
||||
const isPlayerTurn = computed(() => props.player.team === goStore.current_game?.current_turn);
|
||||
const isSelf = computed(() => props.player.uuid === goStore.self?.uuid);
|
||||
const isSelfTurn = computed(() => goStore.self?.team === goStore.current_game?.current_turn)
|
||||
const isPlayerEnemy = computed(() => goStore.self?.team !== props.player.team)
|
||||
const warningText = ref("Effets restants!")
|
||||
const clientStore = useClientSideStore()
|
||||
|
||||
const make_discard = (target: Player) =>
|
||||
clientStore.findUseEffect(CardEffects.MAKE_DISCARD, target);
|
||||
const sacrifice = (target: Card) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
|
||||
const stun = (target: Card) => clientStore.findUseEffect(CardEffects.STUN, target)
|
||||
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 prepare_champion = (target: Card) => clientStore.findUseEffect(CardEffects.PREPARE, target)
|
||||
|
||||
|
||||
let warning = ref(false)
|
||||
|
||||
function has_actions_remaining(): boolean {
|
||||
return (goStore.self?.turn?.damage_reserve ? goStore.self?.turn?.damage_reserve > 0 : false)
|
||||
|| (goStore.self?.turn?.gold_reserve ? goStore.self?.turn?.gold_reserve >= (goStore.current_game?.market.reduce((min, card) => Math.min(min, card?.cost ?? Infinity), Infinity) ?? Infinity) : false)
|
||||
|| (goStore.self?.turn?.fuck_u_markers ? goStore.self?.turn?.fuck_u_markers > 0 : false)
|
||||
|| !goStore.self?.turn?.effects_availables.every(e => !e.usable || e.effect_type == EffectType.SUICIDE)
|
||||
|| !goStore.self?.board.every(c => (c.uuid in (goStore.self?.turn?.cards_locked ?? {})))
|
||||
|| false
|
||||
}
|
||||
|
||||
function can_buy_fire_gem(): boolean {
|
||||
return (goStore.self?.turn?.gold_reserve ? goStore.self?.turn?.gold_reserve >= 2 : 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()
|
||||
}
|
||||
}
|
||||
|
||||
function damage_team_all(team: Team) {
|
||||
for (let i = 0; i < (goStore.self?.turn?.damage_reserve ?? 0); i++) {
|
||||
damage_team(team)
|
||||
}
|
||||
}
|
||||
|
||||
function check_for_guard(player: Player) {
|
||||
return player.board.some(c => c.guard === true)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="player" :class="{ turn: isPlayerTurn, discard_unwrapped, self: isSelf }">
|
||||
<template v-if="!hide_stack_and_discard">
|
||||
<div class="stack">
|
||||
|
||||
<CardsGrouped :stack="props.player.stack_pile">
|
||||
<!-- <button v-if="isSelf && isTurn" @click="draw">DRAW</button> -->
|
||||
</CardsGrouped>
|
||||
</div>
|
||||
|
||||
<div class="discard" :class="{ invisible: discard_unwrapped }" @click="() => discard_unwrapped = true">
|
||||
<CardsGrouped :stack="props.player.discard_pile" :visible="!discard_unwrapped"> </CardsGrouped>
|
||||
</div>
|
||||
|
||||
<div class="discard_overlay" v-if="discard_unwrapped">
|
||||
<CardPreview :tilted="false" v-if="discard_unwrapped" :card_id="c.card_id"
|
||||
v-for="c in props.player.discard_pile">
|
||||
<template v-if="goStore.self?.turn && c && isSelf && isPlayerTurn">
|
||||
<button
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.SACRIFICE) && e.usable)"
|
||||
@click.stop="sacrifice(c)">
|
||||
SACRIFICE
|
||||
</button>
|
||||
<button
|
||||
v-if="c.card_type == CardType.CHAMPION && clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.RESTACK_DISCARDED_CHAMPION) && e.usable)"
|
||||
@click.stop="restack_discarded_champion(c)">
|
||||
RESTACK_DISCARDED_CHAMPION
|
||||
</button>
|
||||
<button
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.RESTACK_DISCARDED_CARD) && e.usable)"
|
||||
@click.stop="restack_discarded_card(c)">
|
||||
RESTACK_DISCARDED_CARD
|
||||
</button>
|
||||
|
||||
</template>
|
||||
</CardPreview>
|
||||
</div>
|
||||
<div :id="`discard_unwrapped_bg`" :class="`${discard_unwrapped ? 'unwrapped' : ''}`"
|
||||
@click="() => discard_unwrapped = false"></div>
|
||||
</template>
|
||||
<div id="player_banner" :class="hide_stack_and_discard ? 'small' : 'big'">
|
||||
<div class="ready-bubble not-ready" v-if="!props.player.ready && !goStore.current_game?.started">Pas encore
|
||||
prêt...
|
||||
</div>
|
||||
<div class="ready-bubble ready" v-if="props.player.ready && !goStore.current_game?.started">Prêt!</div>
|
||||
<div id="player_info">
|
||||
<PlayerIcon :player="props.player"></PlayerIcon>
|
||||
</div>
|
||||
<div id="player_stats">
|
||||
<template v-if="goStore.current_game?.started">
|
||||
<PlayerStats :player="props.player">
|
||||
</PlayerStats>
|
||||
<template v-if="!hide_stack_and_discard">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<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)">
|
||||
Damage
|
||||
</button>
|
||||
<button @click.stop="damage_team_all(props.player.team)"
|
||||
v-if="isSelfTurn && isPlayerEnemy && goStore.self?.turn && props.player.team"
|
||||
class="damage_button all_damage_button"
|
||||
:disabled="(goStore.self?.turn.damage_reserve ?? 0) <= 0 || check_for_guard(props.player)">
|
||||
(All)
|
||||
</button>
|
||||
<button v-if="goStore.self && isSelfTurn && isPlayerEnemy && goStore.self?.turn"
|
||||
@click.stop="make_discard(props.player)" class="make_discard_button"
|
||||
:disabled="!clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.MAKE_DISCARD) && e.usable)">
|
||||
make Discard
|
||||
</button>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="cards_container">
|
||||
<!-- HAND -->
|
||||
<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"
|
||||
@click.stop="discard(c)">
|
||||
DISCARD
|
||||
</button>
|
||||
</CardPreview>
|
||||
<!-- BOARD -->
|
||||
<CardPreview :card_id="c.card_id" v-for="c in props.player.board" @click="lockCard(c)"
|
||||
:locked="props.player.turn && c.uuid in props.player.turn.cards_locked">
|
||||
|
||||
<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">
|
||||
<template v-if="e.card == c">
|
||||
<button v-if="!clientStore.effectsHandled.has(e.effect)" @click.stop="useEffect(e)"
|
||||
:disabled="!e.usable"
|
||||
:class="{ 'suicide_effect': e.effect_type == EffectType.SUICIDE }">
|
||||
EFFECT<br>
|
||||
{{ e.effect + " " + (e.effect_type ?? '') }}
|
||||
</button>
|
||||
<button v-if="clientStore.effectsHandled.has(e.effect)"
|
||||
@click.stop="clientStore.activateEffect(e)" :disabled="!e.usable"
|
||||
:class="{ 'suicide_effect': e.effect_type == EffectType.SUICIDE }">
|
||||
EFFECT<br>
|
||||
{{ e.effect + " " + (e.effect_type ?? '') }}
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
||||
<button
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.PREPARE) && e.usable) && c.card_type == CardType.CHAMPION"
|
||||
@click.stop="prepare_champion(c)">PREPARE</button>
|
||||
</template>
|
||||
<!-- Card Unlocked -->
|
||||
<template v-if="!(c.uuid in props.player.turn.cards_locked)">
|
||||
<button v-if="(props.player.turn.discard_markers + props.player.turn.fuck_u_markers) > 0"
|
||||
@click.stop="discard(c)">
|
||||
DISCARD
|
||||
</button>
|
||||
<button
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.SACRIFICE) && e.usable)"
|
||||
@click.stop="sacrifice(c)">
|
||||
SACRIFICE
|
||||
</button>
|
||||
<button @click.stop="lockCard(c)"
|
||||
:disabled="((props.player.turn.discard_markers + props.player.turn.fuck_u_markers) > 0 && c !== props.player.turn.discard_marker_card) || props.player.turn.fuck_u_markers > 0">
|
||||
LOCK
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
||||
<template
|
||||
v-if="c && goStore.self?.turn && isPlayerEnemy && isSelfTurn && (c.health_as_champion ?? 0) > 0">
|
||||
<button class="enemy_action"
|
||||
:disabled="(goStore.self?.turn.damage_reserve ?? 0) < (c.health_as_champion ?? Infinity) || (check_for_guard(props.player) && !c.guard)"
|
||||
@click.stop="damage_champion(c)">
|
||||
DAMAGE ({{ c.health_as_champion }})
|
||||
</button>
|
||||
<button class="enemy_action"
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.STUN) && e.usable)"
|
||||
@click.stop="stun(c)" :disabled="(check_for_guard(props.player) && !c.guard)">
|
||||
STUN
|
||||
</button>
|
||||
</template>
|
||||
</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
|
||||
}
|
||||
|
||||
.warning.visible {
|
||||
visibility: visible
|
||||
}
|
||||
|
||||
.warning-fire-gem {
|
||||
background-color: rgba(255, 165, 0, 0.7);
|
||||
}
|
||||
|
||||
#end_turn {
|
||||
z-index: 10;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#end_turn.hidden {
|
||||
visibility: hidden
|
||||
}
|
||||
|
||||
.stack,
|
||||
.discard,
|
||||
.cards_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap-reverse;
|
||||
}
|
||||
|
||||
.stack {
|
||||
grid-area: stack;
|
||||
margin-left: 75px;
|
||||
}
|
||||
|
||||
.discard {
|
||||
margin-top: 40px;
|
||||
grid-area: discard;
|
||||
justify-content: start;
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
margin-left: 75px;
|
||||
}
|
||||
|
||||
.discard.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.player {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-template-rows: min-content min-content 1fr;
|
||||
/* border-top: 5px solid black; */
|
||||
grid-template-areas:
|
||||
"player_stats player_stats"
|
||||
"stack player"
|
||||
"discard player";
|
||||
transition: all 1s;
|
||||
/* margin-bottom: -75px;
|
||||
padding-bottom: 75px; */
|
||||
}
|
||||
|
||||
.discard_overlay {
|
||||
grid-template-areas:
|
||||
"player_stats player_stats"
|
||||
"stack player";
|
||||
}
|
||||
|
||||
#current_player_board {
|
||||
grid-area: player;
|
||||
}
|
||||
|
||||
.other_player_buttons_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0 5px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.damage_button:disabled {
|
||||
background-color: rgba(255, 77, 77, 0.5);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.damage_button:hover {
|
||||
background-color: #e60000;
|
||||
}
|
||||
|
||||
.damage_button:disabled:hover {
|
||||
background-color: rgba(230, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.damage_button:active {
|
||||
transform: translateY(2px);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.all_damage_button {
|
||||
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);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.make_discard_button:hover {
|
||||
background-color: #03883a;
|
||||
}
|
||||
|
||||
.make_discard_button:disabled:hover {
|
||||
background-color: rgba(3, 136, 58, 0.5);
|
||||
}
|
||||
|
||||
.make_discard_button:active {
|
||||
transform: translateY(2px);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.turn_icon {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
#player_banner {
|
||||
display: flex;
|
||||
grid-area: player_stats;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding-top: 1em;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#player_banner.small {
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
#player_banner>* {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
|
||||
#player_info {
|
||||
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%);
|
||||
}
|
||||
|
||||
#player_stats {
|
||||
display: flex;
|
||||
grid-area: player_stats;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#player_banner img {
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.turn_icon img {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.turn_icon_number {
|
||||
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);
|
||||
}
|
||||
|
||||
.end_turn_button: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 */
|
||||
}
|
||||
|
||||
.ready-bubble {
|
||||
color: white;
|
||||
padding: 0.5em;
|
||||
border-radius: 1em;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
margin-bottom: 3%;
|
||||
}
|
||||
|
||||
.ready {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.not-ready {
|
||||
background-color: maroon;
|
||||
}
|
||||
|
||||
.suicide_effect {
|
||||
background: linear-gradient(to bottom right, #666666, #ccc);
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
@@ -1,65 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { Player } from '~/netcode/Player';
|
||||
|
||||
|
||||
export interface Props {
|
||||
player: Player
|
||||
}
|
||||
|
||||
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">
|
||||
<img src="/images/gold.png" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">
|
||||
{{ player.turn?.gold_reserve }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="turn_icon">
|
||||
<img src="/images/damage.png" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">
|
||||
{{ player.turn?.damage_reserve }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="turn_icon">
|
||||
<span class="icon_img">🖕</span>
|
||||
<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;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
img.icon_img {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
span.icon_img {
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.turn_icon_number {
|
||||
position: absolute;
|
||||
text-shadow: white 0 0 1px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,319 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import type { Card } from '~/netcode/Card';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="background">
|
||||
<div class="overlay-2">
|
||||
<div class="erika-zone-2">
|
||||
<img @click="router.push({ path: '/' })" 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>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.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;
|
||||
;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
background-color: rgba(255, 255, 255, .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;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.overlay-2 {
|
||||
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;
|
||||
|
||||
.overlay-image {
|
||||
position: sticky;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,110 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import goStore, { useEffect, playCard, playAllCards, discard, lockCard, heal, damage_champion, damage_team, endTurn } from '~/netcode';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="cards_container" id="self_hand">
|
||||
<CardPreview :card_id="c?.card_id" v-for=" c in goStore.self?.hand " @click="c && playCard(c)">
|
||||
<button v-if="c && goStore.self?.turn && (goStore.self.turn.fuck_u_markers ?? 0) > 0"
|
||||
@click.stop="discard(c)">DISCARD</button>
|
||||
</CardPreview>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cards_container {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stack_and_discard {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
#play_all {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
font-size: 10em;
|
||||
}
|
||||
|
||||
#turn_data {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: rgba(255, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
width: 60px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#end_turn {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#self_hand {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
left: 50%;
|
||||
bottom: -50px;
|
||||
max-width: 100vw;
|
||||
justify-content: flex-start;
|
||||
transition: 0.5s all cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
transform: translate(-50%, 150px)
|
||||
}
|
||||
|
||||
#self_hand:hover {
|
||||
transform: translate(-50%, 0px);
|
||||
}
|
||||
|
||||
#end_turn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
#self_hand .card {
|
||||
transition: 0.5s all cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#self_hand .card #contextMenuButtons {
|
||||
top: 0;
|
||||
bottom: unset;
|
||||
}
|
||||
|
||||
#self_hand .card.active:hover #contextMenuButtons {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
|
||||
#self_hand .card:hover {
|
||||
transform: translateY(-40px);
|
||||
|
||||
}
|
||||
|
||||
.cards_container {
|
||||
min-width: 250px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,261 @@
|
||||
<script setup lang="ts">
|
||||
export interface Props {
|
||||
uuid?: string;
|
||||
cardId: number | undefined;
|
||||
locked?: boolean;
|
||||
wrapped?: boolean;
|
||||
tilted?: boolean;
|
||||
interactible?: boolean;
|
||||
noEffects?: boolean;
|
||||
used_effects_indexes?: number[];
|
||||
discardable?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
wrapped: () => false,
|
||||
tilted: () => true,
|
||||
locked: () => true,
|
||||
interactible: () => true,
|
||||
noEffects: () => false,
|
||||
discardable: () => false,
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
"click",
|
||||
"effectClick",
|
||||
"drop",
|
||||
"move",
|
||||
"startMove",
|
||||
"stopMove",
|
||||
]);
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const childCount = ref<number>(0);
|
||||
|
||||
const extension = computed(() => {
|
||||
if (settingsStore.cardStyle == CardStyles.SVG) {
|
||||
return ".svg";
|
||||
}
|
||||
return ".png";
|
||||
});
|
||||
|
||||
function cardClick() {
|
||||
if (!props.cardId) return;
|
||||
emit("click");
|
||||
}
|
||||
|
||||
const isZooming = ref<boolean>(false);
|
||||
|
||||
function mouseenter(e: MouseEvent) {
|
||||
// User is Right Clicking
|
||||
if ((e.buttons & 2) == 2) {
|
||||
isZooming.value = true;
|
||||
}
|
||||
|
||||
// goStore.hoveredCard = props.card_id
|
||||
}
|
||||
function mouseleave(e: MouseEvent) {
|
||||
isZooming.value = false;
|
||||
}
|
||||
|
||||
function rightClickDown(e: MouseEvent) {
|
||||
isZooming.value = true;
|
||||
}
|
||||
|
||||
function rightClickUp(e: MouseEvent) {
|
||||
isZooming.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="cardHTMLElement"
|
||||
class="card"
|
||||
:class="{
|
||||
active: childCount > 0,
|
||||
wrapped,
|
||||
unlocked: !props.locked,
|
||||
zoom: isZooming,
|
||||
no_effects: noEffects || !props.locked,
|
||||
discardable,
|
||||
}"
|
||||
@contextmenu.prevent="false"
|
||||
@mousedown.right="rightClickDown"
|
||||
@mouseup.right="rightClickUp"
|
||||
@click.stop="cardClick"
|
||||
@mouseenter="mouseenter"
|
||||
@mouseleave="mouseleave"
|
||||
>
|
||||
<template v-if="props.cardId">
|
||||
<GameCardAtom
|
||||
v-if="settingsStore.cardStyle == CardStyles.SVG"
|
||||
:cardId="props.cardId"
|
||||
:used_effects_indexes="used_effects_indexes"
|
||||
@effectClick="emit('effectClick', $event)"
|
||||
></GameCardAtom>
|
||||
<div id="overlay">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<img
|
||||
:class="`card_image`"
|
||||
v-else
|
||||
:src="'/cards/background.png'"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* .card.dragging {
|
||||
transform: translateX(v-bind("translateX + 'px'"))
|
||||
translateY(v-bind("translateY + 'px'"));
|
||||
cursor: grabbing;
|
||||
z-index: 999;
|
||||
} */
|
||||
|
||||
/* .card.dragging > svg {
|
||||
transform: scale(110%);
|
||||
} */
|
||||
|
||||
.card.active {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card_image {
|
||||
max-height: var(--max-height);
|
||||
z-index: 1;
|
||||
height: var(--max-height);
|
||||
}
|
||||
|
||||
.card.wrapped {
|
||||
height: 1px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card.wrapped .card_image {
|
||||
filter: drop-shadow(0.5px 0.5px 0.5px gray)
|
||||
drop-shadow(-0.5px -0.5px 0.5px gray);
|
||||
}
|
||||
|
||||
.card.zoom {
|
||||
transform: scale(200%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#contextMenuButtons {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
word-break: break-word;
|
||||
bottom: 0;
|
||||
width: 99%;
|
||||
transform: translateY(-0px);
|
||||
transition: 0.05s transform var(--animation-curve);
|
||||
}
|
||||
|
||||
.card.active:hover #contextMenuButtons {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.card.no_effects svg > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#contextMenuButtons > * {
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
@keyframes gelatine {
|
||||
from,
|
||||
to {
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: scale(1, 1.5);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.5, 1);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: scale(1.1, 1.3);
|
||||
}
|
||||
}
|
||||
|
||||
.svg_resource_icon {
|
||||
cursor: pointer;
|
||||
animation: gelatine 0.3s normal;
|
||||
transition: all 1s var(--animation-curve);
|
||||
}
|
||||
|
||||
.svg_resource_icon:active {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.svg_resource_icon:hover {
|
||||
transform: scale(130%);
|
||||
}
|
||||
|
||||
.used {
|
||||
opacity: 0.2;
|
||||
filter: grayscale(0.6);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.svg_effect text {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card {
|
||||
--max-height: 250px;
|
||||
--max-width: 179px;
|
||||
--margin-bottom: calc(var(--max-height) * 0.05);
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 0px;
|
||||
margin-top: var(--margin-bottom);
|
||||
transition: all 0.15s var(--animation-curve);
|
||||
pointer-events: all;
|
||||
max-height: var(--max-height);
|
||||
max-width: var(--max-width);
|
||||
/* cursor: grab; */
|
||||
}
|
||||
|
||||
.card:not(svg) {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
width: calc(100% - 8%);
|
||||
height: 49%;
|
||||
}
|
||||
|
||||
.card.unlocked {
|
||||
margin-top: 0px;
|
||||
margin-bottom: var(--margin-bottom);
|
||||
transform: translateY(calc(var(--margin-bottom) * -0.25));
|
||||
filter: drop-shadow(
|
||||
calc(var(--margin-bottom) * 0.3) var(--margin-bottom) 1px rgba(0, 0, 0, 0.6)
|
||||
);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card.discardable {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.card.discardable svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { buyCard, buyGem, useToken } from "~/netcode";
|
||||
import {
|
||||
CardEffects,
|
||||
type Card,
|
||||
type Container,
|
||||
type Effect,
|
||||
type Game,
|
||||
} from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
game: Game;
|
||||
market: Container;
|
||||
marketStack: Container;
|
||||
fire_gems: Container;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// const selfPlayer = computed(() => authStore.logged ? playerList.value?.find(p => p.user.userId == authStore.selfUser.userId) : undefined)
|
||||
|
||||
// const isTurn = computed(() => goStore.self && goStore.current_game?.started && (goStore.self.team == goStore.current_game.current_turn))
|
||||
// const clientStore = useClientSideStore()
|
||||
|
||||
// const buyableFiregem = computed(() => goStore?.current_game?.gem_stack.at(-1))
|
||||
|
||||
function marketCardClick(e: string) {
|
||||
buyCard(props.game._id, e);
|
||||
}
|
||||
|
||||
function fireGemCardClick(e: string) {
|
||||
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 class="market">
|
||||
<GameCardGrouped
|
||||
:container="market"
|
||||
@cardClick="marketCardClick"
|
||||
:noEffects="true"
|
||||
:tokens="marketTokens"
|
||||
@tokenClick="tokenClick"
|
||||
>
|
||||
</GameCardGrouped>
|
||||
<div class="separator"></div>
|
||||
<GameCardGrouped
|
||||
:container="fire_gems"
|
||||
:wrapped="true"
|
||||
@cardClick="fireGemCardClick"
|
||||
:noEffects="true"
|
||||
:tokens="gemsTokens"
|
||||
@tokenClick="tokenClick"
|
||||
></GameCardGrouped>
|
||||
<div class="separator"></div>
|
||||
<GameCardGrouped
|
||||
:container="marketStack"
|
||||
:wrapped="true"
|
||||
:hidden="true"
|
||||
></GameCardGrouped>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.market {
|
||||
width: max-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;
|
||||
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);
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.market .card.zoom {
|
||||
transform: scale(200%) translateY(15%);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import type { Effect } from "~/netcode/interfaces";
|
||||
import { tokens } from "~/services/loadCards";
|
||||
import effect_locales from "~/assets/locales/effects.json";
|
||||
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="" class="token_container">
|
||||
<Component
|
||||
:is="tokens[props.token.effect]"
|
||||
v-if="props.token.effect in tokens"
|
||||
/>
|
||||
<span v-else>{{ props.token.effect }}</span>
|
||||
<div class="token_description" v-if="props.token.effect in effect_locales">
|
||||
<span
|
||||
><b>{{
|
||||
effect_locales[props.token.effect as keyof typeof effect_locales]
|
||||
.title
|
||||
}}</b></span
|
||||
>
|
||||
<span>{{
|
||||
effect_locales[props.token.effect as keyof typeof effect_locales]
|
||||
.description
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.token_container {
|
||||
margin: 15px;
|
||||
/* backdrop-filter: blur(60px); */
|
||||
background: rgba(201, 212, 38, 1);
|
||||
border: 5px solid black;
|
||||
width: min-content;
|
||||
cursor: pointer;
|
||||
transition: 0.2s all;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
transition: 0.1s all var(--animation-curve);
|
||||
position: relative;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.token_container:hover {
|
||||
border: 5px solid red;
|
||||
transform: scale(110%);
|
||||
}
|
||||
|
||||
.token_description {
|
||||
min-width: max-content;
|
||||
min-height: 45px;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
transform: translateX(100%);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
background: rgb(168, 175, 74);
|
||||
border: 5px solid red;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.token_container:hover .token_description {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { cards } from "~/services/loadCards";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
cardId: {
|
||||
required: true,
|
||||
type: Number,
|
||||
},
|
||||
used_effects_indexes: {
|
||||
default: [],
|
||||
type: Array,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { cards };
|
||||
},
|
||||
beforeCreate() {
|
||||
(this as any).component = () => cards[this.cardId];
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Component
|
||||
:is="cards[cardId]"
|
||||
:cardId="cardId"
|
||||
:used_effects_indexes="used_effects_indexes"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,138 @@
|
||||
<script setup lang="ts">
|
||||
import type { Card } from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
card: Card;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const active = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="champion_small">
|
||||
<svg
|
||||
height="50"
|
||||
width="50"
|
||||
viewBox="-25 -25 50 50"
|
||||
class="champion_small_svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<defs>
|
||||
<clipPath id="guard">
|
||||
<path
|
||||
d="M 25.03887,48.82869 1.1094102,30.52586 10.249651,0.91124843 H 39.828094 L 48.968331,30.52586 Z"
|
||||
fill="black"
|
||||
stroke="lightgrey"
|
||||
stroke-width="2"
|
||||
></path>
|
||||
</clipPath>
|
||||
<clipPath id="none">
|
||||
<path
|
||||
d="M 0,246.85146 1.1094102,150 0,-11.33365 187.4845,-14.630353 185.82502,247.01727 Z"
|
||||
fill="black"
|
||||
stroke="lightgrey"
|
||||
stroke-width="2"
|
||||
></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<foreignObject
|
||||
width="100%"
|
||||
height="100%"
|
||||
class="champion_small_foreignObject"
|
||||
:class="{ active }"
|
||||
>
|
||||
<div class="champion_small_inside">
|
||||
<GameCardAtom
|
||||
@mousedown.right="active = true"
|
||||
@mouseup.right="active = false"
|
||||
@mouseleave="active = false"
|
||||
width="75"
|
||||
height="75"
|
||||
:card-id="card.cardId"
|
||||
></GameCardAtom>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<circle
|
||||
cx="2"
|
||||
cy="-1"
|
||||
r="1"
|
||||
:fill="card.guard ? 'black' : 'lightgray'"
|
||||
stroke="white"
|
||||
stroke-width="0.2"
|
||||
class="champion_small_defense"
|
||||
:class="{ active }"
|
||||
></circle>
|
||||
<text
|
||||
text-anchor="middle"
|
||||
font-family="Helvetica"
|
||||
font-weight="bold"
|
||||
class="champion_small_defense"
|
||||
font-size="1.5"
|
||||
:class="{ active }"
|
||||
x="2"
|
||||
y="-.5"
|
||||
:fill="card.guard ? 'white' : 'black'"
|
||||
>
|
||||
{{ card.defense }}
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.champion_small {
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.champion_small_svg {
|
||||
transform: scale(1500%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.champion_small_foreignObject {
|
||||
clip-path: polygon(
|
||||
58% 62%,
|
||||
67% 56%,
|
||||
59% 35.8%,
|
||||
40% 35.8%,
|
||||
31% 55%,
|
||||
41% 62%,
|
||||
49% 67.77%
|
||||
);
|
||||
transform: scale(30%) translate(-50%, -50%);
|
||||
transition: 0.15s all var(--animation-curve);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.champion_small_defense {
|
||||
transition: 0.15s all var(--animation-curve);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.champion_small_defense.active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.champion_small_foreignObject.active {
|
||||
clip-path: polygon(100% 100%, 100% 50%, 100% 0, 0 0, 0 50%, 0 100%, 49% 100%);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.champion_small_foreignObject .champion_small_inside {
|
||||
pointer-events: all;
|
||||
transform: translate(0, -20%) scale(50%);
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.champion_small_foreignObject.active .champion_small_inside {
|
||||
z-index: 11;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,279 @@
|
||||
<script setup lang="ts">
|
||||
import { delay } from "~/netcode/events";
|
||||
import {
|
||||
CardEffects,
|
||||
CardType,
|
||||
type Card,
|
||||
type Container,
|
||||
type Effect,
|
||||
type PlayingTurn,
|
||||
} from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
container: Container;
|
||||
playingTurn?: PlayingTurn;
|
||||
wrapped?: boolean;
|
||||
hidden?: boolean;
|
||||
noEffects?: boolean;
|
||||
tokens?: Effect[];
|
||||
championsKillable?: boolean;
|
||||
discardable?: boolean;
|
||||
locked?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
wrapped: () => false,
|
||||
hidden: () => false,
|
||||
noEffects: () => false,
|
||||
tokens: () => [],
|
||||
championsKillable: () => false,
|
||||
discardable: () => false,
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
"cardClick",
|
||||
"effectClick",
|
||||
"tokenClick",
|
||||
"damageChampionClick",
|
||||
"discardCardClick",
|
||||
]);
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const cards = computed(() => {
|
||||
if (props.hidden) {
|
||||
return props.container.cards.map((_v, i) => ({
|
||||
cardId: undefined,
|
||||
})) as unknown as Card[];
|
||||
}
|
||||
return props.container.cards;
|
||||
});
|
||||
|
||||
const animationSpeed = computed(() => settingsStore.animationSpeed + "ms");
|
||||
const shuffleAnimationSpeed = computed(
|
||||
() => settingsStore.animationSpeed * 2 + "ms"
|
||||
);
|
||||
|
||||
function populateContainer() {}
|
||||
|
||||
// const rotate = ref(false);
|
||||
// async function shuffleContainer() {
|
||||
// rotate.value = true;
|
||||
// await delay(settingsStore.animationSpeed * 2);
|
||||
// rotate.value = false;
|
||||
// await delay(settingsStore.animationSpeed * 2);
|
||||
// }
|
||||
|
||||
function getCardsPosition(cards: string) {}
|
||||
|
||||
const animationCallbacks = ref<Array<() => void>>([]);
|
||||
|
||||
function startAnimation() {
|
||||
animationCallbacks.value.forEach((p) => p());
|
||||
animationCallbacks.value = [];
|
||||
}
|
||||
|
||||
function endAnimation() {
|
||||
animationCallbacks.value.forEach((p) => p());
|
||||
}
|
||||
|
||||
// watch(
|
||||
// props.container,
|
||||
// (newValue, oldValue) => {
|
||||
// if (oldValue) {
|
||||
// containerEvents.delete(oldValue._id);
|
||||
// }
|
||||
// if (newValue) {
|
||||
// containerEvents.set(newValue._id, {
|
||||
// populateContainer,
|
||||
// shuffleContainer,
|
||||
// getCardsPosition,
|
||||
// startAnimation,
|
||||
// endAnimation,
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
// { immediate: true }
|
||||
// );
|
||||
|
||||
// async function onEnter(_el: any, done: () => void) {
|
||||
// animationCallbacks.value.push(done);
|
||||
// }
|
||||
|
||||
// async function onLeave(_el: any, done: () => void) {
|
||||
// animationCallbacks.value.push(done);
|
||||
// }
|
||||
|
||||
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>
|
||||
<div
|
||||
class="cards_container"
|
||||
:class="{ wrapped, rotate: container.shuffling }"
|
||||
>
|
||||
<TransitionGroup name="list">
|
||||
<GameCard
|
||||
@click="$emit('cardClick', c._id)"
|
||||
@effectClick="$emit('effectClick', c._id, $event)"
|
||||
v-for="(c, i) in cards"
|
||||
:cardId="c?.cardId"
|
||||
:uuid="c._id"
|
||||
:wrapped="wrapped"
|
||||
:key="c?._id || i"
|
||||
:locked="
|
||||
locked ||
|
||||
!playingTurn ||
|
||||
playingTurn?.cardsLocked.includes(c._id.toString())
|
||||
"
|
||||
:used_effects_indexes="c?.effects?.reduce( (prev,e, i)=>{
|
||||
if(playingTurn?.lockedEffects.includes(e._id)){
|
||||
prev.push(i)
|
||||
}
|
||||
return prev
|
||||
},[] as number[])"
|
||||
:noEffects="noEffects"
|
||||
:discardable="discardable"
|
||||
>
|
||||
<template v-for="t in tokens">
|
||||
<GameToken
|
||||
:token="t"
|
||||
@useToken="tokenClick(t, c)"
|
||||
v-if="
|
||||
(t.effect == CardEffects.SACRIFICE &&
|
||||
playingTurn &&
|
||||
!playingTurn?.cardsLocked.includes(c._id.toString())) ||
|
||||
(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)
|
||||
"
|
||||
></GameToken>
|
||||
</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>
|
||||
</GameCard>
|
||||
</TransitionGroup>
|
||||
<!-- <img :class="`card_image`" :src="'/cards/background.png'" draggable="false"> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.list-move,
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all v-bind("animationSpeed") ease;
|
||||
}
|
||||
|
||||
/* .list-leave-to, */
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
/* transform: translateX(30px); */
|
||||
}
|
||||
|
||||
.list-leave-active {
|
||||
position: absolute;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.rotate {
|
||||
rotate: 360deg;
|
||||
}
|
||||
|
||||
.cards_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
gap: 30px;
|
||||
min-width: 200px;
|
||||
flex-wrap: wrap;
|
||||
transition: rotate v-bind("shuffleAnimationSpeed") ease;
|
||||
}
|
||||
|
||||
.cards_container.wrapped {
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.cards_container.wrapped:before {
|
||||
height: calc(250px - 10px);
|
||||
display: block;
|
||||
content: "";
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.empty {
|
||||
max-height: 10px;
|
||||
}
|
||||
|
||||
.empty > .card_image {
|
||||
filter: brightness(0.5);
|
||||
max-height: 250px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
use * use[data-effect-id="0"] {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.cards_container .card #background rect:first-child {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.cards_container .card.wrapped #background rect:first-child {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.cards_container .card.card.wrapped:nth-child(5n) #background rect:first-child {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,166 @@
|
||||
<script setup lang="ts">
|
||||
import { deleteGame, kickPlayer } from "~/netcode";
|
||||
import type { Game } from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
game: Game;
|
||||
}
|
||||
|
||||
const deletionConfirmation = ref("Delete Game");
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const isVisible = ref(false);
|
||||
|
||||
watch(isVisible, () => {
|
||||
deletionConfirmation.value = "Delete Game";
|
||||
});
|
||||
|
||||
function deleteButtonClick() {
|
||||
if (deletionConfirmation.value == "Delete Game") {
|
||||
deletionConfirmation.value = "Are you sure?";
|
||||
} else {
|
||||
deleteGame(props.game._id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button @click="isVisible = true" class="dialog-open-button yellow">
|
||||
Game Configuration
|
||||
</button>
|
||||
<div
|
||||
:class="{ visible: isVisible }"
|
||||
class="dialog dialog-background"
|
||||
@click="isVisible = false"
|
||||
></div>
|
||||
<div class="dialog dialog-box" :class="{ visible: isVisible }" @click.stop>
|
||||
<div class="dialog-header">
|
||||
<h3>Extensions</h3>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Enable extensions</span>
|
||||
</div>
|
||||
<div class="dialog-body dialog-flex">
|
||||
<span v-for="extension in props.game.packs"
|
||||
><input type="checkbox" checked="true" disabled />{{ extension }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<h3>Players</h3>
|
||||
</div>
|
||||
<template v-for="team in game.teams">
|
||||
<template v-for="player in team.players">
|
||||
<div class="dialog-header">
|
||||
<span>{{ player.user.username }}</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<button
|
||||
:disabled="game.owner._id == player.user._id"
|
||||
@click="kickPlayer(game._id, player._id)"
|
||||
>
|
||||
Kick
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<div class="dialog-header">
|
||||
<h3>Danger Zone</h3>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<button class="red" @click="deleteButtonClick">
|
||||
{{ deletionConfirmation }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dialog {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
transition: 0.3s all var(--animation-curve);
|
||||
}
|
||||
.dialog-open-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dialog-open-button:active {
|
||||
filter: drop-shadow(0px 0px 1px);
|
||||
}
|
||||
|
||||
.dialog-flex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.dialog-background {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
transition: 0.3s all linear;
|
||||
}
|
||||
|
||||
.dialog-background.visible {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 100%);
|
||||
padding: 0 20px;
|
||||
border-radius: 5px;
|
||||
width: 800px;
|
||||
border-radius: 30px;
|
||||
max-width: 100%;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
display: grid;
|
||||
grid-template-columns: 33% 33% 33%;
|
||||
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6));
|
||||
background: rgb(124, 85, 13);
|
||||
}
|
||||
|
||||
.dialog-box.visible {
|
||||
bottom: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
}
|
||||
|
||||
.dialog-box h3 {
|
||||
font-size: 30px;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.dialog-header,
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 10px 0;
|
||||
grid-column-start: 2;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
grid-column-start: 3;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { Player } from '~/netcode/Player';
|
||||
|
||||
import type { Player } from "~/netcode/interfaces";
|
||||
|
||||
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>
|
||||
@@ -29,25 +27,25 @@ const close = () => {
|
||||
<h3 v-else>Défaite!</h3>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<PlayerIcon :player="players[0]"></PlayerIcon>
|
||||
<p v-if="won">
|
||||
Vous avez gagné!
|
||||
</p>
|
||||
<GamePlayerIcon :user="players[0].user"></GamePlayerIcon>
|
||||
<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;
|
||||
@@ -0,0 +1,211 @@
|
||||
<script setup lang="ts">
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const isVisible = ref(false);
|
||||
|
||||
const _devicePixelRatio = computed(() => devicePixelRatio);
|
||||
const _window = computed(() => window);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button @click="isVisible = true" class="dialog-open-button">
|
||||
Open Settings
|
||||
</button>
|
||||
<div
|
||||
:class="{ visible: isVisible }"
|
||||
class="dialog dialog-background"
|
||||
@click="isVisible = false"
|
||||
></div>
|
||||
<div class="dialog dialog-box" :class="{ visible: isVisible }" @click.stop>
|
||||
<div class="dialog-header">
|
||||
<h3>Game</h3>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Save discard size</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="checkbox" v-model="settingsStore.saveDiscardSize" />
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<template v-if="!settingsStore.saveDiscardSize"
|
||||
>Discard will always open small
|
||||
</template>
|
||||
<template v-else="!settingsStore.saveDiscardSize"
|
||||
>Discard will remember its size
|
||||
</template>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<h3>Visuals</h3>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Toggle Card Style</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<button @click="settingsStore.switchCardStyles">Toggle Card style</button>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span>{{ settingsStore.cardStyle }}</span>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Animation Speed</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="250"
|
||||
step="25"
|
||||
v-model="settingsStore.animationSpeed"
|
||||
/>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span>{{ settingsStore.animationSpeed }}ms</span>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Fast mode</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="checkbox" v-model="settingsStore.fastAnimations" />
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<h3>Graphics</h3>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Shader Quality</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="10"
|
||||
v-model="settingsStore.shaderQuality"
|
||||
:disabled="settingsStore.shaderFramerate == 0"
|
||||
/>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span v-if="_window"
|
||||
>{{
|
||||
Math.floor(
|
||||
(_window.innerWidth *
|
||||
_devicePixelRatio *
|
||||
settingsStore.shaderQuality) /
|
||||
10
|
||||
)
|
||||
}}x{{
|
||||
Math.floor(
|
||||
(_window.innerHeight *
|
||||
_devicePixelRatio *
|
||||
settingsStore.shaderQuality) /
|
||||
10
|
||||
)
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Shader Framerate</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="65"
|
||||
step="5"
|
||||
v-model="settingsStore.shaderFramerate"
|
||||
/>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span v-if="settingsStore.shaderFramerate == 65">vsync</span>
|
||||
<span v-else>{{ settingsStore.shaderFramerate }} fps</span>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Pixelated</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="checkbox" v-model="settingsStore.shaderPixelated" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dialog {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
transition: 0.3s all var(--animation-curve);
|
||||
}
|
||||
.dialog-open-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dialog-open-button:active {
|
||||
filter: drop-shadow(0px 0px 1px);
|
||||
}
|
||||
|
||||
.dialog-background {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
transition: 0.3s all linear;
|
||||
}
|
||||
|
||||
.dialog-background.visible {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 100%);
|
||||
padding: 0 20px;
|
||||
border-radius: 5px;
|
||||
width: 800px;
|
||||
border-radius: 30px;
|
||||
max-width: 100%;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
display: grid;
|
||||
grid-template-columns: 33% 33% 33%;
|
||||
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6));
|
||||
background: rgb(38, 62, 15);
|
||||
}
|
||||
|
||||
.dialog-box.visible {
|
||||
bottom: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
}
|
||||
|
||||
.dialog-box h3 {
|
||||
font-size: 30px;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.dialog-header,
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 10px 0;
|
||||
grid-column-start: 2;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
grid-column-start: 3;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,160 @@
|
||||
<script setup lang="ts">
|
||||
import { initCustomFormatter } from "vue";
|
||||
import { buyCard, buyGem, useToken } from "~/netcode";
|
||||
import {
|
||||
CardEffects,
|
||||
type Card,
|
||||
type Container,
|
||||
type Effect,
|
||||
type Game,
|
||||
type Player,
|
||||
} from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
game: Game;
|
||||
player: Player;
|
||||
}
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const isVisible = ref(false);
|
||||
|
||||
const discardTokens = computed(() =>
|
||||
props.game.currentTurn.tokens.filter((t) =>
|
||||
[
|
||||
CardEffects.RESTACK_DISCARDED_ACTION,
|
||||
CardEffects.RESTACK_DISCARDED_CARD,
|
||||
CardEffects.RESTACK_DISCARDED_CHAMPION,
|
||||
CardEffects.SACRIFICE,
|
||||
].includes(t.effect)
|
||||
)
|
||||
);
|
||||
|
||||
function tokenClick({ card, token }: { card: Card; token: Effect }) {
|
||||
useToken(props.game._id, token._id, card._id);
|
||||
}
|
||||
|
||||
function openDiscard() {
|
||||
isVisible.value = true;
|
||||
if (!settingsStore.saveDiscardSize) {
|
||||
settingsStore.discardFullscreen = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="discard">
|
||||
<GameCardGrouped
|
||||
@cardClick="openDiscard"
|
||||
:container="player.discard"
|
||||
:wrapped="true"
|
||||
:no-effects="true"
|
||||
>
|
||||
</GameCardGrouped>
|
||||
</div>
|
||||
<div
|
||||
class="discard-animations discard-background"
|
||||
:class="{ visible: isVisible, fullscreen: settingsStore.discardFullscreen }"
|
||||
@click.stop="isVisible = false"
|
||||
></div>
|
||||
<div
|
||||
class="discard-animations discard-menu"
|
||||
:class="{ visible: isVisible, fullscreen: settingsStore.discardFullscreen }"
|
||||
@click.stop="isVisible = false"
|
||||
>
|
||||
<span>Discard of {{ player.user.username }}</span>
|
||||
<button
|
||||
class="discard-fullscreen-btn"
|
||||
@click.stop="
|
||||
settingsStore.discardFullscreen = !settingsStore.discardFullscreen
|
||||
"
|
||||
>
|
||||
TOGGLE FULLSCREEN
|
||||
</button>
|
||||
<GameCardGrouped
|
||||
:container="player.discard"
|
||||
:wrapped="false"
|
||||
:hidden="false"
|
||||
:tokens="discardTokens"
|
||||
:noEffects="true"
|
||||
:playingTurn="game.currentTurn"
|
||||
:locked="true"
|
||||
@tokenClick="tokenClick"
|
||||
></GameCardGrouped>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
span {
|
||||
background: white;
|
||||
}
|
||||
.discard-fullscreen-btn {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.discard-animations {
|
||||
transition: 0.3s all var(--animation-curve);
|
||||
}
|
||||
.discard-background {
|
||||
transition: 0.3s all linear;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
pointer-events: none;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.discard-background.visible {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
pointer-events: all;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.discard-menu {
|
||||
left: -75%;
|
||||
top: 0;
|
||||
width: 25%;
|
||||
height: 100%;
|
||||
padding-left: 50%;
|
||||
background: rgb(100, 53, 44);
|
||||
position: fixed;
|
||||
z-index: 200;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.discard-menu.visible {
|
||||
left: -50%;
|
||||
}
|
||||
|
||||
.discard-menu.fullscreen {
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.discard-menu.visible.fullscreen {
|
||||
left: 0%;
|
||||
}
|
||||
|
||||
.discard-menu > .cards_container {
|
||||
pointer-events: all;
|
||||
overflow: auto;
|
||||
padding: 30px;
|
||||
max-width: 100%;
|
||||
transition: 0.3s all var(--animation-curve);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.discard-menu.fullscreen > .cards_container {
|
||||
max-width: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="loading_screen">Loading...</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#loading_screen {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import type { User } from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
user: User;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="player-icon-container">
|
||||
<img
|
||||
class="avatar"
|
||||
v-if="props.user.avatar"
|
||||
:src="`https://cdn.discordapp.com/avatars/${props.user.userId}/${props.user.avatar}.webp`"
|
||||
draggable="false"
|
||||
/>
|
||||
<b class="player-name">{{ props.user.username }}</b>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.player-icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.player-name {
|
||||
font-size: 20px;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin-left: 10px;
|
||||
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,191 @@
|
||||
<script setup lang="ts">
|
||||
import { damagePlayer, makeDiscard } from "~/netcode";
|
||||
import {
|
||||
CardEffects,
|
||||
CardType,
|
||||
type Game,
|
||||
type Player,
|
||||
} from "~/netcode/interfaces";
|
||||
|
||||
const emit = defineEmits(["click"]);
|
||||
|
||||
export interface Props {
|
||||
game: Game;
|
||||
player: Player;
|
||||
focused?: boolean;
|
||||
isSelfTurn: boolean;
|
||||
self?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
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>
|
||||
<div>
|
||||
<div
|
||||
class="player_list_element"
|
||||
@click="$emit('click')"
|
||||
:class="{
|
||||
self,
|
||||
focused,
|
||||
isSelfTurn,
|
||||
}"
|
||||
>
|
||||
<div id="player_banner" class="small">
|
||||
<!-- <div
|
||||
class="ready-bubble not-ready"
|
||||
v-if="!props.player.ready && !goStore.current_game?.started"
|
||||
>
|
||||
Pas encore prêt...
|
||||
</div>
|
||||
<div
|
||||
class="ready-bubble ready"
|
||||
v-if="props.player.ready && !goStore.current_game?.started"
|
||||
>
|
||||
Prêt!
|
||||
</div> -->
|
||||
<div id="player_info">
|
||||
<GamePlayerIcon :user="props.player.user"></GamePlayerIcon>
|
||||
</div>
|
||||
<div id="player_stats">
|
||||
<!-- <template v-if="goStore.current_game?.started"> -->
|
||||
<GamePlayerStats :game="props.game" :player="props.player">
|
||||
</GamePlayerStats>
|
||||
<!-- </template> -->
|
||||
</div>
|
||||
<template v-if="!self && isSelfTurn">
|
||||
<button
|
||||
v-if="
|
||||
game.currentTurn.tokens.some(
|
||||
(t) => t.effect == CardEffects.MAKE_DISCARD
|
||||
)
|
||||
"
|
||||
@click.stop="makeDiscardClick"
|
||||
>
|
||||
Make Discard
|
||||
</button>
|
||||
<button @click.stop="damage" :disabled="game.currentTurn.damage == 0">
|
||||
Damage
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="champion_zone">
|
||||
<GameCardChampionSmall
|
||||
v-for="c in player.board.cards.filter(
|
||||
(c) => c.cardType == CardType.CHAMPION
|
||||
)"
|
||||
:card="c"
|
||||
:key="c._id"
|
||||
></GameCardChampionSmall>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.champion_zone {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.player_list_element {
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, 0.6));
|
||||
transform: translate(-2.5px, -7.5px);
|
||||
backdrop-filter: blur(5px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.1s;
|
||||
min-width: 300px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.player_list_element::before {
|
||||
border-radius: 10px;
|
||||
background: rgb(15, 179, 255);
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.player_list_element.focused {
|
||||
filter: unset;
|
||||
transform: unset;
|
||||
}
|
||||
|
||||
.player_list_element.isTurn::before {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.player_list_element.self::before {
|
||||
background: rgb(33, 177, 76);
|
||||
}
|
||||
|
||||
#player_banner {
|
||||
display: flex;
|
||||
grid-area: player_stats;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#player_banner.small {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#player_banner > * {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
#player_stats {
|
||||
display: flex;
|
||||
grid-area: player_stats;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ready-bubble {
|
||||
color: white;
|
||||
padding: 0.5em;
|
||||
border-radius: 1em;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
.ready {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.not-ready {
|
||||
background-color: maroon;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import type { Container } from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
container: Container;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="cards_container" id="self_hand">
|
||||
<!-- <CardPreview :card_id="c.cardId" v-for="c in container.cards "> -->
|
||||
<!-- <button v-if="c && goStore.self?.turn && (goStore.self.turn.fuck_u_markers ?? 0) > 0"
|
||||
@click.stop="discard(c)">DISCARD</button> -->
|
||||
<!-- </CardPreview> -->
|
||||
<GameCardGrouped :container="container" :wrapped="false"> </GameCardGrouped>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cards_container {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stack_and_discard {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#play_all {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
font-size: 10em;
|
||||
}
|
||||
|
||||
#turn_data {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: rgba(255, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
width: 60px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#end_turn {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#self_hand {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
left: 50%;
|
||||
bottom: -50px;
|
||||
max-width: 100vw;
|
||||
justify-content: flex-start;
|
||||
transition: 0.25s all var(--animation-curve);
|
||||
transform: translate(-50%, 150px);
|
||||
}
|
||||
|
||||
#self_hand:hover {
|
||||
transform: translate(-50%, 0px);
|
||||
}
|
||||
|
||||
#end_turn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
#self_hand > div.card {
|
||||
transition: 0.25s all var(--animation-curve);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#self_hand > .cards_container {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
#self_hand > div.card #contextMenuButtons {
|
||||
top: 0;
|
||||
bottom: unset;
|
||||
}
|
||||
|
||||
#self_hand div.card.active:hover #contextMenuButtons {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
#self_hand div.card:hover {
|
||||
transform: translateY(-60px);
|
||||
}
|
||||
|
||||
#self_hand div.card.zoom:hover {
|
||||
transform: translateY(calc(-60px - 55%)) scale(200%);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,447 @@
|
||||
<script setup lang="ts">
|
||||
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 {
|
||||
player: Player;
|
||||
game: Game;
|
||||
hide_stack_and_discard?: boolean;
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
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 isSelf = computed(
|
||||
() => props.player.user.userId === authStore.selfUser.userId
|
||||
);
|
||||
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 make_discard = (target: Player) =>
|
||||
// clientStore.findUseEffect(CardEffects.MAKE_DISCARD, target);
|
||||
// const sacrifice = (target: CardParser) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
|
||||
// const stun = (target: CardParser) => clientStore.findUseEffect(CardEffects.STUN, target)
|
||||
// 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 prepare_champion = (target: CardParser) => clientStore.findUseEffect(CardEffects.PREPARE, target)
|
||||
|
||||
let warning = ref(false);
|
||||
|
||||
// function has_actions_remaining(): boolean {
|
||||
// return (goStore.self?.turn?.damage_reserve ? goStore.self?.turn?.damage_reserve > 0 : false)
|
||||
// || (goStore.self?.turn?.gold_reserve ? goStore.self?.turn?.gold_reserve >= (goStore.current_game?.market.reduce((min, card) => Math.min(min, card?.cost ?? Infinity), Infinity) ?? Infinity) : false)
|
||||
// || (goStore.self?.turn?.fuck_u_markers ? goStore.self?.turn?.fuck_u_markers > 0 : false)
|
||||
// || !goStore.self?.turn?.effects_availables.every(e => !e.usable || e.effect_type == EffectType.SUICIDE)
|
||||
// || !goStore.self?.board.every(c => (c.uuid in (goStore.self?.turn?.cards_locked ?? {})))
|
||||
// || false
|
||||
// }
|
||||
|
||||
// function can_buy_fire_gem(): boolean {
|
||||
// return (goStore.self?.turn?.gold_reserve ? goStore.self?.turn?.gold_reserve >= 2 : 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()
|
||||
// }
|
||||
|
||||
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 effectClick(cardUUID: string, effectIndex: number) {
|
||||
const card = props.player.board.cards.find((c) => c._id == cardUUID);
|
||||
if (!card) {
|
||||
console.error("card not found");
|
||||
return;
|
||||
}
|
||||
if (!props.game.currentTurn.cardsLocked.includes(cardUUID)) {
|
||||
return;
|
||||
}
|
||||
const effectUUID = card.effects[effectIndex]._id;
|
||||
lockEffect(props.game._id, effectUUID);
|
||||
}
|
||||
// function damage_team_all(team: Team) {
|
||||
// for (let i = 0; i < (goStore.self?.turn?.damage_reserve ?? 0); i++) {
|
||||
// damage_team(team)
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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>
|
||||
<div class="player" :class="{ turn: isPlayerTurn, self: isSelf }">
|
||||
<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)">
|
||||
Damage
|
||||
</button>
|
||||
<button @click.stop="damage_team_all(props.player.team)"
|
||||
v-if="isSelfTurn && isPlayerEnemy && goStore.self?.turn && props.player.team"
|
||||
class="damage_button all_damage_button"
|
||||
:disabled="(goStore.self?.turn.damage_reserve ?? 0) <= 0 || check_for_guard(props.player)">
|
||||
(All)
|
||||
</button>
|
||||
<button v-if="goStore.self && isSelfTurn && isPlayerEnemy && goStore.self?.turn"
|
||||
@click.stop="make_discard(props.player)" class="make_discard_button"
|
||||
:disabled="!clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.MAKE_DISCARD) && e.usable)">
|
||||
make Discard
|
||||
</button>
|
||||
</div> -->
|
||||
<!-- <br /> -->
|
||||
|
||||
<GameCardGrouped
|
||||
:container="player.board"
|
||||
:wrapped="false"
|
||||
:playingTurn="props.game.currentTurn"
|
||||
@cardClick="boardCardClick"
|
||||
@effectClick="effectClick"
|
||||
:tokens="cardTokens"
|
||||
@tokenClick="tokenClick"
|
||||
:championsKillable="!isSelf && isSelfTurn.value"
|
||||
:discardable="
|
||||
(player.fuckUMarkers > 0 || player.discardMarkers > 0) && isSelf
|
||||
"
|
||||
@damageChampionClick="damageChampionClick"
|
||||
@discardCardClick="discardCardClick"
|
||||
>
|
||||
</GameCardGrouped>
|
||||
<br />
|
||||
<!-- <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"
|
||||
@click.stop="discard(c)">
|
||||
DISCARD
|
||||
</button>
|
||||
</CardPreview>
|
||||
</template> -->
|
||||
|
||||
<!-- 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">
|
||||
// Card Locked
|
||||
<template v-if="c.uuid in props.player.turn.cards_locked">
|
||||
<template v-for="e in clientStore.maskedServerEffects">
|
||||
<template v-if="e.card == c">
|
||||
<button v-if="!clientStore.effectsHandled.has(e.effect)" @click.stop="useEffect(e)"
|
||||
:disabled="!e.usable"
|
||||
:class="{ 'suicide_effect': e.effect_type == EffectType.SUICIDE }">
|
||||
EFFECT<br>
|
||||
{{ e.effect + " " + (e.effect_type ?? '') }}
|
||||
</button>
|
||||
<button v-if="clientStore.effectsHandled.has(e.effect)"
|
||||
@click.stop="clientStore.activateEffect(e)" :disabled="!e.usable"
|
||||
:class="{ 'suicide_effect': e.effect_type == EffectType.SUICIDE }">
|
||||
EFFECT<br>
|
||||
{{ e.effect + " " + (e.effect_type ?? '') }}
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
||||
<button
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.PREPARE) && e.usable) && c.card_type == CardType.CHAMPION"
|
||||
@click.stop="prepare_champion(c)">PREPARE</button>
|
||||
</template>
|
||||
// Card Unlocked
|
||||
<template v-if="!(c.uuid in props.player.turn.cards_locked)">
|
||||
<button v-if="(props.player.turn.discard_markers + props.player.turn.fuck_u_markers) > 0"
|
||||
@click.stop="discard(c)">
|
||||
DISCARD
|
||||
</button>
|
||||
<button
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.SACRIFICE) && e.usable)"
|
||||
@click.stop="sacrifice(c)">
|
||||
SACRIFICE
|
||||
</button>
|
||||
<button @click.stop="lockCard(c)"
|
||||
:disabled="((props.player.turn.discard_markers + props.player.turn.fuck_u_markers) > 0 && c !== props.player.turn.discard_marker_card) || props.player.turn.fuck_u_markers > 0">
|
||||
LOCK
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
||||
<template
|
||||
v-if="c && goStore.self?.turn && isPlayerEnemy && isSelfTurn && (c.health_as_champion ?? 0) > 0">
|
||||
<button class="enemy_action"
|
||||
:disabled="(goStore.self?.turn.damage_reserve ?? 0) < (c.health_as_champion ?? Infinity) || (check_for_guard(props.player) && !c.guard)"
|
||||
@click.stop="damage_champion(c)">
|
||||
DAMAGE ({{ c.health_as_champion }})
|
||||
</button>
|
||||
<button class="enemy_action"
|
||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.STUN) && e.usable)"
|
||||
@click.stop="stun(c)" :disabled="(check_for_guard(props.player) && !c.guard)">
|
||||
STUN
|
||||
</button>
|
||||
</template> -->
|
||||
<!-- </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;
|
||||
}
|
||||
|
||||
.warning.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.warning-fire-gem {
|
||||
background-color: rgba(255, 165, 0, 0.7);
|
||||
}
|
||||
|
||||
#turn_buttons {
|
||||
grid-area: turn_buttons;
|
||||
}
|
||||
|
||||
#end_turn {
|
||||
z-index: 10;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#end_turn.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.player {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: min-content 1fr;
|
||||
/* border-top: 5px solid black; */
|
||||
grid-template-areas:
|
||||
"player_stats"
|
||||
"player"
|
||||
"turn_buttons";
|
||||
justify-items: center;
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
#current_player_board {
|
||||
grid-area: player;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.damage_button:disabled {
|
||||
background-color: rgba(255, 77, 77, 0.5);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.damage_button:hover {
|
||||
background-color: #e60000;
|
||||
}
|
||||
|
||||
.damage_button:disabled:hover {
|
||||
background-color: rgba(230, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.damage_button:active {
|
||||
transform: translateY(2px);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.all_damage_button {
|
||||
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);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.make_discard_button:hover {
|
||||
background-color: #03883a;
|
||||
}
|
||||
|
||||
.make_discard_button:disabled:hover {
|
||||
background-color: rgba(3, 136, 58, 0.5);
|
||||
}
|
||||
|
||||
.make_discard_button:active {
|
||||
transform: translateY(2px);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.turn_icon {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#player_info {
|
||||
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, 0.6));
|
||||
backdrop-filter: blur(5px) brightness(80%) grayscale(80%);
|
||||
}
|
||||
|
||||
#player_banner img {
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.turn_icon img {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.turn_icon_number {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.end_turn_button:active {
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { Transition } from "vue";
|
||||
import { playSound } from "~/helpers/audioHelpers";
|
||||
import type { Game, Player } from "~/netcode/interfaces";
|
||||
|
||||
export interface Props {
|
||||
game: Game;
|
||||
player: Player;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const team = computed(() =>
|
||||
props.game.teams.find((t) =>
|
||||
t.players.map((p) => p._id).includes(props.player._id)
|
||||
)
|
||||
);
|
||||
|
||||
const isTurn = computed(
|
||||
() => team.value && team.value?._id == props.game.currentTurn.currentTeam
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="turn_icon">
|
||||
<img src="/img/champion-shield.png" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">
|
||||
{{ team?.health }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="turn_icon">
|
||||
<img src="/img/fuck.svg" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">{{
|
||||
(player.discardMarkers ?? 0) + (player.fuckUMarkers ?? 0)
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="turn_icon" v-show="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-show="isTurn">
|
||||
<img src="/img/damage.png" class="icon_img" draggable="false" />
|
||||
<span class="turn_icon_number">
|
||||
{{ game.currentTurn.damage }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.turn_icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
img.icon_img {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
span.icon_img {
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.turn_icon_number {
|
||||
position: absolute;
|
||||
text-shadow: white 0 0 1px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useAuthStore } from "#imports";
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
const authStore = useAuthStore();
|
||||
const config = useRuntimeConfig();
|
||||
// if (to.path == '/') {
|
||||
// const code = to.query.code
|
||||
// if (code) {
|
||||
// if (!Array.isArray(code)) {
|
||||
// try {
|
||||
// await oauth2_register_discord(code)
|
||||
// return navigateTo('/lobby')
|
||||
// } catch {
|
||||
// return navigateTo('/login')
|
||||
// }
|
||||
// } else {
|
||||
// return navigateTo('/login')
|
||||
// }
|
||||
// }
|
||||
// return navigateTo("/lobby")
|
||||
// }
|
||||
|
||||
// if (!goStore.discordId) {
|
||||
// const heron_session = useCookie('heron_session')
|
||||
// if (!heron_session.value) { return navigateTo('/login') }
|
||||
|
||||
// try {
|
||||
// await heron_cookie_login(heron_session.value)
|
||||
// if (to.path == '/') {
|
||||
// return navigateTo("/lobby")
|
||||
// }
|
||||
// } catch {
|
||||
// return navigateTo('/login')
|
||||
// }
|
||||
// }
|
||||
|
||||
if (to.path.startsWith("/game")) {
|
||||
return;
|
||||
}
|
||||
if (to.path == "/login") {
|
||||
return;
|
||||
}
|
||||
if (to.path == "/lobby") {
|
||||
return;
|
||||
}
|
||||
if (to.path == "/rules") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (to.path == "/test") {
|
||||
return;
|
||||
}
|
||||
// In a real app you would probably not redirect every route to `/`
|
||||
// however it is important to check `to.path` before redirecting or you
|
||||
// might get an infinite redirect loop
|
||||
if (to.path !== "/") {
|
||||
return navigateTo("/");
|
||||
}
|
||||
return navigateTo("/lobby");
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
import type { GameObjectRegister } from "."
|
||||
import type { Effect } from "./Effect"
|
||||
import type { GameObjectNetcode } from "./GameObject"
|
||||
import type { Turn } from "./Turn"
|
||||
|
||||
enum CardRole {
|
||||
PERSONAL = "personal",
|
||||
MARKET = "market",
|
||||
FIRE_GEM = "fire_gem"
|
||||
}
|
||||
|
||||
export enum CardType {
|
||||
HERO = "hero",
|
||||
HERO_ABILITY = "hero_ability",
|
||||
CURRENCY = "currency",
|
||||
WEAPON = "weapon",
|
||||
CHAMPION = "champion",
|
||||
ACTION = "action",
|
||||
ITEM = "item"
|
||||
}
|
||||
|
||||
enum CardFaction {
|
||||
BASE = "base",
|
||||
|
||||
BLUE = "blue",
|
||||
RED = "red",
|
||||
GREEN = "green",
|
||||
YELLOW = "yellow"
|
||||
}
|
||||
|
||||
export interface CardNetcode extends GameObjectNetcode {
|
||||
card_id: number
|
||||
sprite: string
|
||||
name: string
|
||||
cost?: number
|
||||
role: CardRole
|
||||
card_type: CardType
|
||||
faction: CardFaction
|
||||
effects: Array<string>
|
||||
defense?: number
|
||||
guard?: boolean
|
||||
}
|
||||
|
||||
export type Card = Omit<CardNetcode, 'effects'> & {
|
||||
effects: Array<Effect>
|
||||
health_as_champion?: number
|
||||
}
|
||||
export function create_card(input: CardNetcode, register: GameObjectRegister) {
|
||||
const card = new Proxy<any>(input, {
|
||||
get(target: CardNetcode, prop, reciever) {
|
||||
if (prop == "effects") {
|
||||
return target.effects.map((e) => register.effects.get(e))
|
||||
}
|
||||
if (prop == "health_as_champion") {
|
||||
return Array.from(register.turns.values()).find(e => e.champions_health.has(card.uuid))?.champions_health.get(card.uuid)
|
||||
}
|
||||
return Reflect.get(target, prop, reciever);
|
||||
},
|
||||
set(target, prop, val, receiver) {
|
||||
return Reflect.set(target, prop, val, receiver);
|
||||
}
|
||||
}) as Card
|
||||
return card
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import { GameObjectRegister } from "."
|
||||
import { CardType, type Card } from "./Card"
|
||||
import type { GameObjectNetcode } from "./GameObject"
|
||||
|
||||
export enum CardEffects {
|
||||
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"
|
||||
}
|
||||
|
||||
export enum EffectTimes {
|
||||
PER_CHAMPION = "per_champion",
|
||||
PER_OTHER_CHAMPION = "per_other_champion",
|
||||
PER_OTHER_GUARD = "per_other_guard",
|
||||
PER_CARD_OF_SAME_FACTION = "per_card_of_same_faction",
|
||||
PER_OTHER_CARD_OF_SAME_FACTION = "per_other_card_of_same_faction"
|
||||
}
|
||||
|
||||
export enum EffectType {
|
||||
CHAMPION_ACTION = "champion_action",
|
||||
SUICIDE = "suicide",
|
||||
FACTION_COMBO = "faction_combo"
|
||||
}
|
||||
|
||||
export interface EffectNetcode extends GameObjectNetcode {
|
||||
effect: CardEffects
|
||||
amount: number
|
||||
sub_effects: Array<EffectNetcode>
|
||||
mutex: Array<string>
|
||||
card: string
|
||||
effect_type?: EffectType
|
||||
times?: EffectTimes
|
||||
}
|
||||
|
||||
export type Effect = Omit<EffectNetcode, 'sub_effects' | 'mutex' | 'card'> & {
|
||||
sub_effects: { [key: string]: Effect }
|
||||
mutex: { [key: string]: Effect }
|
||||
card: Card
|
||||
usable: boolean
|
||||
}
|
||||
|
||||
export function create_effect(input: EffectNetcode, register: GameObjectRegister) {
|
||||
const effect =
|
||||
new Proxy<any>(input, {
|
||||
get(target: EffectNetcode, prop, reciever) {
|
||||
if (prop == "sub_effects") {
|
||||
return Object.fromEntries(Object.entries(register.effects).filter(([i, v]) => i in target.sub_effects))
|
||||
}
|
||||
if (prop == "mutex") {
|
||||
return Object.fromEntries(Object.entries(register.effects).filter(([i, v]) => i in target.mutex))
|
||||
}
|
||||
if (prop == "usable") {
|
||||
// ugly bastard
|
||||
if (!register.self) { return false }
|
||||
|
||||
let cardLocked = register.self.value?.turn?.cards_locked
|
||||
if (!cardLocked) { return false }
|
||||
let cardList = Object.values(cardLocked)
|
||||
const card = cardList.find(c => c.effects.map(e => e.uuid).includes(effect.uuid))
|
||||
if (!card) { return false }
|
||||
|
||||
if (target.effect_type === EffectType.FACTION_COMBO) {
|
||||
if (!cardList.some(c => c !== card && c.faction === card.faction)) { return false }
|
||||
}
|
||||
if (target.times === EffectTimes.PER_OTHER_CARD_OF_SAME_FACTION) {
|
||||
if (!cardList.some(c => c !== card && c.faction === card.faction)) { return false }
|
||||
} else if (target.times === EffectTimes.PER_OTHER_CHAMPION) {
|
||||
if (!cardList.some(c => c !== card && c.card_type === CardType.CHAMPION)) { return false }
|
||||
} else if (target.times === EffectTimes.PER_OTHER_GUARD) {
|
||||
if (!cardList.some(c => c !== card && c.guard)) { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (prop == "card") {
|
||||
return register.cards.get(target.card)
|
||||
}
|
||||
return Reflect.get(target, prop, reciever);
|
||||
},
|
||||
set(target, prop, val, receiver) {
|
||||
return Reflect.set(target, prop, val, receiver);
|
||||
}
|
||||
}) as Effect
|
||||
return effect
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { type GameObjectRegister } from "."
|
||||
import { type Team } from "./Team"
|
||||
import { type GameObjectNetcode } from "./GameObject"
|
||||
import type { Card } from "./Card"
|
||||
import type { Player } from "./Player"
|
||||
|
||||
export interface GameNetcode extends GameObjectNetcode {
|
||||
market_stack: Array<string>
|
||||
market: Array<string>
|
||||
gem_stack: Array<string>
|
||||
started: boolean
|
||||
current_turn: string
|
||||
players: Array<string>
|
||||
losers: Array<string>
|
||||
ended: boolean
|
||||
owner: string
|
||||
}
|
||||
|
||||
|
||||
export type Game = Omit<GameNetcode, 'market_stack' | 'market' | 'gem_stack' | 'current_turn' | 'players'> & {
|
||||
market_stack: Array<Card>
|
||||
market: Array<Card | null>
|
||||
gem_stack: Array<Card>
|
||||
current_turn: Team
|
||||
players: Array<Player>
|
||||
}
|
||||
export function create_game(input: GameNetcode, register: GameObjectRegister) {
|
||||
return new Proxy<any>(input, {
|
||||
get(target: GameNetcode, prop, reciever) {
|
||||
if (prop == "market_stack") {
|
||||
return target.market_stack.map(e => register.cards.get(e))
|
||||
}
|
||||
if (prop == "market") {
|
||||
// console.log(target)
|
||||
return target.market.map(e => register.cards.get(e))
|
||||
}
|
||||
if (prop == "gem_stack") {
|
||||
return target.gem_stack.map(e => register.cards.get(e))
|
||||
}
|
||||
if (prop == "current_turn") {
|
||||
return register.teams.get(input.current_turn)
|
||||
}
|
||||
if (prop == "players") {
|
||||
return target.players.map(e => register.players.get(e))
|
||||
}
|
||||
return Reflect.get(target, prop, reciever);
|
||||
},
|
||||
set(target, prop, val, receiver) {
|
||||
return Reflect.set(target, prop, val, receiver);
|
||||
}
|
||||
}) as Game
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export type GameObjectNetcode = {
|
||||
uuid: string
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { type GameObjectRegister } from "."
|
||||
import { type GameObjectNetcode } from "./GameObject"
|
||||
import type { Game } from "./Game"
|
||||
|
||||
export interface LobbyNetcode extends GameObjectNetcode {
|
||||
games: Array<string>
|
||||
}
|
||||
|
||||
|
||||
export type Lobby = Omit<LobbyNetcode, 'games'> & {
|
||||
games: Array<Game>
|
||||
}
|
||||
export function create_lobby(input: LobbyNetcode, register: GameObjectRegister) {
|
||||
return new Proxy<any>(input, {
|
||||
get(target: LobbyNetcode, prop, reciever) {
|
||||
if (prop == "games") {
|
||||
return target.games.map(e => register.games.get(e))
|
||||
}
|
||||
return Reflect.get(target, prop, reciever);
|
||||
},
|
||||
set(target, prop, val, receiver) {
|
||||
return Reflect.set(target, prop, val, receiver);
|
||||
}
|
||||
}) as Lobby
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { type GameObjectRegister } from "."
|
||||
import type { Card } from "./Card"
|
||||
import type { GameObjectNetcode } from "./GameObject"
|
||||
import { type Team } from "./Team"
|
||||
import { type Turn } from "./Turn"
|
||||
export interface PlayerNetcode extends GameObjectNetcode {
|
||||
stack_pile: Array<string>
|
||||
discard_pile: Array<string>
|
||||
hand: Array<string>
|
||||
board: Array<string>
|
||||
team: string
|
||||
username: string
|
||||
turn: string
|
||||
discord_id?: string
|
||||
image?: string
|
||||
ready: boolean
|
||||
}
|
||||
|
||||
|
||||
export type Player = Omit<PlayerNetcode, 'discard_pile' | 'stack_pile' | 'hand' | 'board' | 'team' | 'turn'> & {
|
||||
stack_pile: Array<Card | null>
|
||||
discard_pile: Array<Card>
|
||||
hand: Array<Card | null>
|
||||
board: Array<Card>
|
||||
team: Team | undefined
|
||||
turn: Turn | undefined
|
||||
}
|
||||
export function create_player(input: PlayerNetcode, register: GameObjectRegister) {
|
||||
return new Proxy<any>(input, {
|
||||
get(target: PlayerNetcode, prop, reciever) {
|
||||
if (prop == "stack_pile") {
|
||||
return target.stack_pile.map(e => register.cards.get(e))
|
||||
}
|
||||
if (prop == "discard_pile") {
|
||||
return target.discard_pile.map(e => register.cards.get(e))
|
||||
}
|
||||
if (prop == "hand") {
|
||||
return target.hand.map(e => register.cards.get(e))
|
||||
}
|
||||
if (prop == "board") {
|
||||
return target.board.map(e => register.cards.get(e))
|
||||
}
|
||||
if (prop == "team") {
|
||||
return register.teams.get(target.team)
|
||||
}
|
||||
if (prop == "turn") {
|
||||
return register.turns.get(target.turn)
|
||||
}
|
||||
return Reflect.get(target, prop, reciever);
|
||||
},
|
||||
set(target, prop, val, receiver) {
|
||||
return Reflect.set(target, prop, val, receiver);
|
||||
}
|
||||
}) as Player
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { type GameObjectRegister } from "."
|
||||
import type { GameObjectNetcode } from "./GameObject"
|
||||
import { type Player } from "./Player"
|
||||
|
||||
export interface TeamNetcode extends GameObjectNetcode {
|
||||
health: number
|
||||
color: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type Team = TeamNetcode & {
|
||||
players: Array<Player>
|
||||
}
|
||||
|
||||
export function create_team(input: TeamNetcode, register: GameObjectRegister) {
|
||||
const team = new Proxy<any>(input, {
|
||||
get(target: TeamNetcode, prop, reciever) {
|
||||
if (prop == "players") {
|
||||
return Object.values(register.players).filter((p) => p.team == team)
|
||||
}
|
||||
return Reflect.get(target, prop, reciever);
|
||||
},
|
||||
set(target, prop, val, receiver) {
|
||||
return Reflect.set(target, prop, val, receiver);
|
||||
}
|
||||
}) as Team
|
||||
|
||||
return team
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { registerRuntimeCompiler } from "vue"
|
||||
import { type GameObjectRegister } from "."
|
||||
import type { Card } from "./Card"
|
||||
import { type Effect, CardEffects } from "./Effect"
|
||||
import type { GameObjectNetcode } from "./GameObject"
|
||||
export interface TurnNetcode extends GameObjectNetcode {
|
||||
cards_locked: Array<string>
|
||||
effects_availables: Array<string>
|
||||
champions_health: { [key: string]: string }
|
||||
discard_markers: number
|
||||
discard_marker_card: string
|
||||
fuck_u_markers: number
|
||||
damage_reserve: number
|
||||
gold_reserve: number
|
||||
}
|
||||
|
||||
|
||||
export type Turn = Omit<TurnNetcode, 'cards_locked' | 'effects_availables' | 'champions_health'|"discard_marker_card"> & {
|
||||
cards_locked: { [key: string]: Card }
|
||||
effects_availables: Array<Effect>
|
||||
champions_health: Map<string, number>
|
||||
discard_marker_card: Card
|
||||
}
|
||||
export function create_turn(input: TurnNetcode, register: GameObjectRegister) {
|
||||
const turn = new Proxy<any>(input, {
|
||||
get(target: TurnNetcode, prop, reciever) {
|
||||
if (prop == "cards_locked") {
|
||||
return Object.fromEntries(target.cards_locked.map(e => [e, register.cards.get(e)]))
|
||||
}
|
||||
if (prop == "effects_availables") {
|
||||
return target.effects_availables.map(e => register.effects.get(e))
|
||||
}
|
||||
if (prop == "champions_health") {
|
||||
const healths = new Map()
|
||||
for (const c in target.champions_health) {
|
||||
healths.set(register.cards.get(c)?.uuid, target.champions_health[c])
|
||||
}
|
||||
return healths
|
||||
}
|
||||
if(prop == "discard_marker_card"){
|
||||
return register.cards.get(target.discard_marker_card)
|
||||
}
|
||||
return Reflect.get(target, prop, reciever);
|
||||
},
|
||||
set(target, prop, val, receiver) {
|
||||
return Reflect.set(target, prop, val, receiver);
|
||||
}
|
||||
}) as Turn
|
||||
return turn
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import { playSound } from "~/helpers/audioHelpers";
|
||||
import type { Container, Game, Player } from "./interfaces";
|
||||
|
||||
export function delay(delay: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, delay);
|
||||
});
|
||||
}
|
||||
|
||||
type handlerInput = {
|
||||
game: Game;
|
||||
containers: Ref<Record<string, Container>>;
|
||||
settingsStore: ReturnType<typeof useSettingsStore>;
|
||||
playerList: Ref<Array<Player>>;
|
||||
refresh: () => Promise<void>;
|
||||
focusPlayer: (player: Player) => void;
|
||||
};
|
||||
export const handlers: {
|
||||
[key: string]: (data: any, input: handlerInput) => Promise<void>;
|
||||
} = {
|
||||
start: async (data: any, { game }) => {
|
||||
game.started = true;
|
||||
game.currentTurn = data.game.currentTurn;
|
||||
},
|
||||
populateContainer: async (data: any, { containers }) => {
|
||||
const fromContainer = containers.value[data.container._id as string];
|
||||
playSound("/sounds/distribute.mp3");
|
||||
for (const card of data.container.cards) {
|
||||
fromContainer.cards.push(card);
|
||||
// if (!settingsStore.fastAnimations) {
|
||||
// await delay(settingsStore.animationSpeed);
|
||||
// }
|
||||
}
|
||||
},
|
||||
shuffleContainer: async (data: any, { containers, settingsStore }) => {
|
||||
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);
|
||||
},
|
||||
distributeCards: async (data: any, { containers, settingsStore }) => {
|
||||
const fromContainer = containers.value[data.from as string];
|
||||
const toContainer = containers.value[data.to as string];
|
||||
if (settingsStore.fastAnimations) {
|
||||
playSound("/sounds/distribute.mp3");
|
||||
}
|
||||
for (const card of data.cards) {
|
||||
const index = fromContainer.cards.findIndex(
|
||||
(c) => !c || c._id == card._id
|
||||
);
|
||||
fromContainer.cards.splice(index, 1);
|
||||
if (!settingsStore.fastAnimations) {
|
||||
await delay(settingsStore.animationSpeed);
|
||||
playSound("/sounds/distribute.mp3");
|
||||
}
|
||||
toContainer.cards.push(card);
|
||||
if (!settingsStore.fastAnimations) {
|
||||
await delay(settingsStore.animationSpeed);
|
||||
}
|
||||
}
|
||||
await delay(settingsStore.animationSpeed * 2);
|
||||
},
|
||||
endTurn: async (data: any, { game, settingsStore, focusPlayer }) => {
|
||||
game.currentTurn = data.currentTurn;
|
||||
await delay(settingsStore.animationSpeed);
|
||||
const playingPlayer = game.teams.find(
|
||||
(t) => t._id == game.currentTurn.currentTeam
|
||||
)?.players[0];
|
||||
if (playingPlayer) {
|
||||
focusPlayer(playingPlayer);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
}
|
||||
},
|
||||
lockCard: async (data: any, { game, settingsStore }) => {
|
||||
playSound("/sounds/lock.mp3");
|
||||
game.currentTurn.cardsLocked.push(data.card);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
},
|
||||
destroyCard: async (data: any, { containers, settingsStore }) => {
|
||||
const fromContainer = containers.value[data.from as string];
|
||||
const index = fromContainer.cards.findIndex(
|
||||
(c) => !c || c._id == data.card
|
||||
);
|
||||
fromContainer.cards.splice(index, 1);
|
||||
playSound("/sounds/destroy.mp3");
|
||||
await delay(settingsStore.animationSpeed);
|
||||
},
|
||||
joinGame: async (data: any, { game }) => {
|
||||
playSound("/sounds/teleport.mp3");
|
||||
game.teams.push(data.team);
|
||||
},
|
||||
leaveTeam: async (data: any, { game }) => {
|
||||
const team = game.teams.find((t) => t._id == data.team._id);
|
||||
if (team == undefined) {
|
||||
throw new Error("Cannot find team");
|
||||
}
|
||||
const playerIndex = team.players.findIndex((p) => p._id == data.playerId);
|
||||
if (playerIndex == -1) {
|
||||
throw new Error("Cannot find player in team");
|
||||
}
|
||||
team.players.splice(playerIndex, 1);
|
||||
if (team.players.length === 0) {
|
||||
const index = game.teams.findIndex((t) => t._id == data.team._id);
|
||||
game.teams.splice(index, 1);
|
||||
}
|
||||
},
|
||||
lockEffect: async (data: any, { game, settingsStore }) => {
|
||||
game.currentTurn.lockedEffects.push(data.effect);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
},
|
||||
unlockEffect: async (data: any, { game, settingsStore }) => {
|
||||
const effectIndex = game.currentTurn.lockedEffects.indexOf(data.effect);
|
||||
game.currentTurn.lockedEffects.splice(effectIndex, 1);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
},
|
||||
"currentTurn.updateGold": async (data: any, { game }) => {
|
||||
console.log(`adding gold amount : ` + data.operation);
|
||||
game.currentTurn.gold = data.gold;
|
||||
},
|
||||
"currentTurn.updateDamage": async (data: any, { game }) => {
|
||||
console.log(`adding damage amount : ` + data.operation);
|
||||
game.currentTurn.damage = data.damage;
|
||||
},
|
||||
"currentTurn.addToken": async (data: any, { game, settingsStore }) => {
|
||||
game.currentTurn.tokens.push(data.token);
|
||||
await delay(settingsStore.animationSpeed);
|
||||
},
|
||||
"currentTurn.useToken": async (data: any, { game, settingsStore }) => {
|
||||
const index = game.currentTurn.tokens.findIndex(
|
||||
(t) => t._id == data.tokenId
|
||||
);
|
||||
game.currentTurn.tokens.splice(index, 1);
|
||||
if (!settingsStore.fastAnimations) {
|
||||
await delay(settingsStore.animationSpeed);
|
||||
}
|
||||
},
|
||||
"team.updateHealth": async (data: any, { game }) => {
|
||||
const team = game.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;
|
||||
},
|
||||
"player.addFuckUMarker": async (data: any, { playerList }) => {
|
||||
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;
|
||||
},
|
||||
"player.removeFuckUMarker": async (data: any, { playerList }) => {
|
||||
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;
|
||||
},
|
||||
"player.addDiscardMarker": async (data: any, { playerList }) => {
|
||||
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;
|
||||
},
|
||||
"player.removeDiscardMarker": async (data: any, { playerList }) => {
|
||||
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;
|
||||
},
|
||||
killTeam: async (data: any, { game, refresh }) => {
|
||||
const index = game.teams.findIndex((t) => t._id == data.teamId);
|
||||
if (!index) {
|
||||
console.error("team index not found. Desyncs might happen");
|
||||
await refresh();
|
||||
return;
|
||||
}
|
||||
game.teams.splice(index, 1);
|
||||
},
|
||||
consumeEffect: async (data: any) => {
|
||||
console.warn("consumeEffect not implemented");
|
||||
},
|
||||
endGame: async () => {
|
||||
alert("game ended");
|
||||
},
|
||||
delete: async () => {
|
||||
alert("game deleted");
|
||||
},
|
||||
};
|
||||
+194
-342
@@ -1,375 +1,227 @@
|
||||
import { type Card, create_card, type CardNetcode } from "./Card";
|
||||
import {
|
||||
type Effect,
|
||||
create_effect,
|
||||
type EffectNetcode,
|
||||
EffectType,
|
||||
CardEffects,
|
||||
} from "./Effect";
|
||||
import { type Game, create_game, type GameNetcode } from "./Game";
|
||||
import type { GameObjectNetcode } from "./GameObject";
|
||||
import { type Player, create_player, type PlayerNetcode } from "./Player";
|
||||
import { type Team, create_team, type TeamNetcode } from "./Team";
|
||||
import { type Turn, create_turn } from "./Turn";
|
||||
import { type Lobby, create_lobby, type LobbyNetcode } from "./Lobby";
|
||||
import type { Game, User } from "./interfaces";
|
||||
|
||||
import { type Ref } from "vue";
|
||||
|
||||
let socket: WebSocket;
|
||||
|
||||
export class GameObjectRegister {
|
||||
readonly effects: Map<string, Effect>;
|
||||
readonly cards: Map<string, Card>;
|
||||
readonly teams: Map<string, Team>;
|
||||
readonly players: Map<string, Player>;
|
||||
readonly turns: Map<string, Turn>;
|
||||
readonly games: Map<string, Game>;
|
||||
|
||||
context: "lobby" | "login" | "pregame" | "game" | "rules" = "login";
|
||||
private readonly objects: Map<string, GameObjectNetcode>;
|
||||
current_game: Ref<Game | null>;
|
||||
lobby: Ref<Lobby | null>;
|
||||
self: Ref<Player | null>;
|
||||
zoomKeyPressed: Ref<boolean>
|
||||
hoveredCard: Ref<number | undefined>
|
||||
|
||||
clientMouseX: Ref<number>
|
||||
clientMouseY: Ref<number>
|
||||
discordId: Ref<string>
|
||||
constructor() {
|
||||
this.effects = reactive(new Map());
|
||||
this.cards = reactive(new Map());
|
||||
this.teams = reactive(new Map());
|
||||
this.players = reactive(new Map());
|
||||
this.turns = reactive(new Map());
|
||||
this.games = reactive(new Map());
|
||||
|
||||
this.objects = reactive(new Map());
|
||||
this.current_game = ref(null);
|
||||
this.lobby = ref(null);
|
||||
this.self = ref(null);
|
||||
this.zoomKeyPressed = ref(false);
|
||||
this.hoveredCard = ref()
|
||||
this.clientMouseX = ref(0)
|
||||
this.clientMouseY = ref(0)
|
||||
this.discordId = ref("")
|
||||
}
|
||||
|
||||
update_object(obj: GameObjectNetcode) {
|
||||
// console.log(obj);
|
||||
// console.log(obj.uuid);
|
||||
// console.log(this.objects.get(obj.uuid));
|
||||
Object.assign(this.objects.get(obj.uuid) as GameNetcode, obj);
|
||||
if (unref(this.current_game)?.uuid === obj.uuid) {
|
||||
triggerRef(this.current_game);
|
||||
} else if (unref(this.lobby)?.uuid === obj.uuid) {
|
||||
triggerRef(this.lobby);
|
||||
} else if (unref(this.self)?.uuid === obj.uuid) {
|
||||
triggerRef(this.self);
|
||||
} else if (unref(this.self)?.turn?.uuid === obj.uuid) {
|
||||
triggerRef(this.self);
|
||||
}
|
||||
}
|
||||
|
||||
createObject = {
|
||||
effect: (data: EffectNetcode) => {
|
||||
const effect = create_effect(data, this);
|
||||
this.effects.set(data.uuid, effect);
|
||||
this.objects.set(data.uuid, data);
|
||||
},
|
||||
card: (data: CardNetcode) => {
|
||||
const card = create_card(data, this);
|
||||
this.cards.set(data.uuid, card);
|
||||
this.objects.set(data.uuid, card);
|
||||
},
|
||||
player: (data: PlayerNetcode) => {
|
||||
const player = create_player(data, this);
|
||||
this.players.set(data.uuid, player);
|
||||
this.objects.set(data.uuid, player);
|
||||
},
|
||||
team: (data: TeamNetcode) => {
|
||||
const team = create_team(data, this);
|
||||
this.teams.set(data.uuid, team);
|
||||
this.objects.set(data.uuid, team);
|
||||
},
|
||||
game: (data: GameNetcode) => {
|
||||
const game = create_game(data, this);
|
||||
this.games.set(data.uuid, game);
|
||||
this.objects.set(data.uuid, game);
|
||||
},
|
||||
turn: (data: any) => {
|
||||
const turn = create_turn(data, this);
|
||||
this.turns.set(data.uuid, turn);
|
||||
this.objects.set(data.uuid, turn);
|
||||
},
|
||||
lobby: (data: LobbyNetcode) => {
|
||||
this.lobby.value = create_lobby(data, this);
|
||||
this.objects.set(data.uuid, this.lobby.value);
|
||||
},
|
||||
} as { [id: string]: (data: any) => any };
|
||||
|
||||
setSelf = (uuid: string) => {
|
||||
this.self.value = this.players.get(uuid) ?? null;
|
||||
};
|
||||
|
||||
setDiscordId = (username: string) => {
|
||||
this.discordId.value = username
|
||||
}
|
||||
setCurrentGame = (uuid: string) => {
|
||||
this.current_game.value = this.games.get(uuid) ?? null;
|
||||
};
|
||||
}
|
||||
|
||||
const goStore = reactive(new GameObjectRegister());
|
||||
|
||||
export const setup_websocket = () =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
if (socket) {
|
||||
return resolve();
|
||||
}
|
||||
const config = useRuntimeConfig();
|
||||
const heron_session = useCookie("heron_session");
|
||||
socket = new WebSocket(config.public.websocketEndpoint);
|
||||
|
||||
socket.onopen = function (e) {
|
||||
if (socket.readyState == 1) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
console.log("[open] Connection established");
|
||||
};
|
||||
|
||||
socket.onclose = function (event) {
|
||||
if (event.wasClean) {
|
||||
console.warn(
|
||||
`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`
|
||||
);
|
||||
} else {
|
||||
// e.g. server process killed or network down
|
||||
// event.code is usually 1006 in this case
|
||||
console.error("[close] Connection died");
|
||||
}
|
||||
};
|
||||
|
||||
socket.onerror = function (error) {
|
||||
console.error(error);
|
||||
};
|
||||
|
||||
socket.onmessage = function (event) {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type == "create" && data.data_type == "object") {
|
||||
if (!(data.data.object_type in goStore.createObject)) {
|
||||
throw new Error(
|
||||
`Object ${data.data.object_type} couldn't be created`
|
||||
);
|
||||
}
|
||||
goStore.createObject[data.data.object_type](data.data);
|
||||
} else if (data.type == "update" && data.data_type == "object") {
|
||||
goStore.update_object(data.data);
|
||||
} else if (data.type === "context") {
|
||||
goStore.context = data.data;
|
||||
// console.log(data.data);
|
||||
} else if (data.type === "set") {
|
||||
if (data.data_type == "self") {
|
||||
goStore.setSelf(data.data);
|
||||
} else if (data.data_type == "current_game") {
|
||||
goStore.setCurrentGame(data.data);
|
||||
} else if (data.data_type == "session_cookie") {
|
||||
heron_session.value = data.data;
|
||||
} else if (data.data_type == "self_discord_id") {
|
||||
goStore.setDiscordId(data.data)
|
||||
}
|
||||
} else {
|
||||
// console.log(data.type, data.data_type, data.data);
|
||||
}
|
||||
};
|
||||
export function subscribe(endpoint: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const evtSource = new EventSource(config.public.endpoint + endpoint, {
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
export async function heron_cookie_login(cookie: string) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "cookie_login",
|
||||
data_type: "cookie",
|
||||
data: cookie,
|
||||
})
|
||||
return evtSource;
|
||||
}
|
||||
|
||||
export async function whoami() {
|
||||
const config = useRuntimeConfig();
|
||||
const req = await useFetch<User>(config.public.endpoint + "/whoami", {
|
||||
credentials: "include",
|
||||
server: false,
|
||||
});
|
||||
await req.execute();
|
||||
if (req.error.value && req.error.value.statusCode == 401) {
|
||||
// await navigateTo(config.public.discordLoginEndpoint, {
|
||||
// external: true,
|
||||
// });
|
||||
throw new Error("You are not logged in");
|
||||
}
|
||||
if (req.error.value) {
|
||||
throw new Error(
|
||||
"Something went wrong while trying to get user information"
|
||||
);
|
||||
}
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function createGame() {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(config.public.endpoint + "/games", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
navigateTo(`/game?id=${res._id}`);
|
||||
}
|
||||
|
||||
export async function startGame(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(
|
||||
config.public.endpoint + `/games/${game}/start`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function oauth2_register_discord(code: string) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "discord_login",
|
||||
data_type: "code",
|
||||
data: code,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function register(username: string) {
|
||||
await setup_websocket();
|
||||
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "register",
|
||||
data_type: "username",
|
||||
data: username,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function login(username: string) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "login",
|
||||
data_type: "username",
|
||||
data: username,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function setReady(value: boolean) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "ready",
|
||||
data_type: "ready_state",
|
||||
data: value,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function buy(card: Card) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "action",
|
||||
data_type: "buy",
|
||||
data: card.uuid,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function endTurn() {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "action",
|
||||
data_type: "end_turn",
|
||||
data: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function playAllCards(cards: Card[]) {
|
||||
cards.forEach((c: any) => {
|
||||
playCard(c);
|
||||
export async function deleteGame(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(config.public.endpoint + `/games/${game}`, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
|
||||
export async function playCard(card: Card) {
|
||||
// console.log(card.uuid);
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "action",
|
||||
data_type: "play_card",
|
||||
data: card.uuid,
|
||||
})
|
||||
export async function joinGame(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(
|
||||
config.public.endpoint + `/games/${game}/join`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function lockCard(card: Card) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "action",
|
||||
data_type: "lock_card",
|
||||
data: card.uuid,
|
||||
})
|
||||
export async function leaveGame(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(
|
||||
config.public.endpoint + `/games/${game}/leave`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function useEffect(
|
||||
effect: Effect,
|
||||
target?: Effect | Card | Player | Team
|
||||
export async function surrender(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(
|
||||
config.public.endpoint + `/games/${game}/surrender`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function kickPlayer(game: string, playerId: string) {
|
||||
const config = useRuntimeConfig();
|
||||
const res = await $fetch<Game>(
|
||||
config.public.endpoint + `/games/${game}/kick/${playerId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function endTurn(game: string) {
|
||||
const config = useRuntimeConfig();
|
||||
await $fetch(config.public.endpoint + `/games/${game}/endTurn`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
|
||||
export async function lockCard(game: string, cardId: string) {
|
||||
const config = useRuntimeConfig();
|
||||
await $fetch(
|
||||
config.public.endpoint + `/games/${game}/turn/lockCard/${cardId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function buyCard(game: string, cardId: string) {
|
||||
const config = useRuntimeConfig();
|
||||
await $fetch(
|
||||
config.public.endpoint + `/games/${game}/turn/buyCard/${cardId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "action",
|
||||
data_type: "use_effect",
|
||||
data: { effect_id: effect.uuid, target: target?.uuid },
|
||||
})
|
||||
const config = useRuntimeConfig();
|
||||
await $fetch(
|
||||
config.public.endpoint + `/games/${game}/turn/useToken/${tokenId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: { cardId, playerId },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function discard(card: Card) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "action",
|
||||
data_type: "discard",
|
||||
data: card.uuid,
|
||||
})
|
||||
);
|
||||
}
|
||||
h;
|
||||
export async function heal() {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "action",
|
||||
data_type: "heal",
|
||||
data: 1,
|
||||
})
|
||||
export async function lockEffect(game: string, effectId: string) {
|
||||
const config = useRuntimeConfig();
|
||||
await $fetch(
|
||||
config.public.endpoint + `/games/${game}/turn/lockEffect/${effectId}`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function damage_team(target: Team) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "action",
|
||||
data_type: "damage_team",
|
||||
data: target.uuid,
|
||||
})
|
||||
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 damage_champion(target: Card) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "action",
|
||||
data_type: "damage_champion",
|
||||
data: target.uuid,
|
||||
})
|
||||
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 netcode_create_game() {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "create",
|
||||
data_type: "game",
|
||||
data: "bite",
|
||||
})
|
||||
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 join_game(game_uuid: string) {
|
||||
await setup_websocket();
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "join",
|
||||
data_type: "game",
|
||||
data: game_uuid,
|
||||
})
|
||||
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",
|
||||
}
|
||||
);
|
||||
}
|
||||
export default goStore;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
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;
|
||||
}
|
||||
|
||||
export interface PlayingTurn {
|
||||
currentTeam: string;
|
||||
cardsLocked: string[];
|
||||
lockedEffects: string[];
|
||||
tokens: Effect[];
|
||||
damage: number;
|
||||
gold: number;
|
||||
}
|
||||
|
||||
export interface Game extends GameObject {
|
||||
fireGems: Container;
|
||||
market: Container;
|
||||
marketStack: Container;
|
||||
teams: Team[];
|
||||
currentTurn: PlayingTurn;
|
||||
started: boolean;
|
||||
owner: User;
|
||||
packs: string[];
|
||||
}
|
||||
|
||||
export interface Team extends GameObject {
|
||||
players: Player[];
|
||||
health: number;
|
||||
}
|
||||
|
||||
export interface Player extends GameObject {
|
||||
board: Container;
|
||||
discard: Container;
|
||||
hand: Container;
|
||||
stack: Container;
|
||||
user: User;
|
||||
discardMarkers: number;
|
||||
fuckUMarkers: number;
|
||||
}
|
||||
|
||||
export interface User extends GameObject {
|
||||
userId: string;
|
||||
avatar: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface Container extends GameObject {
|
||||
cards: Card[];
|
||||
shuffling?: boolean;
|
||||
}
|
||||
|
||||
export interface Effect extends GameObject {
|
||||
effect: CardEffects;
|
||||
}
|
||||
export interface Card extends GameObject {
|
||||
cardId: number;
|
||||
cardType: CardType;
|
||||
effects: Effect[];
|
||||
defense?: number;
|
||||
guard?: boolean;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
#access_log /var/log/nginx/host.access.log main;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
|
||||
error_page 404 /usr/share/nginx/html/404.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
# error_page 500 502 503 504 /50x.html;
|
||||
# location = /50x.html {
|
||||
# root /usr/share/nginx/html;
|
||||
# }
|
||||
|
||||
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
|
||||
#
|
||||
#location ~ \.php$ {
|
||||
# proxy_pass http://127.0.0.1;
|
||||
#}
|
||||
|
||||
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
|
||||
#
|
||||
#location ~ \.php$ {
|
||||
# root html;
|
||||
# fastcgi_pass 127.0.0.1:9000;
|
||||
# fastcgi_index index.php;
|
||||
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
|
||||
# include fastcgi_params;
|
||||
#}
|
||||
|
||||
# deny access to .htaccess files, if Apache's document root
|
||||
# concurs with nginx's one
|
||||
#
|
||||
#location ~ /\.ht {
|
||||
# deny all;
|
||||
#}
|
||||
}
|
||||
|
||||
+18
-9
@@ -1,15 +1,24 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
devtools: { enabled: true },
|
||||
ssr: false,
|
||||
ssr: true,
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
websocketEndpoint: 'ws://127.0.0.1:8765', // can be overridden by NUXT_PUBLIC_API_BASE environment variable
|
||||
discordLoginEndpoint: ''
|
||||
}
|
||||
welcomeMessage: "Hello",
|
||||
endpoint: "https://playtest.legonzaur.fr/api", // can be overridden by NUXT_PUBLIC_ENDPOINT environment variable
|
||||
discordLoginEndpoint: process.env.NUXT_PUBLIC_DISCORD_LOGIN_ENDPOINT,
|
||||
devMode: false,
|
||||
},
|
||||
},
|
||||
modules: [
|
||||
'@pinia/nuxt',
|
||||
'@pinia-plugin-persistedstate/nuxt',
|
||||
],
|
||||
})
|
||||
modules: ["@pinia/nuxt", "@pinia-plugin-persistedstate/nuxt"],
|
||||
vue: {
|
||||
runtimeCompiler: true,
|
||||
},
|
||||
nitro: {
|
||||
prerender: {
|
||||
routes: ["/rules", "/login", "/game", "/lobby"],
|
||||
|
||||
crawlLinks: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"cards-gen": "sh -c 'docker run --rm --pull=always -v ./assets/images/cards/heron-svg:/app/output/heron git.legonzaur.fr/heronfief/cards-gen:main' && sudo chown -R $USER:$USER ./assets/images/cards/heron-svg",
|
||||
"pull-images": "sh -c 'docker run --rm --pull=always -v ./public/img:/repo/images/skins git.legonzaur.fr/heronfief/cards-gen:main cp -r img/skins/ /repo/images/skins' && sudo chown -R $USER:$USER ./public/",
|
||||
"fetch": "npm run cards-gen && npm run pull-images",
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
<script setup lang="ts">
|
||||
import LoadingScreen from "~/components/game/overlay/LoadingScreen.vue";
|
||||
import {
|
||||
endTurn,
|
||||
subscribe,
|
||||
useToken,
|
||||
startGame,
|
||||
joinGame,
|
||||
deleteGame,
|
||||
leaveGame,
|
||||
surrender,
|
||||
} from "~/netcode";
|
||||
import { delay } from "~/netcode/events";
|
||||
import {
|
||||
type Container,
|
||||
type Effect,
|
||||
type Game,
|
||||
type Player,
|
||||
} from "~/netcode/interfaces";
|
||||
import { handlers } from "~/netcode/events";
|
||||
|
||||
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,
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
await 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]);
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
if (game.value == null) {
|
||||
navigateTo("/lobby");
|
||||
throw new Error("game not found");
|
||||
}
|
||||
if (game.value.started) {
|
||||
focusedPlayer.value = game.value.teams.find(
|
||||
(t) => t._id == game.value?.currentTurn.currentTeam
|
||||
)?.players[0];
|
||||
} else {
|
||||
focusedPlayer.value = selfPlayer.value ?? playerList.value![0];
|
||||
}
|
||||
|
||||
eventSource = subscribe(`/games/${gameId}/subscribe`);
|
||||
eventSource.addEventListener("message", async (message) => {
|
||||
const current_game = game.value;
|
||||
if (!current_game) {
|
||||
throw new Error("game not found");
|
||||
}
|
||||
const data = JSON.parse(message.data);
|
||||
queueAction(async () => {
|
||||
if (data.type in handlers) {
|
||||
await handlers[data.type as keyof typeof handlers](data, {
|
||||
game: current_game,
|
||||
containers,
|
||||
playerList,
|
||||
refresh,
|
||||
focusPlayer,
|
||||
settingsStore,
|
||||
});
|
||||
} else {
|
||||
console.error(data.type + " not implemented");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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) ??
|
||||
false
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function focusPlayer(p: Player) {
|
||||
focusedPlayer.value = p;
|
||||
}
|
||||
|
||||
function useTokenClick(t: Effect) {
|
||||
console.log(t);
|
||||
useToken(game.value!._id, t._id);
|
||||
}
|
||||
|
||||
function showDiscard() {
|
||||
discard_unwrapped.value = true;
|
||||
}
|
||||
onUnmounted(() => {
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @contextmenu.prevent="false" id="game_board">
|
||||
<AnimatedBackground
|
||||
@ready="loaded = true"
|
||||
:isTurn="isTurn ?? false"
|
||||
></AnimatedBackground>
|
||||
<ClientOnly>
|
||||
<template v-if="game && focusedPlayer && playerList">
|
||||
<!-- <Transition name="slide-left" mode="out-in"> -->
|
||||
<div id="stack_discard" :key="focusedPlayer?._id">
|
||||
<div id="stack">
|
||||
<GameCardGrouped
|
||||
:container="focusedPlayer.stack"
|
||||
:wrapped="true"
|
||||
:hidden="true"
|
||||
>
|
||||
</GameCardGrouped>
|
||||
</div>
|
||||
|
||||
<GameOverlayDiscard
|
||||
:game="game"
|
||||
:player="focusedPlayer"
|
||||
></GameOverlayDiscard>
|
||||
</div>
|
||||
<!-- </Transition> -->
|
||||
|
||||
<GameMarket
|
||||
:game="game"
|
||||
:market="game.market"
|
||||
:fire_gems="game.fireGems"
|
||||
:marketStack="game.marketStack"
|
||||
>
|
||||
</GameMarket>
|
||||
<div>
|
||||
<GameToken
|
||||
:token="t"
|
||||
v-for="t in game.currentTurn.tokens"
|
||||
@useToken="useTokenClick"
|
||||
></GameToken>
|
||||
</div>
|
||||
<div id="player_list_container">
|
||||
<div id="player_list">
|
||||
<GamePlayerListElement
|
||||
v-for="p in playerList"
|
||||
:player="p"
|
||||
:game="game"
|
||||
@click="focusPlayer(p)"
|
||||
:focused="focusedPlayer == p"
|
||||
:is-self-turn="isTurn"
|
||||
:self="selfPlayer == p"
|
||||
>
|
||||
</GamePlayerListElement>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="current_player">
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<GamePlayerSlot
|
||||
v-if="focusedPlayer"
|
||||
:player="focusedPlayer"
|
||||
:game="game"
|
||||
:key="focusedPlayer._id"
|
||||
></GamePlayerSlot>
|
||||
</Transition>
|
||||
</div>
|
||||
<GameModalResultDialog
|
||||
:isVisible="showResults"
|
||||
:won="isWinner"
|
||||
:players="playerList"
|
||||
@close="showResults = false"
|
||||
>
|
||||
</GameModalResultDialog>
|
||||
<GamePlayerSelf
|
||||
v-if="selfPlayer"
|
||||
:container="selfPlayer.hand"
|
||||
></GamePlayerSelf>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
|
||||
<!-- <LoadingScreen v-if="!loaded"></LoadingScreen> -->
|
||||
<footer>
|
||||
<div>
|
||||
<GameModalOptionsDialog></GameModalOptionsDialog>
|
||||
<GameModalConfigureDialog
|
||||
v-if="game && game.owner.userId == authStore.selfUser.userId"
|
||||
:game="game"
|
||||
></GameModalConfigureDialog>
|
||||
</div>
|
||||
|
||||
<ClientOnly>
|
||||
<div id="turn_buttons" v-if="game">
|
||||
<template v-if="game.started">
|
||||
<button
|
||||
id="end_turn"
|
||||
class="end_turn_button turn_icon"
|
||||
:class="{ hidden: !isTurn }"
|
||||
@click="endTurn(game._id)"
|
||||
v-if="isTurn"
|
||||
>
|
||||
END TURN
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="authStore.logged">
|
||||
<template v-if="game.owner.userId == authStore.selfUser.userId">
|
||||
<button
|
||||
class="end_turn_button turn_icon"
|
||||
@click="startGame(game._id)"
|
||||
>
|
||||
Start game
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<button
|
||||
class="end_turn_button turn_icon"
|
||||
@click="joinGame(game._id)"
|
||||
v-else-if="!selfPlayer"
|
||||
>
|
||||
Join game
|
||||
</button>
|
||||
<button v-else @click="leaveGame(game._id)">Leave game</button>
|
||||
</template>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
v-if="game && game.started && selfPlayer"
|
||||
class="red"
|
||||
@click="surrender(game._id)"
|
||||
>
|
||||
Surrender
|
||||
</button>
|
||||
<button @click="navigateTo('/lobby')">Return to lobby</button>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
footer {
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
grid-area: footer;
|
||||
height: 50px;
|
||||
/* background: red; */
|
||||
justify-content: space-between;
|
||||
}
|
||||
#game_status {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
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 min-content;
|
||||
grid-template-areas:
|
||||
"stack_discard market turn_stats"
|
||||
"player_list player_list player_list"
|
||||
"current_player current_player current_player"
|
||||
"footer footer footer";
|
||||
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;
|
||||
overflow-x: clip;
|
||||
/* 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,37 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import goStore, { setReady } from "@/netcode";
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="root_page_div">
|
||||
<div id="game_status">
|
||||
|
||||
<template v-if="goStore.self && !goStore.current_game?.started">
|
||||
<input @click.prevent="setReady(($event.target as HTMLInputElement).checked)" type="checkbox" id="ready"
|
||||
:checked="goStore.self?.ready" />
|
||||
<label for="ready">Ready</label>
|
||||
</template>
|
||||
</div>
|
||||
<LoginView v-if="goStore.context === 'login'"></LoginView>
|
||||
<GameBoard v-if="goStore.context != 'lobby' && goStore.context != 'login'"></GameBoard>
|
||||
<LobbyView v-if="goStore.context == 'lobby'"></LobbyView>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.root_page_div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#game_status {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(255, 255, 255, .75);
|
||||
}
|
||||
</style>
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
<script setup lang="ts">
|
||||
// import { playSound } from '~/helpers/audioHelpers';
|
||||
import { playSound } from "~/helpers/audioHelpers";
|
||||
import { createGame, subscribe } from "~/netcode";
|
||||
import type { Game, Player, User } from "~/netcode/interfaces";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
let eventSource: EventSource;
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
const req = await useFetch<Array<Game>>(config.public.endpoint + "/games", {
|
||||
credentials: "include",
|
||||
server: false,
|
||||
});
|
||||
|
||||
let games = req.data;
|
||||
|
||||
const erika_alive = ref(true);
|
||||
const router = useRouter();
|
||||
|
||||
function killErika() {
|
||||
playSound("/sounds/splat1.mp3");
|
||||
erika_alive.value = false;
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
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 || index == undefined) {
|
||||
throw new Error("Couldn't delete game");
|
||||
}
|
||||
games.value?.splice(index, 1);
|
||||
} else if (data.type == "joinGame") {
|
||||
games.value?.find((g) => g._id == data.game)?.teams.push(data.team);
|
||||
} else if (data.type == "leaveTeam") {
|
||||
const game = games.value?.find((g) => g._id == data.game);
|
||||
if (game == undefined) {
|
||||
throw new Error("Cannot find game");
|
||||
}
|
||||
const team = game.teams.find((t) => t._id == data.team._id);
|
||||
if (team == undefined) {
|
||||
throw new Error("Cannot find team");
|
||||
}
|
||||
const playerIndex = team.players.findIndex((p) => p._id == data.playerId);
|
||||
if (playerIndex == -1) {
|
||||
throw new Error("Cannot find player in team");
|
||||
}
|
||||
team.players.splice(playerIndex, 1);
|
||||
if (team.players.length === 0) {
|
||||
const index = game.teams.findIndex((t) => t._id == data.team._id);
|
||||
game.teams.splice(index, 1);
|
||||
}
|
||||
} else if (data.type == "start") {
|
||||
const targetGame = games.value?.find((g) => g._id == data.game);
|
||||
if (targetGame) {
|
||||
targetGame.started = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
eventSource.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="background">
|
||||
<header>
|
||||
<ClientOnly>
|
||||
<GamePlayerIcon
|
||||
v-if="authStore.logged"
|
||||
:user="(authStore.selfUser as User)"
|
||||
/>
|
||||
<button v-else @click="navigateTo('/login')">Login</button>
|
||||
</ClientOnly>
|
||||
</header>
|
||||
<main>
|
||||
<div class="title">Entrez dans le Royaume de Héron !</div>
|
||||
<ClientOnly>
|
||||
<template v-if="authStore.logged">
|
||||
<div class="create-game-zone">
|
||||
<button
|
||||
@click="createGame"
|
||||
v-if="
|
||||
!games?.some(
|
||||
(g) => g.owner.userId === authStore.selfUser.userId
|
||||
)
|
||||
"
|
||||
>
|
||||
Create Game
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="games-list-zone">
|
||||
<div v-for="g in games">
|
||||
<button
|
||||
v-if="g"
|
||||
@click="router.push({ path: `/game`, query: { id: g._id } })"
|
||||
:class="
|
||||
authStore.selfUser.userId == g.owner.userId ? 'own_game' : ''
|
||||
"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
g.teams.every((t) =>
|
||||
t.players.every(
|
||||
(p) => p.user.userId !== authStore.selfUser.userId
|
||||
)
|
||||
)
|
||||
"
|
||||
>
|
||||
SPECTATE
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="!g.started"> JOIN BACK </template>
|
||||
<template v-else> CONTINUE </template>
|
||||
</template>
|
||||
|
||||
<br />
|
||||
{{
|
||||
g.teams.reduce(
|
||||
(prev, curr) => prev.concat(curr.players),
|
||||
[] as Player[]
|
||||
).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>
|
||||
</ClientOnly>
|
||||
</main>
|
||||
|
||||
<div class="overlay" v-if="erika_alive && !config.public.devMode">
|
||||
<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>
|
||||
</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: grid;
|
||||
grid-template-rows: 100px 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
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);
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 100px;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.create-game-zone,
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
header {
|
||||
flex-direction: row;
|
||||
backdrop-filter: blur(50px);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.create-game-zone {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.create-game-zone button {
|
||||
background-color: #4caf50;
|
||||
}
|
||||
|
||||
.games-list-zone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.games-list-zone button {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
padding: 20px 24px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.games-list-zone button.own_game {
|
||||
border: solid yellow 5px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
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;
|
||||
}
|
||||
|
||||
.overlay-image {
|
||||
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;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.erika-zone:hover .overlay-button .kill-erika-button {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const code = route.query.code;
|
||||
|
||||
if (!code) {
|
||||
navigateTo(config.public.discordLoginEndpoint, { external: true });
|
||||
} else {
|
||||
navigateTo("/login");
|
||||
}
|
||||
</script>
|
||||
+391
-2
@@ -1,3 +1,392 @@
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RulesDisplay></RulesDisplay>
|
||||
</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/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">
|
||||
<GameCard :cardId="6" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="19" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="36" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="46" :tilted="false"></GameCard>
|
||||
</div>
|
||||
<p>​</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">
|
||||
<GameCard :cardId="undefined" :tilted="false"></GameCard>
|
||||
</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">
|
||||
<GameCard :cardId="56" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="59" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="58" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="57" :tilted="false"></GameCard>
|
||||
</div>
|
||||
<p>
|
||||
La pioche est formée au hasard: on peut donc avoir une main de départ
|
||||
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">
|
||||
<GameCard :cardId="56" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="52" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="43" :tilted="false"></GameCard>
|
||||
</div>
|
||||
<p>​</p>
|
||||
<h3>Objets & actions</h3>
|
||||
<div class="card-row">
|
||||
<GameCard :cardId="56" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="52" :tilted="false"></GameCard>
|
||||
</div>
|
||||
<p>
|
||||
Par simplicité, disons que les objets et les actions sont pareil: on
|
||||
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">
|
||||
<GameCard :cardId="43" :tilted="false"></GameCard>
|
||||
</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">
|
||||
<GameCard :cardId="49" :tilted="false"></GameCard>
|
||||
</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">
|
||||
<GameCard :cardId="6" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="19" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="36" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="46" :tilted="false"></GameCard>
|
||||
</div>
|
||||
<p>
|
||||
Un marché est mis à disposition de tous les joueurs, mettant à
|
||||
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">
|
||||
<GameCard :cardId="13" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="3" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="1" :tilted="false"></GameCard>
|
||||
</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">
|
||||
<GameCard :cardId="39" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="34" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="40" :tilted="false"></GameCard>
|
||||
</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">
|
||||
<GameCard :cardId="52" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="46" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="47" :tilted="false"></GameCard>
|
||||
</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">
|
||||
<GameCard :cardId="24" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="25" :tilted="false"></GameCard>
|
||||
<GameCard :cardId="19" :tilted="false"></GameCard>
|
||||
</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">
|
||||
<GameCard :cardId="14" :tilted="false"></GameCard>
|
||||
</div>
|
||||
<p>Sur cette carte, on peut voir les trois en même temps.</p>
|
||||
<p>La 1ère ligne s'active tout le temps.</p>
|
||||
<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("/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, 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;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.overlay-2 {
|
||||
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;
|
||||
}
|
||||
|
||||
.overlay-image {
|
||||
position: sticky;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const config = useRuntimeConfig();
|
||||
config.public.welcomeMessage;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
{{ config.public.welcomeMessage }}
|
||||
<GameCard :cardId="6" :tilted="false"></GameCard>
|
||||
</div>
|
||||
</template>
|
||||
BIN
Binary file not shown.
+620
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
contributors: [Stefan Gustavson, Ian McEwan]
|
||||
description: modulus of 289
|
||||
use: <float|vec2|vec3|vec4> mod289(<float|vec2|vec3|vec4> x)
|
||||
*/
|
||||
#ifndef FNC_MOD289
|
||||
#define FNC_MOD289
|
||||
float mod289(const in float x) { return x - floor(x * (1. / 289.)) * 289.; }
|
||||
vec2 mod289(const in vec2 x) { return x - floor(x * (1. / 289.)) * 289.; }
|
||||
vec3 mod289(const in vec3 x) { return x - floor(x * (1. / 289.)) * 289.; }
|
||||
vec4 mod289(const in vec4 x) { return x - floor(x * (1. / 289.)) * 289.; }
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
contributors: [Stefan Gustavson, Ian McEwan]
|
||||
description: permute
|
||||
use: <float|vec2|vec3|vec4> permute(<float|vec2|vec3|vec4> x)
|
||||
examples:
|
||||
- https://raw.githubusercontent.com/patriciogonzalezvivo/lygia_examples/main/math_functions.frag
|
||||
*/
|
||||
#ifndef FNC_PERMUTE
|
||||
#define FNC_PERMUTE
|
||||
float permute(const in float v) { return mod289(((v * 34.0) + 1.0) * v); }
|
||||
vec2 permute(const in vec2 v) { return mod289(((v * 34.0) + 1.0) * v); }
|
||||
vec3 permute(const in vec3 v) { return mod289(((v * 34.0) + 1.0) * v); }
|
||||
vec4 permute(const in vec4 v) { return mod289(((v * 34.0) + 1.0) * v); }
|
||||
#endif
|
||||
|
||||
/*
|
||||
contributors: [Stefan Gustavson, Ian McEwan]
|
||||
description: Fast, accurate inverse square root.
|
||||
use: <float|vec2|vec3|vec4> taylorInvSqrt(<float|vec2|vec3|vec4> x)
|
||||
*/
|
||||
#ifndef FNC_TAYLORINVSQRT
|
||||
#define FNC_TAYLORINVSQRT
|
||||
float taylorInvSqrt(in float r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
||||
vec2 taylorInvSqrt(in vec2 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
||||
vec3 taylorInvSqrt(in vec3 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
||||
vec4 taylorInvSqrt(in vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
||||
#endif
|
||||
/*
|
||||
contributors: [Stefan Gustavson, Ian McEwan]
|
||||
description: grad4, used for snoise(vec4 v)
|
||||
use: grad4(<float> j, <vec4> ip)
|
||||
*/
|
||||
#ifndef FNC_GRAD4
|
||||
#define FNC_GRAD4
|
||||
vec4 grad4(float j, vec4 ip) {
|
||||
const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
|
||||
vec4 p,s;
|
||||
p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
|
||||
p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
|
||||
s = vec4(lessThan(p, vec4(0.0)));
|
||||
p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;
|
||||
return p;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
contributors: [Stefan Gustavson, Ian McEwan]
|
||||
description: Simplex Noise https://github.com/stegu/webgl-noise
|
||||
use: snoise(<vec2|vec3|vec4> pos)
|
||||
license: |
|
||||
Copyright 2021-2023 by Stefan Gustavson and Ian McEwan.
|
||||
Published under the terms of the MIT license:
|
||||
https://opensource.org/license/mit/
|
||||
examples:
|
||||
- /shaders/generative_snoise.frag
|
||||
*/
|
||||
#ifndef FNC_SNOISE
|
||||
#define FNC_SNOISE
|
||||
float snoise(in vec2 v) {
|
||||
const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
|
||||
0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
|
||||
-0.577350269189626, // -1.0 + 2.0 * C.x
|
||||
0.024390243902439); // 1.0 / 41.0
|
||||
// First corner
|
||||
vec2 i = floor(v + dot(v, C.yy) );
|
||||
vec2 x0 = v - i + dot(i, C.xx);
|
||||
// Other corners
|
||||
vec2 i1;
|
||||
//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
|
||||
//i1.y = 1.0 - i1.x;
|
||||
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
||||
// x0 = x0 - 0.0 + 0.0 * C.xx ;
|
||||
// x1 = x0 - i1 + 1.0 * C.xx ;
|
||||
// x2 = x0 - 1.0 + 2.0 * C.xx ;
|
||||
vec4 x12 = x0.xyxy + C.xxzz;
|
||||
x12.xy -= i1;
|
||||
// Permutations
|
||||
i = mod289(i); // Avoid truncation effects in permutation
|
||||
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
|
||||
+ i.x + vec3(0.0, i1.x, 1.0 ));
|
||||
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
|
||||
m = m*m ;
|
||||
m = m*m ;
|
||||
// Gradients: 41 points uniformly over a line, mapped onto a diamond.
|
||||
// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
|
||||
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
||||
vec3 h = abs(x) - 0.5;
|
||||
vec3 ox = floor(x + 0.5);
|
||||
vec3 a0 = x - ox;
|
||||
// Normalise gradients implicitly by scaling m
|
||||
// Approximation of: m *= inversesqrt( a0*a0 + h*h );
|
||||
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
|
||||
// Compute final noise value at P
|
||||
vec3 g;
|
||||
g.x = a0.x * x0.x + h.x * x0.y;
|
||||
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||||
return 130.0 * dot(m, g);
|
||||
}
|
||||
float snoise(in vec3 v) {
|
||||
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
|
||||
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
||||
// First corner
|
||||
vec3 i = floor(v + dot(v, C.yyy) );
|
||||
vec3 x0 = v - i + dot(i, C.xxx) ;
|
||||
// Other corners
|
||||
vec3 g = step(x0.yzx, x0.xyz);
|
||||
vec3 l = 1.0 - g;
|
||||
vec3 i1 = min( g.xyz, l.zxy );
|
||||
vec3 i2 = max( g.xyz, l.zxy );
|
||||
// x0 = x0 - 0.0 + 0.0 * C.xxx;
|
||||
// x1 = x0 - i1 + 1.0 * C.xxx;
|
||||
// x2 = x0 - i2 + 2.0 * C.xxx;
|
||||
// x3 = x0 - 1.0 + 3.0 * C.xxx;
|
||||
vec3 x1 = x0 - i1 + C.xxx;
|
||||
vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
|
||||
vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
|
||||
// Permutations
|
||||
i = mod289(i);
|
||||
vec4 p = permute( permute( permute(
|
||||
i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
|
||||
+ i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
|
||||
+ i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
|
||||
// Gradients: 7x7 points over a square, mapped onto an octahedron.
|
||||
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
|
||||
float n_ = 0.142857142857; // 1.0/7.0
|
||||
vec3 ns = n_ * D.wyz - D.xzx;
|
||||
vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
|
||||
vec4 x_ = floor(j * ns.z);
|
||||
vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
|
||||
vec4 x = x_ *ns.x + ns.yyyy;
|
||||
vec4 y = y_ *ns.x + ns.yyyy;
|
||||
vec4 h = 1.0 - abs(x) - abs(y);
|
||||
vec4 b0 = vec4( x.xy, y.xy );
|
||||
vec4 b1 = vec4( x.zw, y.zw );
|
||||
//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
|
||||
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
|
||||
vec4 s0 = floor(b0)*2.0 + 1.0;
|
||||
vec4 s1 = floor(b1)*2.0 + 1.0;
|
||||
vec4 sh = -step(h, vec4(0.0));
|
||||
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
|
||||
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
|
||||
vec3 p0 = vec3(a0.xy,h.x);
|
||||
vec3 p1 = vec3(a0.zw,h.y);
|
||||
vec3 p2 = vec3(a1.xy,h.z);
|
||||
vec3 p3 = vec3(a1.zw,h.w);
|
||||
//Normalise gradients
|
||||
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
|
||||
p0 *= norm.x;
|
||||
p1 *= norm.y;
|
||||
p2 *= norm.z;
|
||||
p3 *= norm.w;
|
||||
// Mix final noise value
|
||||
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
||||
m = m * m;
|
||||
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
|
||||
dot(p2,x2), dot(p3,x3) ) );
|
||||
}
|
||||
float snoise(in vec4 v) {
|
||||
const vec4 C = vec4( 0.138196601125011, // (5 - sqrt(5))/20 G4
|
||||
0.276393202250021, // 2 * G4
|
||||
0.414589803375032, // 3 * G4
|
||||
-0.447213595499958); // -1 + 4 * G4
|
||||
// First corner
|
||||
vec4 i = floor(v + dot(v, vec4(.309016994374947451)) ); // (sqrt(5) - 1)/4
|
||||
vec4 x0 = v - i + dot(i, C.xxxx);
|
||||
// Other corners
|
||||
// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
|
||||
vec4 i0;
|
||||
vec3 isX = step( x0.yzw, x0.xxx );
|
||||
vec3 isYZ = step( x0.zww, x0.yyz );
|
||||
// i0.x = dot( isX, vec3( 1.0 ) );
|
||||
i0.x = isX.x + isX.y + isX.z;
|
||||
i0.yzw = 1.0 - isX;
|
||||
// i0.y += dot( isYZ.xy, vec2( 1.0 ) );
|
||||
i0.y += isYZ.x + isYZ.y;
|
||||
i0.zw += 1.0 - isYZ.xy;
|
||||
i0.z += isYZ.z;
|
||||
i0.w += 1.0 - isYZ.z;
|
||||
// i0 now contains the unique values 0,1,2,3 in each channel
|
||||
vec4 i3 = clamp( i0, 0.0, 1.0 );
|
||||
vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
|
||||
vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );
|
||||
// x0 = x0 - 0.0 + 0.0 * C.xxxx
|
||||
// x1 = x0 - i1 + 1.0 * C.xxxx
|
||||
// x2 = x0 - i2 + 2.0 * C.xxxx
|
||||
// x3 = x0 - i3 + 3.0 * C.xxxx
|
||||
// x4 = x0 - 1.0 + 4.0 * C.xxxx
|
||||
vec4 x1 = x0 - i1 + C.xxxx;
|
||||
vec4 x2 = x0 - i2 + C.yyyy;
|
||||
vec4 x3 = x0 - i3 + C.zzzz;
|
||||
vec4 x4 = x0 + C.wwww;
|
||||
// Permutations
|
||||
i = mod289(i);
|
||||
float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x);
|
||||
vec4 j1 = permute( permute( permute( permute (
|
||||
i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
|
||||
+ i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
|
||||
+ i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
|
||||
+ i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));
|
||||
// Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope
|
||||
// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
|
||||
vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;
|
||||
vec4 p0 = grad4(j0, ip);
|
||||
vec4 p1 = grad4(j1.x, ip);
|
||||
vec4 p2 = grad4(j1.y, ip);
|
||||
vec4 p3 = grad4(j1.z, ip);
|
||||
vec4 p4 = grad4(j1.w, ip);
|
||||
// Normalise gradients
|
||||
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
|
||||
p0 *= norm.x;
|
||||
p1 *= norm.y;
|
||||
p2 *= norm.z;
|
||||
p3 *= norm.w;
|
||||
p4 *= taylorInvSqrt(dot(p4,p4));
|
||||
// Mix contributions from the five corners
|
||||
vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);
|
||||
vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0);
|
||||
m0 = m0 * m0;
|
||||
m1 = m1 * m1;
|
||||
return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))
|
||||
+ dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;
|
||||
}
|
||||
vec2 snoise2( vec2 x ){
|
||||
float s = snoise(vec2( x ));
|
||||
float s1 = snoise(vec2( x.y - 19.1, x.x + 47.2 ));
|
||||
return vec2( s , s1 );
|
||||
}
|
||||
vec3 snoise3( vec3 x ){
|
||||
float s = snoise(vec3( x ));
|
||||
float s1 = snoise(vec3( x.y - 19.1 , x.z + 33.4 , x.x + 47.2 ));
|
||||
float s2 = snoise(vec3( x.z + 74.2 , x.x - 124.5 , x.y + 99.4 ));
|
||||
return vec3( s , s1 , s2 );
|
||||
}
|
||||
vec3 snoise3( vec4 x ){
|
||||
float s = snoise(vec4( x ));
|
||||
float s1 = snoise(vec4( x.y - 19.1 , x.z + 33.4 , x.x + 47.2, x.w ));
|
||||
float s2 = snoise(vec4( x.z + 74.2 , x.x - 124.5 , x.y + 99.4, x.w ));
|
||||
return vec3( s , s1 , s2 );
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
contributors: ["Patricio Gonzalez Vivo", "David Hoskins", "Inigo Quilez"]
|
||||
description: Pass a value and get some random normalize value between 0 and 1
|
||||
use: float random[2|3](<float|vec2|vec3> value)
|
||||
options:
|
||||
- RANDOM_HIGHER_RANGE: for working with a range over 0 and 1
|
||||
- RANDOM_SINLESS: Use sin-less random, which tolerates bigger values before producing pattern. From https://www.shadertoy.com/view/4djSRW
|
||||
- RANDOM_SCALE: by default this scale if for number with a big range. For producing good random between 0 and 1 use bigger range
|
||||
examples:
|
||||
- /shaders/generative_random.frag
|
||||
license:
|
||||
- MIT License (MIT) Copyright 2014, David Hoskins
|
||||
*/
|
||||
#ifndef RANDOM_SCALE
|
||||
#ifdef RANDOM_HIGHER_RANGE
|
||||
#define RANDOM_SCALE vec4(.1031, .1030, .0973, .1099)
|
||||
#else
|
||||
#define RANDOM_SCALE vec4(443.897, 441.423, .0973, .1099)
|
||||
#endif
|
||||
#endif
|
||||
#ifndef FNC_RANDOM
|
||||
#define FNC_RANDOM
|
||||
float random(in float x) {
|
||||
#ifdef RANDOM_SINLESS
|
||||
x = fract(x * RANDOM_SCALE.x);
|
||||
x *= x + 33.33;
|
||||
x *= x + x;
|
||||
return fract(x);
|
||||
#else
|
||||
return fract(sin(x) * 43758.5453);
|
||||
#endif
|
||||
}
|
||||
float random(in vec2 st) {
|
||||
#ifdef RANDOM_SINLESS
|
||||
vec3 p3 = fract(vec3(st.xyx) * RANDOM_SCALE.xyz);
|
||||
p3 += dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
#else
|
||||
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
#endif
|
||||
}
|
||||
float random(in vec3 pos) {
|
||||
#ifdef RANDOM_SINLESS
|
||||
pos = fract(pos * RANDOM_SCALE.xyz);
|
||||
pos += dot(pos, pos.zyx + 31.32);
|
||||
return fract((pos.x + pos.y) * pos.z);
|
||||
#else
|
||||
return fract(sin(dot(pos.xyz, vec3(70.9898, 78.233, 32.4355))) * 43758.5453123);
|
||||
#endif
|
||||
}
|
||||
float random(in vec4 pos) {
|
||||
#ifdef RANDOM_SINLESS
|
||||
pos = fract(pos * RANDOM_SCALE);
|
||||
pos += dot(pos, pos.wzxy + 33.33);
|
||||
return fract((pos.x + pos.y) * (pos.z + pos.w));
|
||||
#else
|
||||
float dot_product = dot(pos, vec4(12.9898,78.233,45.164,94.673));
|
||||
return fract(sin(dot_product) * 43758.5453);
|
||||
#endif
|
||||
}
|
||||
vec2 random2(float p) {
|
||||
vec3 p3 = fract(vec3(p) * RANDOM_SCALE.xyz);
|
||||
p3 += dot(p3, p3.yzx + 19.19);
|
||||
return fract((p3.xx + p3.yz) * p3.zy);
|
||||
}
|
||||
vec2 random2(vec2 p) {
|
||||
vec3 p3 = fract(p.xyx * RANDOM_SCALE.xyz);
|
||||
p3 += dot(p3, p3.yzx + 19.19);
|
||||
return fract((p3.xx + p3.yz) * p3.zy);
|
||||
}
|
||||
vec2 random2(vec3 p3) {
|
||||
p3 = fract(p3 * RANDOM_SCALE.xyz);
|
||||
p3 += dot(p3, p3.yzx + 19.19);
|
||||
return fract((p3.xx + p3.yz) * p3.zy);
|
||||
}
|
||||
vec3 random3(float p) {
|
||||
vec3 p3 = fract(vec3(p) * RANDOM_SCALE.xyz);
|
||||
p3 += dot(p3, p3.yzx + 19.19);
|
||||
return fract((p3.xxy + p3.yzz) * p3.zyx);
|
||||
}
|
||||
vec3 random3(vec2 p) {
|
||||
vec3 p3 = fract(vec3(p.xyx) * RANDOM_SCALE.xyz);
|
||||
p3 += dot(p3, p3.yxz + 19.19);
|
||||
return fract((p3.xxy + p3.yzz) * p3.zyx);
|
||||
}
|
||||
vec3 random3(vec3 p) {
|
||||
p = fract(p * RANDOM_SCALE.xyz);
|
||||
p += dot(p, p.yxz + 19.19);
|
||||
return fract((p.xxy + p.yzz) * p.zyx);
|
||||
}
|
||||
vec4 random4(float p) {
|
||||
vec4 p4 = fract(p * RANDOM_SCALE);
|
||||
p4 += dot(p4, p4.wzxy + 19.19);
|
||||
return fract((p4.xxyz + p4.yzzw) * p4.zywx);
|
||||
}
|
||||
vec4 random4(vec2 p) {
|
||||
vec4 p4 = fract(p.xyxy * RANDOM_SCALE);
|
||||
p4 += dot(p4, p4.wzxy + 19.19);
|
||||
return fract((p4.xxyz + p4.yzzw) * p4.zywx);
|
||||
}
|
||||
vec4 random4(vec3 p) {
|
||||
vec4 p4 = fract(p.xyzx * RANDOM_SCALE);
|
||||
p4 += dot(p4, p4.wzxy + 19.19);
|
||||
return fract((p4.xxyz + p4.yzzw) * p4.zywx);
|
||||
}
|
||||
vec4 random4(vec4 p4) {
|
||||
p4 = fract(p4 * RANDOM_SCALE);
|
||||
p4 += dot(p4, p4.wzxy + 19.19);
|
||||
return fract((p4.xxyz + p4.yzzw) * p4.zywx);
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
contributors: Patricio Gonzalez Vivo
|
||||
description: Signed Random
|
||||
use: srandomX(<vec2|vec3> x)
|
||||
license:
|
||||
- Copyright (c) 2021 Patricio Gonzalez Vivo under Prosperity License - https://prosperitylicense.com/versions/3.0.0
|
||||
- Copyright (c) 2021 Patricio Gonzalez Vivo under Patron License - https://lygia.xyz/license
|
||||
*/
|
||||
#ifndef FNC_SRANDOM
|
||||
#define FNC_SRANDOM
|
||||
float srandom(in float x) {
|
||||
return -1. + 2. * fract(sin(x) * 43758.5453);
|
||||
}
|
||||
float srandom(in vec2 st) {
|
||||
return -1. + 2. * fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
}
|
||||
float srandom(in vec3 pos) {
|
||||
return -1. + 2. * fract(sin(dot(pos.xyz, vec3(70.9898, 78.233, 32.4355))) * 43758.5453123);
|
||||
}
|
||||
float srandom(in vec4 pos) {
|
||||
float dot_product = dot(pos, vec4(12.9898,78.233,45.164,94.673));
|
||||
return -1. + 2. * fract(sin(dot_product) * 43758.5453);
|
||||
}
|
||||
vec2 srandom2(in vec2 st) {
|
||||
const vec2 k = vec2(.3183099, .3678794);
|
||||
st = st * k + k.yx;
|
||||
return -1. + 2. * fract(16. * k * fract(st.x * st.y * (st.x + st.y)));
|
||||
}
|
||||
vec3 srandom3(in vec3 p) {
|
||||
p = vec3( dot(p, vec3(127.1, 311.7, 74.7)),
|
||||
dot(p, vec3(269.5, 183.3, 246.1)),
|
||||
dot(p, vec3(113.5, 271.9, 124.6)));
|
||||
return -1. + 2. * fract(sin(p) * 43758.5453123);
|
||||
}
|
||||
vec2 srandom2(in vec2 p, const in float tileLength) {
|
||||
p = mod(p, vec2(tileLength));
|
||||
return srandom2(p);
|
||||
}
|
||||
vec3 srandom3(in vec3 p, const in float tileLength) {
|
||||
p = mod(p, vec3(tileLength));
|
||||
return srandom3(p);
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
contributors: Inigo Quiles
|
||||
description: cubic polynomial https://iquilezles.org/articles/smoothsteps/
|
||||
use: <float|vec2|vec3|vec4> cubic(<float|vec2|vec3|vec4> value[, <float> in, <float> out]);
|
||||
examples:
|
||||
- https://raw.githubusercontent.com/patriciogonzalezvivo/lygia_examples/main/math_functions.frag
|
||||
*/
|
||||
#ifndef FNC_CUBIC
|
||||
#define FNC_CUBIC
|
||||
float cubic(const in float v) { return v*v*(3.0-2.0*v); }
|
||||
vec2 cubic(const in vec2 v) { return v*v*(3.0-2.0*v); }
|
||||
vec3 cubic(const in vec3 v) { return v*v*(3.0-2.0*v); }
|
||||
vec4 cubic(const in vec4 v) { return v*v*(3.0-2.0*v); }
|
||||
float cubic(const in float v, in float slope0, in float slope1) {
|
||||
float a = slope0 + slope1 - 2.;
|
||||
float b = -2. * slope0 - slope1 + 3.;
|
||||
float c = slope0;
|
||||
float v2 = v * v;
|
||||
float v3 = v * v2;
|
||||
return a * v3 + b * v2 + c * v;
|
||||
}
|
||||
vec2 cubic(const in vec2 v, in float slope0, in float slope1) {
|
||||
float a = slope0 + slope1 - 2.;
|
||||
float b = -2. * slope0 - slope1 + 3.;
|
||||
float c = slope0;
|
||||
vec2 v2 = v * v;
|
||||
vec2 v3 = v * v2;
|
||||
return a * v3 + b * v2 + c * v;
|
||||
}
|
||||
vec3 cubic(const in vec3 v, in float slope0, in float slope1) {
|
||||
float a = slope0 + slope1 - 2.;
|
||||
float b = -2. * slope0 - slope1 + 3.;
|
||||
float c = slope0;
|
||||
vec3 v2 = v * v;
|
||||
vec3 v3 = v * v2;
|
||||
return a * v3 + b * v2 + c * v;
|
||||
}
|
||||
vec4 cubic(const in vec4 v, in float slope0, in float slope1) {
|
||||
float a = slope0 + slope1 - 2.;
|
||||
float b = -2. * slope0 - slope1 + 3.;
|
||||
float c = slope0;
|
||||
vec4 v2 = v * v;
|
||||
vec4 v3 = v * v2;
|
||||
return a * v3 + b * v2 + c * v;
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
contributors: Inigo Quiles
|
||||
description: quintic polynomial https://iquilezles.org/articles/smoothsteps/
|
||||
use: <float|vec2|vec3|vec4> quintic(<float|vec2|vec3|vec4> value);
|
||||
examples:
|
||||
- https://raw.githubusercontent.com/patriciogonzalezvivo/lygia_examples/main/math_functions.frag
|
||||
*/
|
||||
#ifndef FNC_QUINTIC
|
||||
#define FNC_QUINTIC
|
||||
float quintic(const in float v) { return v*v*v*(v*(v*6.0-15.0)+10.0); }
|
||||
vec2 quintic(const in vec2 v) { return v*v*v*(v*(v*6.0-15.0)+10.0); }
|
||||
vec3 quintic(const in vec3 v) { return v*v*v*(v*(v*6.0-15.0)+10.0); }
|
||||
vec4 quintic(const in vec4 v) { return v*v*v*(v*(v*6.0-15.0)+10.0); }
|
||||
#endif
|
||||
/*
|
||||
contributors: Patricio Gonzalez Vivo
|
||||
description: Gradient Noise
|
||||
use: gnoise(<float> x)
|
||||
license:
|
||||
- Copyright (c) 2021 Patricio Gonzalez Vivo under Prosperity License - https://prosperitylicense.com/versions/3.0.0
|
||||
- Copyright (c) 2021 Patricio Gonzalez Vivo under Patron License - https://lygia.xyz/license
|
||||
*/
|
||||
#ifndef FNC_GNOISE
|
||||
#define FNC_GNOISE
|
||||
float gnoise(float x) {
|
||||
float i = floor(x); // integer
|
||||
float f = fract(x); // fraction
|
||||
return mix(random(i), random(i + 1.0), smoothstep(0.,1.,f));
|
||||
}
|
||||
float gnoise(vec2 st) {
|
||||
vec2 i = floor(st);
|
||||
vec2 f = fract(st);
|
||||
float a = random(i);
|
||||
float b = random(i + vec2(1.0, 0.0));
|
||||
float c = random(i + vec2(0.0, 1.0));
|
||||
float d = random(i + vec2(1.0, 1.0));
|
||||
vec2 u = cubic(f);
|
||||
return mix( a, b, u.x) +
|
||||
(c - a)* u.y * (1.0 - u.x) +
|
||||
(d - b) * u.x * u.y;
|
||||
}
|
||||
float gnoise(vec3 p) {
|
||||
vec3 i = floor(p);
|
||||
vec3 f = fract(p);
|
||||
vec3 u = quintic(f);
|
||||
return -1.0 + 2.0 * mix( mix( mix( random(i + vec3(0.0,0.0,0.0)),
|
||||
random(i + vec3(1.0,0.0,0.0)), u.x),
|
||||
mix( random(i + vec3(0.0,1.0,0.0)),
|
||||
random(i + vec3(1.0,1.0,0.0)), u.x), u.y),
|
||||
mix( mix( random(i + vec3(0.0,0.0,1.0)),
|
||||
random(i + vec3(1.0,0.0,1.0)), u.x),
|
||||
mix( random(i + vec3(0.0,1.0,1.0)),
|
||||
random(i + vec3(1.0,1.0,1.0)), u.x), u.y), u.z );
|
||||
}
|
||||
float gnoise(vec3 p, float tileLength) {
|
||||
vec3 i = floor(p);
|
||||
vec3 f = fract(p);
|
||||
vec3 u = quintic(f);
|
||||
return mix( mix( mix( dot( srandom3(i + vec3(0.0,0.0,0.0), tileLength), f - vec3(0.0,0.0,0.0)),
|
||||
dot( srandom3(i + vec3(1.0,0.0,0.0), tileLength), f - vec3(1.0,0.0,0.0)), u.x),
|
||||
mix( dot( srandom3(i + vec3(0.0,1.0,0.0), tileLength), f - vec3(0.0,1.0,0.0)),
|
||||
dot( srandom3(i + vec3(1.0,1.0,0.0), tileLength), f - vec3(1.0,1.0,0.0)), u.x), u.y),
|
||||
mix( mix( dot( srandom3(i + vec3(0.0,0.0,1.0), tileLength), f - vec3(0.0,0.0,1.0)),
|
||||
dot( srandom3(i + vec3(1.0,0.0,1.0), tileLength), f - vec3(1.0,0.0,1.0)), u.x),
|
||||
mix( dot( srandom3(i + vec3(0.0,1.0,1.0), tileLength), f - vec3(0.0,1.0,1.0)),
|
||||
dot( srandom3(i + vec3(1.0,1.0,1.0), tileLength), f - vec3(1.0,1.0,1.0)), u.x), u.y), u.z );
|
||||
}
|
||||
vec3 gnoise3(vec3 x) {
|
||||
return vec3(gnoise(x+vec3(123.456, 0.567, 0.37)),
|
||||
gnoise(x+vec3(0.11, 47.43, 19.17)),
|
||||
gnoise(x) );
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
contributors: Patricio Gonzalez Vivo
|
||||
description: Fractal Brownian Motion
|
||||
use: fbm(<vec2> pos)
|
||||
options:
|
||||
FBM_OCTAVES: numbers of octaves. Default is 4.
|
||||
FBM_NOISE_FNC(UV): noise function to use Default 'snoise(UV)' (simplex noise)
|
||||
FBM_VALUE_INITIAL: initial value. Default is 0.
|
||||
FBM_SCALE_SCALAR: scalar. Defualt is 2.
|
||||
FBM_AMPLITUD_INITIAL: initial amplitud value. Default is 0.5
|
||||
FBM_AMPLITUD_SCALAR: amplitud scalar. Default is 0.5
|
||||
examples:
|
||||
- /shaders/generative_fbm.frag
|
||||
license:
|
||||
- Copyright (c) 2021 Patricio Gonzalez Vivo under Prosperity License - https://prosperitylicense.com/versions/3.0.0
|
||||
- Copyright (c) 2021 Patricio Gonzalez Vivo under Patron License - https://lygia.xyz/license
|
||||
*/
|
||||
#ifndef FBM_OCTAVES
|
||||
#define FBM_OCTAVES 4
|
||||
#endif
|
||||
#ifndef FBM_NOISE_FNC
|
||||
#define FBM_NOISE_FNC(UV) snoise(UV)
|
||||
#endif
|
||||
#ifndef FBM_NOISE2_FNC
|
||||
#define FBM_NOISE2_FNC(UV) FBM_NOISE_FNC(UV)
|
||||
#endif
|
||||
#ifndef FBM_NOISE3_FNC
|
||||
#define FBM_NOISE3_FNC(UV) FBM_NOISE_FNC(UV)
|
||||
#endif
|
||||
#ifndef FBM_NOISE_TILABLE_FNC
|
||||
#define FBM_NOISE_TILABLE_FNC(UV, TILE) gnoise(UV, TILE)
|
||||
#endif
|
||||
#ifndef FBM_NOISE3_TILABLE_FNC
|
||||
#define FBM_NOISE3_TILABLE_FNC(UV, TILE) FBM_NOISE_TILABLE_FNC(UV, TILE)
|
||||
#endif
|
||||
#ifndef FBM_NOISE_TYPE
|
||||
#define FBM_NOISE_TYPE float
|
||||
#endif
|
||||
#ifndef FBM_VALUE_INITIAL
|
||||
#define FBM_VALUE_INITIAL 0.0
|
||||
#endif
|
||||
#ifndef FBM_SCALE_SCALAR
|
||||
#define FBM_SCALE_SCALAR 2.0
|
||||
#endif
|
||||
#ifndef FBM_AMPLITUD_INITIAL
|
||||
#define FBM_AMPLITUD_INITIAL 0.5
|
||||
#endif
|
||||
#ifndef FBM_AMPLITUD_SCALAR
|
||||
#define FBM_AMPLITUD_SCALAR 0.5
|
||||
#endif
|
||||
#ifndef FNC_FBM
|
||||
#define FNC_FBM
|
||||
FBM_NOISE_TYPE fbm(in vec2 st) {
|
||||
// Initial values
|
||||
FBM_NOISE_TYPE value = FBM_NOISE_TYPE(FBM_VALUE_INITIAL);
|
||||
float amplitud = FBM_AMPLITUD_INITIAL;
|
||||
// Loop of octaves
|
||||
for (int i = 0; i < FBM_OCTAVES; i++) {
|
||||
value += amplitud * FBM_NOISE2_FNC(st);
|
||||
st *= FBM_SCALE_SCALAR;
|
||||
amplitud *= FBM_AMPLITUD_SCALAR;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
FBM_NOISE_TYPE fbm(in vec3 pos) {
|
||||
// Initial values
|
||||
FBM_NOISE_TYPE value = FBM_NOISE_TYPE(FBM_VALUE_INITIAL);
|
||||
float amplitud = FBM_AMPLITUD_INITIAL;
|
||||
// Loop of octaves
|
||||
for (int i = 0; i < FBM_OCTAVES; i++) {
|
||||
value += amplitud * FBM_NOISE3_FNC(pos);
|
||||
pos *= FBM_SCALE_SCALAR;
|
||||
amplitud *= FBM_AMPLITUD_SCALAR;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
FBM_NOISE_TYPE fbm(vec3 p, float tileLength) {
|
||||
const float persistence = 0.5;
|
||||
const float lacunarity = 2.0;
|
||||
float amplitude = 0.5;
|
||||
FBM_NOISE_TYPE total = FBM_NOISE_TYPE(0.0);
|
||||
float normalization = 0.0;
|
||||
for (int i = 0; i < FBM_OCTAVES; ++i) {
|
||||
float noiseValue = FBM_NOISE3_TILABLE_FNC(p, tileLength * lacunarity * 0.5) * 0.5 + 0.5;
|
||||
total += noiseValue * amplitude;
|
||||
normalization += amplitude;
|
||||
amplitude *= persistence;
|
||||
p = p * lacunarity;
|
||||
}
|
||||
return total / normalization;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="88.73304mm"
|
||||
height="134.13795mm"
|
||||
viewBox="0 0 88.73304 134.13795"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="fuck.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.71891835"
|
||||
inkscape:cx="134.22943"
|
||||
inkscape:cy="468.06428"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="1680"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" /><defs
|
||||
id="defs1" /><g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-59.418203,-70.094122)"><path
|
||||
style="opacity:1;fill:#e5d600;fill-opacity:1;stroke:none;stroke-width:3.49999;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 96.352499,125.0496 v -5.30232 c -2.421262,-8.41348 -13.713084,-12.75856 -18.744229,-0.044 v 5.33484 4.73839 0 l -6.716543,0.0313 c -3.042502,0.51774 -4.401994,2.29774 -5.382434,4.36203 l -5.635456,19.68959 c -0.536458,2.04722 -0.674646,4.17979 0,6.48653 11.431564,61.83841 86.704933,55.09416 88.200633,0 0,0 0.17274,-17.73214 0,-18.58223 -1.91851,-9.4414 -12.83795,-12.2172 -15.51392,-4.15694 0.48306,-9.71109 -7.76736,-14.78186 -17.64884,-10.18956 0,0 0.0508,-48.350297 0,-49.216216 -0.60205,-10.268778 -18.30446,-11.336423 -18.559211,0 V 125.0496"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cccccccccccsccscc" /><path
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#eea200;stroke-width:3.9172;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 75.857311,129.7732 v 39.46359"
|
||||
id="path2" /></g></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="59.500008mm"
|
||||
height="79.500008mm"
|
||||
viewBox="0 0 59.500008 79.500008"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="Unnamed document 1"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="true">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="mm"
|
||||
originx="-75.249997"
|
||||
originy="-72.249997"
|
||||
spacingx="0.99999998"
|
||||
spacingy="1"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true"
|
||||
enabled="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1">
|
||||
<inkscape:path-effect
|
||||
effect="mirror_symmetry"
|
||||
start_point="62.136614,73.509798"
|
||||
end_point="62.136614,153.00981"
|
||||
center_point="62.136614,113.2598"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1.2"
|
||||
lpesatellites=""
|
||||
mode="vertical"
|
||||
discard_orig_path="false"
|
||||
fuse_paths="false"
|
||||
oposite_fuse="false"
|
||||
split_items="false"
|
||||
split_open="false"
|
||||
link_styles="false" />
|
||||
<inkscape:path-effect
|
||||
effect="mirror_symmetry"
|
||||
start_point="62.136614,73.509798"
|
||||
end_point="62.136614,153.00981"
|
||||
center_point="62.136614,113.2598"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1.2"
|
||||
lpesatellites=""
|
||||
mode="vertical"
|
||||
discard_orig_path="false"
|
||||
fuse_paths="false"
|
||||
oposite_fuse="false"
|
||||
split_items="false"
|
||||
split_open="false"
|
||||
link_styles="false" />
|
||||
<inkscape:path-effect
|
||||
effect="mirror_symmetry"
|
||||
start_point="62.136614,73.509798"
|
||||
end_point="62.136614,153.00981"
|
||||
center_point="62.136614,113.2598"
|
||||
id="path-effect7"
|
||||
is_visible="true"
|
||||
lpeversion="1.2"
|
||||
lpesatellites=""
|
||||
mode="vertical"
|
||||
discard_orig_path="false"
|
||||
fuse_paths="false"
|
||||
oposite_fuse="false"
|
||||
split_items="false"
|
||||
split_open="false"
|
||||
link_styles="false" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Stroke"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
style="display:inline"
|
||||
transform="translate(-75.249994,-72.249998)">
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#a0a0a0;fill-opacity:1;stroke:none;stroke-width:3.5;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 62.136618,151.2598 h 15.000001 c 0,0 13,-12 13,-18 0,-13 -1,-20 -18,-34.000003 v -8 h -4 4 c 5,0 5,-7 0,-7 h -10.000001 7.000001 l 3,-8.999999 H 62.136618 m -8e-6,76.000002 H 47.136609 c 0,0 -13,-12 -13,-18 0,-13 1,-20 18,-34.000003 v -8 h 4 -4 c -5,0 -5,-7 0,-7 H 62.13661 55.136609 l -3,-8.999999 H 62.13661"
|
||||
id="Fill"
|
||||
sodipodi:nodetypes="ccsccccccccc"
|
||||
transform="translate(42.863384,-1.2597997)"
|
||||
inkscape:original-d="m 62.136618,151.2598 h 15.000001 c 0,0 13,-12 13,-18 0,-13 -1,-20 -18,-34.000003 v -8 h -4 4 c 5,0 5,-7 0,-7 h -10.000001 7.000001 l 3,-8.999999 H 62.136618"
|
||||
inkscape:path-effect="#path-effect7"
|
||||
inkscape:highlight-color="#47e661"
|
||||
inkscape:label="Fill" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#83b229;fill-opacity:1;stroke:none;stroke-width:3.5;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 62.136618,151.2598 h 15.000001 c 0,0 13,-12 13,-18 0,-13 -2,-22 -18,-34.000003 -2.345162,0.366674 -4.685658,0.58873 -7.000001,0 -1.716931,-0.618631 -2.013396,-0.567824 -3.000002,-0.649209 m -6e-6,52.649212 H 47.136609 c 0,0 -13,-12 -13,-18 0,-13 2,-22 18,-34.000003 2.345162,0.366674 4.685658,0.58873 7.000001,0 1.716931,-0.618631 2.013396,-0.567824 3.000002,-0.649209"
|
||||
id="path19"
|
||||
sodipodi:nodetypes="ccsccc"
|
||||
transform="translate(42.863384,-1.2597997)"
|
||||
inkscape:original-d="m 62.136618,151.2598 h 15.000001 c 0,0 13,-12 13,-18 0,-13 -2,-22 -18,-34.000003 -2.345162,0.366674 -4.685658,0.58873 -7.000001,0 -1.716931,-0.618631 -2.013396,-0.567824 -3.000002,-0.649209"
|
||||
inkscape:path-effect="#path-effect21"
|
||||
inkscape:highlight-color="#000000"
|
||||
inkscape:label="path19" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#967a3e;fill-opacity:1;stroke:none;stroke-width:3.49999;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 95.000002,73.999997 H 115 l -3,9 H 97.999996 l -2.999994,-9"
|
||||
id="Bouchon"
|
||||
inkscape:highlight-color="#9f633c" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.5;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 62.136618,151.2598 h 15.000001 c 0,0 13,-12 13,-18 0,-13 -1,-20 -18,-34.000003 v -8 h -4 4 c 5,0 5,-7 0,-7 h -10.000001 7.000001 l 3,-8.999999 H 62.136618 m -8e-6,76.000002 H 47.136609 c 0,0 -13,-12 -13,-18 0,-13 1,-20 18,-34.000003 v -8 h 4 -4 c -5,0 -5,-7 0,-7 H 62.13661 55.136609 l -3,-8.999999 H 62.13661"
|
||||
id="Stroke"
|
||||
sodipodi:nodetypes="ccsccccccccc"
|
||||
transform="translate(42.863384,-1.2597997)"
|
||||
inkscape:original-d="m 62.136618,151.2598 h 15.000001 c 0,0 13,-12 13,-18 0,-13 -1,-20 -18,-34.000003 v -8 h -4 4 c 5,0 5,-7 0,-7 h -10.000001 7.000001 l 3,-8.999999 H 62.136618"
|
||||
inkscape:path-effect="#path-effect19"
|
||||
inkscape:highlight-color="#000000" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:6.99998;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path23"
|
||||
cx="101.0117"
|
||||
cy="77.499992"
|
||||
rx="0.99999881"
|
||||
ry="1.0000011" />
|
||||
<ellipse
|
||||
style="display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:6.99998;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path23-9"
|
||||
cx="105"
|
||||
cy="79"
|
||||
rx="0.99999881"
|
||||
ry="1.0000011" />
|
||||
<ellipse
|
||||
style="display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:6.99998;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path23-9-4"
|
||||
cx="108.9883"
|
||||
cy="77.499992"
|
||||
rx="0.99999881"
|
||||
ry="1.0000011" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+116
-59
@@ -30,17 +30,18 @@
|
||||
*/
|
||||
|
||||
/* global define */
|
||||
(function(root, factory) { // eslint-disable-line
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
(function (root, factory) {
|
||||
// eslint-disable-line
|
||||
if (typeof define === "function" && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([], function() {
|
||||
define([], function () {
|
||||
return factory.call(root);
|
||||
});
|
||||
} else {
|
||||
// Browser globals
|
||||
root.webglUtils = factory.call(root);
|
||||
}
|
||||
}(this, function() {
|
||||
})(this, function () {
|
||||
"use strict";
|
||||
|
||||
const topWindow = this;
|
||||
@@ -53,8 +54,8 @@
|
||||
}
|
||||
|
||||
if (!isInIFrame()) {
|
||||
console.log("%c%s", 'color:blue;font-weight:bold;', 'for more about webgl-utils.js see:'); // eslint-disable-line
|
||||
console.log("%c%s", 'color:blue;font-weight:bold;', 'http://webgl2fundamentals.org/webgl/lessons/webgl-boilerplate.html'); // eslint-disable-line
|
||||
// console.log("%c%s", 'color:blue;font-weight:bold;', 'for more about webgl-utils.js see:'); // eslint-disable-line
|
||||
// console.log("%c%s", 'color:blue;font-weight:bold;', 'http://webgl2fundamentals.org/webgl/lessons/webgl-boilerplate.html'); // eslint-disable-line
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,23 +73,27 @@
|
||||
}
|
||||
|
||||
const errorRE = /ERROR:\s*\d+:(\d+)/gi;
|
||||
function addLineNumbersWithError(src, log = '') {
|
||||
function addLineNumbersWithError(src, log = "") {
|
||||
// Note: Error message formats are not defined by any spec so this may or may not work.
|
||||
const matches = [...log.matchAll(errorRE)];
|
||||
const lineNoToErrorMap = new Map(matches.map((m, ndx) => {
|
||||
const lineNo = parseInt(m[1]);
|
||||
const next = matches[ndx + 1];
|
||||
const end = next ? next.index : log.length;
|
||||
const msg = log.substring(m.index, end);
|
||||
return [lineNo - 1, msg];
|
||||
}));
|
||||
return src.split('\n').map((line, lineNo) => {
|
||||
const err = lineNoToErrorMap.get(lineNo);
|
||||
return `${lineNo + 1}: ${line}${err ? `\n\n^^^ ${err}` : ''}`;
|
||||
}).join('\n');
|
||||
const lineNoToErrorMap = new Map(
|
||||
matches.map((m, ndx) => {
|
||||
const lineNo = parseInt(m[1]);
|
||||
const next = matches[ndx + 1];
|
||||
const end = next ? next.index : log.length;
|
||||
const msg = log.substring(m.index, end);
|
||||
return [lineNo - 1, msg];
|
||||
})
|
||||
);
|
||||
return src
|
||||
.split("\n")
|
||||
.map((line, lineNo) => {
|
||||
const err = lineNoToErrorMap.get(lineNo);
|
||||
return `${lineNo + 1}: ${line}${err ? `\n\n^^^ ${err}` : ""}`;
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Error Callback
|
||||
* @callback ErrorCallback
|
||||
@@ -120,7 +125,12 @@
|
||||
if (!compiled) {
|
||||
// Something went wrong during compilation; get the error
|
||||
const lastError = gl.getShaderInfoLog(shader);
|
||||
errFn(`Error compiling shader: ${lastError}\n${addLineNumbersWithError(shaderSource, lastError)}`);
|
||||
errFn(
|
||||
`Error compiling shader: ${lastError}\n${addLineNumbersWithError(
|
||||
shaderSource,
|
||||
lastError
|
||||
)}`
|
||||
);
|
||||
gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
@@ -139,18 +149,24 @@
|
||||
* @memberOf module:webgl-utils
|
||||
*/
|
||||
function createProgram(
|
||||
gl, shaders, opt_attribs, opt_locations, opt_errorCallback) {
|
||||
gl,
|
||||
shaders,
|
||||
opt_attribs,
|
||||
opt_locations,
|
||||
opt_errorCallback
|
||||
) {
|
||||
const errFn = opt_errorCallback || error;
|
||||
const program = gl.createProgram();
|
||||
shaders.forEach(function(shader) {
|
||||
shaders.forEach(function (shader) {
|
||||
gl.attachShader(program, shader);
|
||||
});
|
||||
if (opt_attribs) {
|
||||
opt_attribs.forEach(function(attrib, ndx) {
|
||||
opt_attribs.forEach(function (attrib, ndx) {
|
||||
gl.bindAttribLocation(
|
||||
program,
|
||||
opt_locations ? opt_locations[ndx] : ndx,
|
||||
attrib);
|
||||
program,
|
||||
opt_locations ? opt_locations[ndx] : ndx,
|
||||
attrib
|
||||
);
|
||||
});
|
||||
}
|
||||
gl.linkProgram(program);
|
||||
@@ -158,18 +174,20 @@
|
||||
// Check the link status
|
||||
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||
if (!linked) {
|
||||
// something went wrong with the link
|
||||
const lastError = gl.getProgramInfoLog(program);
|
||||
errFn(`Error in program linking: ${lastError}\n${
|
||||
shaders.map(shader => {
|
||||
// something went wrong with the link
|
||||
const lastError = gl.getProgramInfoLog(program);
|
||||
errFn(
|
||||
`Error in program linking: ${lastError}\n${shaders
|
||||
.map((shader) => {
|
||||
const src = addLineNumbersWithError(gl.getShaderSource(shader));
|
||||
const type = gl.getShaderParameter(shader, gl.SHADER_TYPE);
|
||||
return `${glEnumToString(gl, type)}:\n${src}`;
|
||||
}).join('\n')
|
||||
}`);
|
||||
})
|
||||
.join("\n")}`
|
||||
);
|
||||
|
||||
gl.deleteProgram(program);
|
||||
return null;
|
||||
gl.deleteProgram(program);
|
||||
return null;
|
||||
}
|
||||
return program;
|
||||
}
|
||||
@@ -184,12 +202,16 @@
|
||||
* @return {WebGLShader} The created shader.
|
||||
*/
|
||||
function createShaderFromScript(
|
||||
gl, scriptId, opt_shaderType, opt_errorCallback) {
|
||||
gl,
|
||||
scriptId,
|
||||
opt_shaderType,
|
||||
opt_errorCallback
|
||||
) {
|
||||
let shaderSource = "";
|
||||
let shaderType;
|
||||
const shaderScript = document.getElementById(scriptId);
|
||||
if (!shaderScript) {
|
||||
throw ("*** Error: unknown script element" + scriptId);
|
||||
throw "*** Error: unknown script element" + scriptId;
|
||||
}
|
||||
shaderSource = shaderScript.text;
|
||||
|
||||
@@ -198,20 +220,23 @@
|
||||
shaderType = gl.VERTEX_SHADER;
|
||||
} else if (shaderScript.type === "x-shader/x-fragment") {
|
||||
shaderType = gl.FRAGMENT_SHADER;
|
||||
} else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) {
|
||||
throw ("*** Error: unknown shader type");
|
||||
} else if (
|
||||
shaderType !== gl.VERTEX_SHADER &&
|
||||
shaderType !== gl.FRAGMENT_SHADER
|
||||
) {
|
||||
throw "*** Error: unknown shader type";
|
||||
}
|
||||
}
|
||||
|
||||
return loadShader(
|
||||
gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
|
||||
opt_errorCallback);
|
||||
gl,
|
||||
shaderSource,
|
||||
opt_shaderType ? opt_shaderType : shaderType,
|
||||
opt_errorCallback
|
||||
);
|
||||
}
|
||||
|
||||
const defaultShaderType = [
|
||||
"VERTEX_SHADER",
|
||||
"FRAGMENT_SHADER",
|
||||
];
|
||||
const defaultShaderType = ["VERTEX_SHADER", "FRAGMENT_SHADER"];
|
||||
|
||||
/**
|
||||
* Creates a program from 2 script tags.
|
||||
@@ -229,13 +254,30 @@
|
||||
* @memberOf module:webgl-utils
|
||||
*/
|
||||
function createProgramFromScripts(
|
||||
gl, shaderScriptIds, opt_attribs, opt_locations, opt_errorCallback) {
|
||||
gl,
|
||||
shaderScriptIds,
|
||||
opt_attribs,
|
||||
opt_locations,
|
||||
opt_errorCallback
|
||||
) {
|
||||
const shaders = [];
|
||||
for (let ii = 0; ii < shaderScriptIds.length; ++ii) {
|
||||
shaders.push(createShaderFromScript(
|
||||
gl, shaderScriptIds[ii], gl[defaultShaderType[ii]], opt_errorCallback));
|
||||
shaders.push(
|
||||
createShaderFromScript(
|
||||
gl,
|
||||
shaderScriptIds[ii],
|
||||
gl[defaultShaderType[ii]],
|
||||
opt_errorCallback
|
||||
)
|
||||
);
|
||||
}
|
||||
return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
|
||||
return createProgram(
|
||||
gl,
|
||||
shaders,
|
||||
opt_attribs,
|
||||
opt_locations,
|
||||
opt_errorCallback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,13 +296,30 @@
|
||||
* @memberOf module:webgl-utils
|
||||
*/
|
||||
function createProgramFromSources(
|
||||
gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {
|
||||
gl,
|
||||
shaderSources,
|
||||
opt_attribs,
|
||||
opt_locations,
|
||||
opt_errorCallback
|
||||
) {
|
||||
const shaders = [];
|
||||
for (let ii = 0; ii < shaderSources.length; ++ii) {
|
||||
shaders.push(loadShader(
|
||||
gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback));
|
||||
shaders.push(
|
||||
loadShader(
|
||||
gl,
|
||||
shaderSources[ii],
|
||||
gl[defaultShaderType[ii]],
|
||||
opt_errorCallback
|
||||
)
|
||||
);
|
||||
}
|
||||
return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
|
||||
return createProgram(
|
||||
gl,
|
||||
shaders,
|
||||
opt_attribs,
|
||||
opt_locations,
|
||||
opt_errorCallback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,10 +332,10 @@
|
||||
*/
|
||||
function resizeCanvasToDisplaySize(canvas, multiplier) {
|
||||
multiplier = multiplier || 1;
|
||||
const width = canvas.clientWidth * multiplier | 0;
|
||||
const height = canvas.clientHeight * multiplier | 0;
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width;
|
||||
const width = (canvas.clientWidth * multiplier) | 0;
|
||||
const height = (canvas.clientHeight * multiplier) | 0;
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return true;
|
||||
}
|
||||
@@ -289,6 +348,4 @@
|
||||
createProgramFromSources: createProgramFromSources,
|
||||
resizeCanvasToDisplaySize: resizeCanvasToDisplaySize,
|
||||
};
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { App, Component, DefineComponent } from "vue";
|
||||
|
||||
export const cards = {} as Record<string, Component>;
|
||||
export const tokens = {} as Record<string, Component>;
|
||||
|
||||
export async function compileCards() {
|
||||
const svgs = import.meta.glob("@/assets/images/cards/heron-svg/*.svg", {
|
||||
eager: true,
|
||||
});
|
||||
|
||||
const components = await Promise.all(
|
||||
Object.entries(svgs).map(async ([k, v]) => {
|
||||
const regex = /([\d]+)(?=\.svg)/gm;
|
||||
const url = (v as any).default as string;
|
||||
try {
|
||||
let template: string;
|
||||
if (process.dev && !process.client) {
|
||||
template = (await import(/* @vite-ignore */ k + "?raw")).default;
|
||||
} else {
|
||||
template = await (await fetch(url)).text();
|
||||
}
|
||||
|
||||
template = template.replace(/\<\?.*\?\>/gm, "");
|
||||
|
||||
return [
|
||||
Number(k.match(regex)![0]).toString(),
|
||||
// markRaw(
|
||||
// defineAsyncComponent({
|
||||
// loader: async () =>
|
||||
// defineComponent({
|
||||
// props: ["used_effects_indexes", "cardId"],
|
||||
// methods: {
|
||||
// effectClick(effect: number) {
|
||||
// this.$emit("effectClick", effect);
|
||||
// },
|
||||
// },
|
||||
// template: template,
|
||||
// }),
|
||||
// })
|
||||
// ),
|
||||
markRaw(
|
||||
defineComponent({
|
||||
props: ["used_effects_indexes", "cardId"],
|
||||
methods: {
|
||||
effectClick(effect: number) {
|
||||
this.$emit("effectClick", effect);
|
||||
},
|
||||
},
|
||||
template: template,
|
||||
})
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
console.error(e, k);
|
||||
return [];
|
||||
}
|
||||
// let arrayTemplate = template.split("\n");
|
||||
// arrayTemplate.splice(0, 1);
|
||||
// template = arrayTemplate.join("\n");
|
||||
})
|
||||
);
|
||||
|
||||
Object.assign(cards, Object.fromEntries(components));
|
||||
}
|
||||
|
||||
export async function compileTokens() {
|
||||
const svgs = import.meta.glob("@/assets/images/tokens/*.svg", {
|
||||
eager: true,
|
||||
});
|
||||
|
||||
const components = await Promise.all(
|
||||
Object.entries(svgs).map(async ([k, v]) => {
|
||||
const regex = /([\w]+)(?=\.svg)/gm;
|
||||
const url = (v as any).default as string;
|
||||
try {
|
||||
let template: string;
|
||||
if (process.dev && !process.client) {
|
||||
template = (await import(/* @vite-ignore */ k + "?raw")).default;
|
||||
} else {
|
||||
template = await (await fetch(url)).text();
|
||||
}
|
||||
template = template.replace(/\<\?.*\?\>/gm, "");
|
||||
return [
|
||||
k.match(regex)![0].toString(),
|
||||
markRaw(
|
||||
defineComponent({
|
||||
template: template,
|
||||
})
|
||||
),
|
||||
];
|
||||
} catch (e) {
|
||||
console.error(e, k);
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
Object.assign(tokens, Object.fromEntries(components));
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { whoami } from "~/netcode";
|
||||
|
||||
export const useAuthStore = defineStore("auth", {
|
||||
state: () => ({
|
||||
selfUser: {} as { userId?: string; image?: string; username?: string },
|
||||
logged: false,
|
||||
}),
|
||||
actions: {
|
||||
async whoami() {
|
||||
try {
|
||||
const data = await whoami().then((data) => {
|
||||
this.logged = true;
|
||||
return data;
|
||||
});
|
||||
|
||||
Object.assign(this.selfUser, data.value);
|
||||
} catch (e) {}
|
||||
},
|
||||
oauth2_register_discord: () => {
|
||||
throw Error("not implemented");
|
||||
},
|
||||
},
|
||||
});
|
||||
+27
-18
@@ -1,25 +1,34 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
enum CardStyles {
|
||||
HERON = "heron",
|
||||
ORIGINAL = "original"
|
||||
export enum CardStyles {
|
||||
HERON = "heron",
|
||||
ORIGINAL = "original",
|
||||
SVG = "heron-svg",
|
||||
}
|
||||
|
||||
export const useSettingsStore = defineStore("settingstore", {
|
||||
state: () => ({
|
||||
cardStyle: CardStyles.HERON,
|
||||
shaderQuality: 3,
|
||||
shaderFramerate: 0,
|
||||
shaderPixelated: true
|
||||
}),
|
||||
actions: {
|
||||
switchCardStyles() {
|
||||
if (this.cardStyle === CardStyles.HERON) {
|
||||
this.cardStyle = CardStyles.ORIGINAL
|
||||
} else {
|
||||
this.cardStyle = CardStyles.HERON
|
||||
}
|
||||
}
|
||||
state: () => ({
|
||||
cardStyle: CardStyles.SVG,
|
||||
animationSpeed: 100,
|
||||
fastAnimations: false,
|
||||
shaderQuality: 3,
|
||||
shaderFramerate: 10,
|
||||
shaderPixelated: true,
|
||||
saveDiscardSize: false,
|
||||
discardFullscreen: false,
|
||||
}),
|
||||
actions: {
|
||||
switchCardStyles() {
|
||||
if (this.cardStyle === CardStyles.HERON) {
|
||||
this.cardStyle = CardStyles.ORIGINAL;
|
||||
} else if (this.cardStyle === CardStyles.ORIGINAL) {
|
||||
this.cardStyle = CardStyles.SVG;
|
||||
} else {
|
||||
this.cardStyle = CardStyles.HERON;
|
||||
}
|
||||
},
|
||||
persist: true
|
||||
},
|
||||
persist: {
|
||||
storage: persistedState.localStorage,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { defineStore } from "pinia";
|
||||
import goStore, { useEffect } from "~/netcode";
|
||||
import type { Card } from "~/netcode/Card";
|
||||
import { CardEffects, type Effect } from "~/netcode/Effect";
|
||||
import type { Player } from "~/netcode/Player";
|
||||
|
||||
watch(
|
||||
() => goStore.current_game?.current_turn?.uuid,
|
||||
() => {
|
||||
useClientSideStore().activatedClientside = []
|
||||
})
|
||||
|
||||
export const useClientSideStore = defineStore("clientside", {
|
||||
state: () => ({
|
||||
activatedClientside: [] as Effect[],
|
||||
effectsHandled: new Set([
|
||||
CardEffects.MAKE_DISCARD,
|
||||
CardEffects.PLAY_NEXT_CARD_BOUGHT,
|
||||
CardEffects.RESTACK_DISCARDED_CARD,
|
||||
CardEffects.RESTACK_DISCARDED_CHAMPION,
|
||||
CardEffects.SACRIFICE,
|
||||
CardEffects.STACK_NEXT_ACTION_BOUGHT,
|
||||
CardEffects.STACK_NEXT_CARD_BOUGHT,
|
||||
CardEffects.STUN,
|
||||
CardEffects.PREPARE
|
||||
]),
|
||||
}),
|
||||
getters: {
|
||||
maskedServerEffects(state) {
|
||||
return goStore.self?.turn?.effects_availables.filter(e => !state.activatedClientside.includes(e));
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
activateEffect(effect: Effect) {
|
||||
const amount =
|
||||
goStore.self?.turn?.effects_availables.filter((e) => e == effect)
|
||||
.length ?? 0;
|
||||
for (let i = 0; i < amount; i++) {
|
||||
this.activatedClientside.push(effect);
|
||||
}
|
||||
},
|
||||
useEffect(effect: Effect, target: Player | Card) {
|
||||
const index = this.activatedClientside.indexOf(effect);
|
||||
if (index > -1) {
|
||||
this.activatedClientside.splice(index, 1);
|
||||
useEffect(effect, target);
|
||||
}
|
||||
},
|
||||
findUseEffect(cardEffect: CardEffects, target: Player | Card) {
|
||||
const effect = this.activatedClientside.find(e => (e.effect == cardEffect) && e.usable)
|
||||
if (!effect) { return }
|
||||
this.useEffect(effect, target)
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
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";
|
||||
// import type { Player } from "~/netcode/Player";
|
||||
|
||||
// watch(
|
||||
// () => goStore.current_game?.current_turn?.uuid,
|
||||
// () => {
|
||||
// useClientSideStore().activatedClientside = []
|
||||
// })
|
||||
|
||||
export const useTurnStore = defineStore("turn", {
|
||||
// state: () => ({
|
||||
// activatedClientside: [] as Effect[],
|
||||
// effectsHandled: new Set([
|
||||
// CardEffects.MAKE_DISCARD,
|
||||
// CardEffects.PLAY_NEXT_CARD_BOUGHT,
|
||||
// CardEffects.RESTACK_DISCARDED_CARD,
|
||||
// CardEffects.RESTACK_DISCARDED_CHAMPION,
|
||||
// CardEffects.SACRIFICE,
|
||||
// CardEffects.STACK_NEXT_ACTION_BOUGHT,
|
||||
// CardEffects.STACK_NEXT_CARD_BOUGHT,
|
||||
// CardEffects.STUN,
|
||||
// CardEffects.PREPARE
|
||||
// ]),
|
||||
// }),
|
||||
// getters: {
|
||||
// maskedServerEffects(state) {
|
||||
// return goStore.self?.turn?.effects_availables.filter(e => !state.activatedClientside.includes(e));
|
||||
// },
|
||||
// },
|
||||
// actions: {
|
||||
// activateEffect(effect: Effect) {
|
||||
// const amount =
|
||||
// goStore.self?.turn?.effects_availables.filter((e) => e == effect)
|
||||
// .length ?? 0;
|
||||
// for (let i = 0; i < amount; i++) {
|
||||
// this.activatedClientside.push(effect);
|
||||
// }
|
||||
// },
|
||||
// useEffect(effect: Effect, target: Player | Card) {
|
||||
// const index = this.activatedClientside.indexOf(effect);
|
||||
// if (index > -1) {
|
||||
// this.activatedClientside.splice(index, 1);
|
||||
// useEffect(effect, target);
|
||||
// }
|
||||
// },
|
||||
// findUseEffect(cardEffect: CardEffects, target: Player | Card) {
|
||||
// const effect = this.activatedClientside.find(e => (e.effect == cardEffect) && e.usable)
|
||||
// if (!effect) { return }
|
||||
// this.useEffect(effect, target)
|
||||
// }
|
||||
// },
|
||||
});
|
||||
Reference in New Issue
Block a user