Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83fe97a427 | ||
|
|
4d66dea14c | ||
|
|
6ae1ce701d | ||
|
|
7d7f22a82c | ||
|
|
aa8332cb20 | ||
|
|
aa610a58a5 | ||
|
|
443bfc128a | ||
|
|
a3630abf92 | ||
|
|
74d99aff6e | ||
|
|
76814efb18 | ||
|
|
571a35633b | ||
|
|
5fa34eda14 | ||
|
|
37ad826a80 | ||
|
|
3f8a580984 | ||
|
|
b5f4d91ff8 | ||
|
|
351cc06495 | ||
|
|
4ea5c25a47 | ||
|
|
766dc92931 | ||
|
|
f570b456ca | ||
|
|
8c7cc19991 | ||
|
|
df6d77b870 | ||
|
|
b2a3d4f701 | ||
|
|
6291989dd4 | ||
|
|
a6fcc28ced | ||
|
|
66b6cc61e9 | ||
|
|
90ed8a6211 | ||
|
|
ceab43e861 | ||
|
|
ffd9f0187d | ||
|
|
0305dbe9b7 | ||
|
|
db4aac845c | ||
|
|
bd6978d392 | ||
|
|
310b3e7b17 | ||
|
|
ca87817b33 | ||
|
|
6f7b1bdf03 | ||
|
|
17a1d23768 | ||
|
|
0915c0a8d8 | ||
|
|
7ae052ee8b | ||
|
|
d9d8a7a8f0 |
@@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DOCKER_ORG: legonzaur
|
DOCKER_ORG: heronfief
|
||||||
DOCKER_LATEST: nightly
|
DOCKER_LATEST: nightly
|
||||||
ENDPOINT: git.legonzaur.fr
|
ENDPOINT: git.legonzaur.fr
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import goStore, { oauth2_register_discord, setup_websocket, heron_cookie_login } from "@/netcode";
|
import goStore, { oauth2_register_discord, setup_websocket, heron_cookie_login } from "@/netcode";
|
||||||
|
|
||||||
await setup_websocket()
|
const router = useRouter();
|
||||||
|
|
||||||
useHead
|
useHead
|
||||||
({
|
({
|
||||||
@@ -10,27 +10,6 @@ useHead
|
|||||||
: 'Héron Realms',
|
: '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) {
|
function keyDownHandler(e: KeyboardEvent) {
|
||||||
if (e.repeat) { return }
|
if (e.repeat) { return }
|
||||||
@@ -85,8 +64,6 @@ onUnmounted(() => {
|
|||||||
window.removeEventListener("mouseup", mouseUpHandler)
|
window.removeEventListener("mouseup", mouseUpHandler)
|
||||||
document.removeEventListener("mousemove", follow_mouse)
|
document.removeEventListener("mousemove", follow_mouse)
|
||||||
})
|
})
|
||||||
|
|
||||||
onDeactivated
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="root_dom_div">
|
<div class="root_dom_div">
|
||||||
|
|||||||
@@ -15,20 +15,22 @@ uniform float u_time;
|
|||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
vec2 st=(gl_FragCoord.xy+u_offset)/u_resolution;
|
vec2 st=(gl_FragCoord.xy+u_offset)/u_resolution;
|
||||||
|
|
||||||
|
|
||||||
vec2 q=vec2(0.);
|
vec2 q=vec2(0.);
|
||||||
q.x=fbm(st);
|
q.x=fbm(st);
|
||||||
q.y=fbm(st+vec2(1.));
|
q.y=fbm(st+vec2(1.));
|
||||||
|
|
||||||
q=q*.5+.5;
|
q=q*.5+.5;
|
||||||
vec2 r=vec2(0.);
|
vec2 r=vec2(0.);
|
||||||
r.x=fbm(st+1.*q+vec2(1.7,9.2)+.00515*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)+.0126*u_time);
|
r.y=fbm(st+1.*q+vec2(8.3,2.8)+.00526*u_time);
|
||||||
r=r*.5+.5;
|
r=r*.5+.5;
|
||||||
|
|
||||||
float f=fbm(st+r);
|
float f=fbm(st+r);
|
||||||
|
f = floor((f+.4)*4.)/4.;
|
||||||
vec3 color=mix(u_color_start,u_color_end,f*.3);
|
vec3 color=mix(u_color_start,u_color_end,f*.3);
|
||||||
|
|
||||||
gl_FragColor=vec4(color,1.);
|
gl_FragColor=vec4(color,1.);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import noise from "~/assets/shaders/noise.frag?raw"
|
||||||
|
|
||||||
|
import resolveLygia from "~/assets/workers/resolve-lygia.esm"
|
||||||
|
|
||||||
|
let interval: number
|
||||||
|
let timeout: number
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
let fragmentShader = resolveLygia(noise);
|
||||||
|
|
||||||
|
let canvas: OffscreenCanvas
|
||||||
|
|
||||||
|
let render: (time: number) => void
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
function getFile(url) {
|
||||||
|
let httpRequest = new XMLHttpRequest();
|
||||||
|
httpRequest.open("GET", url, false);
|
||||||
|
httpRequest.send();
|
||||||
|
if (httpRequest.status == 200)
|
||||||
|
return httpRequest.responseText;
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveLygia(lines) {
|
||||||
|
if ( !Array.isArray(lines) ) {
|
||||||
|
lines = lines.split(/\r?\n/);
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "";
|
||||||
|
lines.forEach( (line, i) => {
|
||||||
|
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,'');
|
||||||
|
src += getFile(include_url) + '\n';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
src += line + '\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveLygiaAsync(lines) {
|
||||||
|
if (!Array.isArray(lines))
|
||||||
|
lines = lines.split(/\r?\n/);
|
||||||
|
|
||||||
|
let src = "";
|
||||||
|
const response = await Promise.all(
|
||||||
|
lines.map(async (line, i) => {
|
||||||
|
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, "");
|
||||||
|
return fetch(include_url).then((res) => res.text());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return line;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.join("\n");
|
||||||
|
}
|
||||||
|
export { resolveLygia as default };
|
||||||
@@ -1,151 +1,121 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import noise from "~/assets/shaders/noise.frag?raw"
|
|
||||||
|
|
||||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
declare const webglUtils: {
|
import gsap from 'gsap';
|
||||||
createProgramFromSources: (gl: WebGLRenderingContext, array: [string, string]) => WebGLProgram,
|
import ShaderWorker from '~/assets/workers/ShaderWorker?worker'
|
||||||
resizeCanvasToDisplaySize: (e: HTMLCanvasElement | OffscreenCanvas) => any
|
import goStore from '~/netcode';
|
||||||
}
|
const worker = new ShaderWorker()
|
||||||
declare const resolveLygiaAsync: (source: string) => Promise<string>
|
|
||||||
|
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||||
|
const canvas_resize_temp = ref<HTMLCanvasElement | null>(null)
|
||||||
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
colorStart?: [number, number, number]
|
colorStart?: [number, number, number]
|
||||||
colorEnd?: [number, number, number]
|
colorEnd?: [number, number, number]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
colorStart: () => [0, .3, 0],
|
colorStart: () => [0, .3, 0],
|
||||||
colorEnd: () => [0, 1, 0]
|
colorEnd: () => [0, 1, 0]
|
||||||
})
|
})
|
||||||
|
|
||||||
const targetFramerate = 1000 / settingsStore.shaderFramerate
|
const _colorStart = reactive(props.colorStart)
|
||||||
|
const _colorEnd = reactive(props.colorEnd)
|
||||||
|
|
||||||
const interval = ref<ReturnType<typeof setInterval>>()
|
|
||||||
|
|
||||||
let fragmentShader = await resolveLygiaAsync(noise);
|
function initShader() {
|
||||||
|
if (!canvas.value) { return }
|
||||||
async function main(canvas: HTMLCanvasElement, fragmentShader2: string) {
|
const offscreen = canvas.value?.transferControlToOffscreen()
|
||||||
// Get A WebGL context
|
worker.postMessage({
|
||||||
/** @type {HTMLCanvasElement} */
|
command: "initShader",
|
||||||
const gl = canvas.getContext("webgl");
|
canvas: offscreen,
|
||||||
if (!gl) {
|
screen: { height: window.innerHeight * devicePixelRatio, width: window.innerWidth * devicePixelRatio, devicePixelRatio },
|
||||||
return;
|
shaderOptions: { quality: settingsStore.shaderQuality, framerate: settingsStore.shaderFramerate },
|
||||||
|
}, [offscreen])
|
||||||
|
if (settingsStore.shaderFramerate == 0) {
|
||||||
|
killShader()
|
||||||
}
|
}
|
||||||
|
resizeShader()
|
||||||
const vs = `attribute vec4 a_position;void main() {gl_Position = a_position;}`;
|
|
||||||
|
|
||||||
// setup GLSL program
|
|
||||||
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) {
|
|
||||||
time *= 0.001; // convert to seconds
|
|
||||||
webglUtils.resizeCanvasToDisplaySize(gl2.canvas);
|
|
||||||
|
|
||||||
// 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 bounds = canvas.getBoundingClientRect()
|
|
||||||
const aspectRatio = Math.max(window.screen.width, window.screen.height) * devicePixelRatio
|
|
||||||
|
|
||||||
gl2.uniform2f(offset, bounds.left, -bounds.bottom);
|
|
||||||
// console.log(canvas.getBoundingClientRect().left * window.devicePixelRatio)
|
|
||||||
gl2.uniform1i(quality, settingsStore.shaderQuality)
|
|
||||||
gl2.uniform2f(resolutionLocation, aspectRatio, aspectRatio);
|
|
||||||
gl2.uniform3f(colorStart, props.colorStart[0], props.colorStart[1], props.colorStart[2]);
|
|
||||||
gl2.uniform3f(colorEnd, props.colorEnd[0], props.colorEnd[1], props.colorEnd[2]);
|
|
||||||
gl2.uniform1f(timeLocation, time);
|
|
||||||
gl2.drawArrays(
|
|
||||||
gl2.TRIANGLES,
|
|
||||||
0, // offset
|
|
||||||
6, // num vertices to process
|
|
||||||
);
|
|
||||||
}
|
|
||||||
clearInterval(interval.value)
|
|
||||||
interval.value = setInterval(() => requestAnimationFrame(render), targetFramerate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function recolorShader() {
|
||||||
async function reloadShader() {
|
worker.postMessage({
|
||||||
nextTick(() => {
|
command: 'recolorShader',
|
||||||
if (!canvas.value) { return }
|
colorStart: [..._colorStart],
|
||||||
let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + settingsStore.shaderQuality);
|
colorEnd: [..._colorEnd]
|
||||||
clearInterval(interval.value)
|
|
||||||
main(canvas.value, fragmentShader2);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
watch(() => settingsStore.shaderQuality, (newValue, oldValue) => {
|
|
||||||
reloadShader()
|
function resizeShader() {
|
||||||
|
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'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeShader() {
|
||||||
|
worker.postMessage({
|
||||||
|
command: 'resumeShader',
|
||||||
|
shaderOptions: { quality: settingsStore.shaderQuality, framerate: settingsStore.shaderFramerate },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => [settingsStore.shaderFramerate], () => {
|
||||||
|
if (settingsStore.shaderFramerate == 0) {
|
||||||
|
killShader()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resumeShader()
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => settingsStore.shaderFramerate, (newValue, oldValue) => {
|
watch(() => [settingsStore.shaderQuality], () => {
|
||||||
reloadShader()
|
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 })
|
||||||
|
} 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 })
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
|
||||||
|
watch([_colorEnd, _colorStart], () => {
|
||||||
|
recolorShader()
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
reloadShader()
|
nextTick(() => {
|
||||||
|
recolorShader()
|
||||||
|
initShader()
|
||||||
|
window.addEventListener("resize", resizeShader, true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clearInterval(interval.value)
|
window.removeEventListener("resize", resizeShader)
|
||||||
|
killShader()
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<canvas v-if="settingsStore.shaderQuality != 0" ref="canvas" id="background">test</canvas>
|
<canvas ref="canvas" id="background" :class="{ pixelated: settingsStore.shaderPixelated }" width="480"
|
||||||
|
height="270">test</canvas>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -154,5 +124,10 @@ onUnmounted(() => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
image-rendering: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
#background.pixelated {
|
||||||
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
+56
-14
@@ -11,7 +11,8 @@ export interface Props {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
wrapped: () => false,
|
wrapped: () => false,
|
||||||
tilted: () => true
|
tilted: () => true,
|
||||||
|
locked: () => true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -21,6 +22,8 @@ const settingsStore = useSettingsStore()
|
|||||||
|
|
||||||
const childCount = ref<number>(0)
|
const childCount = ref<number>(0)
|
||||||
|
|
||||||
|
const card = ref<HTMLElement>()
|
||||||
|
|
||||||
function cardClick() {
|
function cardClick() {
|
||||||
if (!props.card_id) return
|
if (!props.card_id) return
|
||||||
emit("click")
|
emit("click")
|
||||||
@@ -41,10 +44,15 @@ watch(
|
|||||||
props.wrapped ? rotation.value = (Math.random() * 4) - 2 : rotation.value = Math.random() > .5 ? 2 : -2
|
props.wrapped ? rotation.value = (Math.random() * 4) - 2 : rotation.value = Math.random() > .5 ? 2 : -2
|
||||||
},
|
},
|
||||||
{ immediate: true })
|
{ immediate: true })
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
card.value?.style.setProperty("--random-seed", -Math.random() * 10 + 's')
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="`card ${childCount > 0 ? 'active' : ''} ${wrapped ? 'wrapped' : ''} ${props.locked ? 'locked' : ''}`"
|
<div ref="card" class="card" :class="{ active: childCount > 0, wrapped, unlocked: !props.locked }"
|
||||||
@contextmenu.prevent="false" @click.left="cardClick" :style="tilted ? `rotate:${rotation}deg;` : ''"
|
@contextmenu.prevent="false" @click.left="cardClick" :style="tilted ? `rotate:${rotation}deg;` : ''"
|
||||||
@mouseenter="mouseenter" @mouseleave="mouseleave">
|
@mouseenter="mouseenter" @mouseleave="mouseleave">
|
||||||
<div id="contextMenuButtons" :ref="(el) => { childCount = ((el as Element)?.childElementCount ?? 0) }">
|
<div id="contextMenuButtons" :ref="(el) => { childCount = ((el as Element)?.childElementCount ?? 0) }">
|
||||||
@@ -66,15 +74,18 @@ watch(
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.card {
|
||||||
transition: 0.025s rotate linear;
|
--max-height: 250px;
|
||||||
|
--margin-bottom: calc(var(--max-height) * 0.05);
|
||||||
max-width: 256px;
|
max-width: 256px;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
margin-top: 15px;
|
margin-top: var(--margin-bottom);
|
||||||
transition: all .125s cubic-bezier(0.075, 0.82, 0.165, 1);
|
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;
|
isolation: isolate;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
max-height: var(--max-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card.active {
|
.card.active {
|
||||||
@@ -82,9 +93,9 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card_image {
|
.card_image {
|
||||||
max-height: 250px;
|
max-height: var(--max-height);
|
||||||
transition: all .125s cubic-bezier(0.075, 0.82, 0.165, 1);
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
height: var(--max-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card_image.outlined {
|
.card_image.outlined {
|
||||||
@@ -92,18 +103,49 @@ watch(
|
|||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card.locked {
|
@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-top: 0px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: var(--margin-bottom);
|
||||||
filter: drop-shadow(5px 15px 2px rgba(0, 0, 0, .60));
|
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 {
|
.card.wrapped {
|
||||||
max-width: 10px;
|
max-height: 6px;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card_image {
|
.card.wrapped .card_image {
|
||||||
max-height: 250px;
|
filter: drop-shadow(.5px .5px .5px gray) drop-shadow(-.5px -.5px .5px gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
#contextMenuButtons {
|
#contextMenuButtons {
|
||||||
@@ -115,7 +157,7 @@ watch(
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 99%;
|
width: 99%;
|
||||||
transform: translateY(-0px);
|
transform: translateY(-0px);
|
||||||
transition: 0.1s transform cubic-bezier(0.075, 0.82, 0.165, 1);
|
transition: 0.1s transform cubic-bezier(1, 1.81, .59, 1.42);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card.active:hover #contextMenuButtons {
|
.card.active:hover #contextMenuButtons {
|
||||||
|
|||||||
+16
-19
@@ -12,41 +12,38 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="cards_container">
|
||||||
<div class="cards_container">
|
<template v-if="props.stack.length > 0" v-for="c in props.stack">
|
||||||
<template v-if="props.stack.length > 0" v-for="c in props.stack">
|
<CardPreview :card_id="c?.card_id" :wrapped="true">
|
||||||
<CardPreview :card_id="c?.card_id" :wrapped="true">
|
<slot></slot>
|
||||||
<slot></slot>
|
</CardPreview>
|
||||||
</CardPreview>
|
</template>
|
||||||
</template>
|
<template v-if="props.stack.length == 0">
|
||||||
<template v-if="props.stack.length == 0">
|
<div class="empty">
|
||||||
<div class="empty">
|
<img :class="`card_image`" :src="'/cards/background.png'" draggable="false">
|
||||||
<img :class="`card_image`" :src="'/cards/background.png'" draggable="false">
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.cards_container::before {
|
.cards_container::before {
|
||||||
width: calc(185px - 10px);
|
height: calc(250px - 10px);
|
||||||
display: block;
|
display: block;
|
||||||
content: "";
|
content: "";
|
||||||
height: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cards_container {
|
.cards_container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: column-reverse;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
max-width: 10px;
|
max-height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty>.card_image {
|
.empty>.card_image {
|
||||||
|
|||||||
@@ -1,128 +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">
|
|
||||||
<div id="market">
|
|
||||||
<MarketCards></MarketCards>
|
|
||||||
</div>
|
|
||||||
<div id="player_list">
|
|
||||||
<AnimatedBackground />
|
|
||||||
<PlayerListElement v-for="p in goStore.current_game?.players" :player="p">
|
|
||||||
</PlayerListElement>
|
|
||||||
</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 400px;
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
#market {
|
|
||||||
grid-area: market;
|
|
||||||
background-image: url("/images/bg-sand.png");
|
|
||||||
border-bottom: 3px solid black;
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
#current_player {
|
|
||||||
grid-area: current_player;
|
|
||||||
}
|
|
||||||
|
|
||||||
#player_list {
|
|
||||||
grid-area: player_list;
|
|
||||||
border-left: 5px solid black;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url("/images/bg-darkgrass.png");
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#player_list .card_image {
|
|
||||||
max-width: 64px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -7,7 +7,7 @@ const displayOptions = ref(false)
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="`card_overlay`" ref="card">
|
<div :class="`card_overlay`" ref="card">
|
||||||
<img class="settings_button" src="/settings.png" alt="settings" @click="displayOptions = true"/>
|
<img class="settings_button" src="/images/settings.png" alt="settings" @click="displayOptions = true"/>
|
||||||
</div>
|
</div>
|
||||||
<OptionsDialog :isVisible="displayOptions" @close="displayOptions = false" />
|
<OptionsDialog :isVisible="displayOptions" @close="displayOptions = false" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+67
-19
@@ -2,36 +2,59 @@
|
|||||||
import goStore, { buy, useEffect } from '@/netcode';
|
import goStore, { buy, useEffect } from '@/netcode';
|
||||||
import { CardEffects, type Effect } from '@/netcode/Effect'
|
import { CardEffects, type Effect } from '@/netcode/Effect'
|
||||||
import { CardType } from '@/netcode/Card'
|
import { CardType } from '@/netcode/Card'
|
||||||
|
import gsap from 'gsap'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const isTurn = computed(() => goStore.self && goStore.current_game?.started && (goStore.self.team == goStore.current_game.current_turn))
|
const isTurn = computed(() => goStore.self && goStore.current_game?.started && (goStore.self.team == goStore.current_game.current_turn))
|
||||||
const clientStore = useClientSideStore()
|
const clientStore = useClientSideStore()
|
||||||
|
|
||||||
const buyableFiregem = computed(() => goStore?.current_game?.gem_stack.at(-1))
|
const buyableFiregem = computed(() => goStore?.current_game?.gem_stack.at(-1))
|
||||||
|
|
||||||
|
|
||||||
|
function onBeforeEnter(el: Element) {
|
||||||
|
(el as HTMLElement).style.visibility = "hidden"
|
||||||
|
const element = el as HTMLElement
|
||||||
|
element.style.top = element.offsetTop + "px"
|
||||||
|
element.style.left = element.offsetLeft + "px"
|
||||||
|
element.style.zIndex = "999"
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEnter(el: any, done: () => unknown) {
|
||||||
|
console.log("enter")
|
||||||
|
// setTimeout(() => {
|
||||||
|
// el.style.visibility = null
|
||||||
|
// done()
|
||||||
|
// }, 1000)
|
||||||
|
done()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="market">
|
<div id="market">
|
||||||
<AnimatedBackground :color-end="[1, 1, 0]" :color-start="[.3, .3, 0]"></AnimatedBackground>
|
|
||||||
<CardPreview :tilted="false" :card_id="undefined"></CardPreview>
|
<CardPreview :tilted="false" :card_id="undefined"></CardPreview>
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
<CardPreview :tilted="false" :card_id="c?.card_id" v-for="c in goStore.current_game?.market"
|
<TransitionGroup name="market_card">
|
||||||
@click="c && buy(c)">
|
<CardPreview :tilted="false" :card_id="c?.card_id" v-for="c in goStore.current_game?.market"
|
||||||
<template
|
:key="c?.card_id" @click="c && buy(c)">
|
||||||
v-if="isTurn && goStore.self?.turn && c && c.cost && (goStore.self?.turn.gold_reserve ?? 0) >= c.cost">
|
<template
|
||||||
<button @click.stop="buy(c)">BUY</button>
|
v-if="isTurn && goStore.self?.turn && c && c.cost && (goStore.self?.turn.gold_reserve ?? 0) >= c.cost">
|
||||||
<button @click.stop="clientStore.findUseEffect(CardEffects.PLAY_NEXT_CARD_BOUGHT, c);"
|
<button @click.stop="buy(c)">BUY</button>
|
||||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.PLAY_NEXT_CARD_BOUGHT) && e.usable)">
|
<button @click.stop="clientStore.findUseEffect(CardEffects.PLAY_NEXT_CARD_BOUGHT, c);"
|
||||||
BUY IN HAND</button>
|
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.PLAY_NEXT_CARD_BOUGHT) && e.usable)">
|
||||||
<button @click.stop="clientStore.findUseEffect(CardEffects.STACK_NEXT_CARD_BOUGHT, c)"
|
BUY IN HAND</button>
|
||||||
v-if="clientStore.activatedClientside.find((e: Effect) => (e.effect == CardEffects.STACK_NEXT_CARD_BOUGHT) && e.usable)">
|
<button @click.stop="clientStore.findUseEffect(CardEffects.STACK_NEXT_CARD_BOUGHT, c)"
|
||||||
BUY IN STACK (generic)</button>
|
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)"
|
<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)">
|
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>
|
BUY IN STACK (actions)</button>
|
||||||
</template>
|
</template>
|
||||||
</CardPreview>
|
</CardPreview>
|
||||||
|
</TransitionGroup>
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
<CardPreview :tilted="false" :card_id="goStore.current_game?.gem_stack.at(-1)?.card_id"
|
<CardPreview :tilted="false" :card_id="goStore.current_game?.gem_stack.at(-1)?.card_id"
|
||||||
@click="buy(goStore.current_game?.gem_stack.at(-1)!)">
|
@click="buy(goStore.current_game?.gem_stack.at(-1)!)">
|
||||||
@@ -53,8 +76,26 @@ const buyableFiregem = computed(() => goStore?.current_game?.gem_stack.at(-1))
|
|||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* .market_card-leave-active,
|
||||||
|
.market_card-move {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market_card-enter-from {
|
||||||
|
transform: translate(-500px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.market_card-enter-active,
|
||||||
|
.market_card-leave-active {
|
||||||
|
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
|
||||||
|
} */
|
||||||
|
</style>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
#market {
|
#market {
|
||||||
|
width: min-content;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -62,8 +103,15 @@ const buyableFiregem = computed(() => goStore?.current_game?.gem_stack.at(-1))
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
position: relative;
|
position: relative;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
padding: 10px;
|
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 {
|
.separator {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ export interface Props {
|
|||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _devicePixelRatio = computed(() => devicePixelRatio)
|
||||||
|
const _window = computed(() => window)
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -15,10 +18,6 @@ const close = () => {
|
|||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => settingsStore.shaderQuality, async () => {
|
|
||||||
console.log(settingsStore.shaderQuality)
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -40,27 +39,29 @@ watch(() => settingsStore.shaderQuality, async () => {
|
|||||||
<span>Shader Quality</span>
|
<span>Shader Quality</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-body">
|
<div class="dialog-body">
|
||||||
<input type="range" min="0" max="10" v-model="settingsStore.shaderQuality">
|
<input type="range" min="1" max="10" v-model="settingsStore.shaderQuality"
|
||||||
|
:disabled="settingsStore.shaderFramerate == 0">
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<span>
|
<span>{{ Math.floor(_window.innerWidth * _devicePixelRatio * settingsStore.shaderQuality / 10) }}x{{
|
||||||
<template v-if="settingsStore.shaderQuality == 0">
|
Math.floor(_window.innerHeight *
|
||||||
Disabled
|
_devicePixelRatio * settingsStore.shaderQuality / 10) }}</span>
|
||||||
</template>
|
|
||||||
<template v-if="settingsStore.shaderQuality > 0">
|
|
||||||
{{ settingsStore.shaderQuality }}
|
|
||||||
</template>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
<span>Shader Framerate</span>
|
<span>Shader Framerate</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-body">
|
<div class="dialog-body">
|
||||||
<input type="range" min="10" max="60" step="5" v-model="settingsStore.shaderFramerate"
|
<input type="range" min="0" max="65" step="5" v-model="settingsStore.shaderFramerate">
|
||||||
:disabled="settingsStore.shaderQuality == 0">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<span>{{ settingsStore.shaderFramerate }} fps</span>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const props = defineProps<Props>()
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="player-icon-container">
|
<div class="player-icon-container">
|
||||||
<img class="avatar" v-if="props.player.image" :src="props.player.image">
|
<img class="avatar" v-if="props.player.image" :src="props.player.image" draggable="false">
|
||||||
<b class="player-name">{{ props.player.username }}</b>
|
<b class="player-name">{{ props.player.username }}</b>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -31,7 +31,7 @@ const props = defineProps<Props>()
|
|||||||
|
|
||||||
.player-name {
|
.player-name {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
@@ -2,152 +2,125 @@
|
|||||||
import goStore from '@/netcode';
|
import goStore from '@/netcode';
|
||||||
import type { Player } from "~/netcode/Player";
|
import type { Player } from "~/netcode/Player";
|
||||||
|
|
||||||
|
const emit = defineEmits(['click'])
|
||||||
|
|
||||||
|
|
||||||
const view = ref<"main" | "discard">("main")
|
const view = ref<"main" | "discard">("main")
|
||||||
|
|
||||||
function show(data: "main" | "discard") {
|
function show(data: "main" | "discard") {
|
||||||
view.value = data
|
view.value = data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
player: Player;
|
player: Player;
|
||||||
|
focused?: Player
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="`player_list_element ${goStore.self === props.player ? 'self' : ''}`">
|
<div class="player_list_element" @click="$emit('click')"
|
||||||
<AnimatedBackground v-if="goStore.self === props.player" :color-start="[.0, .3, .3]" :color-end="[.0, 1, 1]" />
|
:class="{ self: props.player == goStore.self, focused: props.focused == props.player, isTurn: props.player.team == goStore.current_game?.current_turn }">
|
||||||
<div class="button" id="player_list_buttons">
|
<div id="player_banner" class="small">
|
||||||
<div class="main_button" @click="show('main')">
|
<div class="ready-bubble not-ready" v-if="!props.player.ready && !goStore.current_game?.started">Pas encore
|
||||||
<div class="tooltip">Board</div>
|
prêt...
|
||||||
<img :src="`/cards/${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>
|
||||||
<div class="stack_button">
|
<div class="ready-bubble ready" v-if="props.player.ready && !goStore.current_game?.started">Prêt!</div>
|
||||||
<div class="tooltip">Stack</div>
|
<div id="player_info">
|
||||||
<img :src="'/cards/background.png'" draggable="false">
|
<PlayerIcon :player="props.player"></PlayerIcon>
|
||||||
<span class="button_card_number">{{ player.stack_pile.length }}</span>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="discard_button" @click="show('discard')">
|
<div id="player_stats">
|
||||||
<div class="tooltip">Discard</div>
|
<template v-if="goStore.current_game?.started">
|
||||||
<img :src="`/cards/${player.discard_pile.at(-1)?.card_id.toString().padStart(3, '0') ?? 'background'}.png`"
|
<PlayerStats :player="props.player">
|
||||||
draggable="false" :class="view === 'discard' && 'selected'">
|
</PlayerStats>
|
||||||
<span class="button_card_number">{{ player.discard_pile.length }}</span>
|
</template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.button>div>img:not(.selected) {
|
|
||||||
filter: brightness(.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
visibility: visible;
|
|
||||||
position: absolute;
|
|
||||||
transition: .1s transform;
|
|
||||||
background: white;
|
|
||||||
right: 0;
|
|
||||||
border-top-right-radius: 1em;
|
|
||||||
border-bottom-right-radius: 1em;
|
|
||||||
padding-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
.player_list_element {
|
||||||
display: grid;
|
border-radius: 10px;
|
||||||
grid-template-columns: min-content 1fr;
|
margin: 10px;
|
||||||
grid-template-areas:
|
filter: drop-shadow(2.5px 7.5px 1px rgba(0, 0, 0, .60));
|
||||||
"button player";
|
transform: translate(-2.5px, -7.5px);
|
||||||
border-bottom: 5px solid black;
|
backdrop-filter: blur(5px);
|
||||||
min-height: 200px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player_list_element.self {
|
|
||||||
background-image: url('/images/bg-grass.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.view {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all .1s;
|
||||||
|
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: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player_list_element.focused {
|
||||||
|
filter: unset;
|
||||||
|
transform: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player_list_element.isTurn::before {
|
||||||
|
opacity: .1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player_list_element.self::before {
|
||||||
|
background: rgb(33, 177, 76);
|
||||||
|
}
|
||||||
|
|
||||||
|
#player_banner {
|
||||||
|
display: flex;
|
||||||
|
grid-area: player_stats;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
align-items: center;
|
||||||
align-content: 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;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
|
||||||
<style>
|
.ready-bubble {
|
||||||
#player_list_buttons img {
|
color: white;
|
||||||
max-width: 100%;
|
padding: 0.5em;
|
||||||
object-fit: contain;
|
border-radius: 1em;
|
||||||
position: absolute;
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||||
display: block;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
margin: auto 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.view #contextMenuButtons button:not(.enemy_action) {
|
.ready {
|
||||||
display: none
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-ready {
|
||||||
|
background-color: maroon;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
+52
-177
@@ -26,7 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
hide_stack_and_discard: () => false,
|
hide_stack_and_discard: () => false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const discard_unwrapped = ref(false)
|
|
||||||
const isPlayerTurn = computed(() => props.player.team === goStore.current_game?.current_turn);
|
const isPlayerTurn = computed(() => props.player.team === goStore.current_game?.current_turn);
|
||||||
const isSelf = computed(() => props.player.uuid === goStore.self?.uuid);
|
const isSelf = computed(() => props.player.uuid === goStore.self?.uuid);
|
||||||
const isSelfTurn = computed(() => goStore.self?.team === goStore.current_game?.current_turn)
|
const isSelfTurn = computed(() => goStore.self?.team === goStore.current_game?.current_turn)
|
||||||
@@ -38,8 +38,8 @@ const make_discard = (target: Player) =>
|
|||||||
clientStore.findUseEffect(CardEffects.MAKE_DISCARD, target);
|
clientStore.findUseEffect(CardEffects.MAKE_DISCARD, target);
|
||||||
const sacrifice = (target: Card) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
|
const sacrifice = (target: Card) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
|
||||||
const stun = (target: Card) => clientStore.findUseEffect(CardEffects.STUN, 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_champion = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CHAMPION, target)
|
||||||
const restack_discarded_card = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CARD, target)
|
// const restack_discarded_card = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CARD, target)
|
||||||
const prepare_champion = (target: Card) => clientStore.findUseEffect(CardEffects.PREPARE, target)
|
const prepare_champion = (target: Card) => clientStore.findUseEffect(CardEffects.PREPARE, target)
|
||||||
|
|
||||||
|
|
||||||
@@ -60,7 +60,6 @@ function can_buy_fire_gem(): boolean {
|
|||||||
|
|
||||||
function checkEndTurn() {
|
function checkEndTurn() {
|
||||||
// TODO: Add a confirmation dialog if resources are left
|
// TODO: Add a confirmation dialog if resources are left
|
||||||
console.log("Checking end turn")
|
|
||||||
if (
|
if (
|
||||||
(!warning.value)
|
(!warning.value)
|
||||||
&& has_actions_remaining()) {
|
&& has_actions_remaining()) {
|
||||||
@@ -92,71 +91,19 @@ function check_for_guard(player: Player) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="player" :class="{ turn: isPlayerTurn, discard_unwrapped, self: isSelf }">
|
<div class="player" :class="{ turn: isPlayerTurn, self: isSelf }">
|
||||||
<template v-if="!hide_stack_and_discard">
|
<div id="player_banner" class="big">test</div>
|
||||||
<div class="stack">
|
<div id="turn_buttons">
|
||||||
|
<button id="end_turn" class="end_turn_button turn_icon" :class="{ hidden: !isSelfTurn }"
|
||||||
|
@click="checkEndTurn">END TURN</button>
|
||||||
|
|
||||||
<CardsGrouped :stack="props.player.stack_pile">
|
<div class="warning"
|
||||||
<!-- <button v-if="isSelf && isTurn" @click="draw">DRAW</button> -->
|
:class="{ 'warning-fire-gem': warningText == 'P\'tite Fire gem?', 'visible': warning }">
|
||||||
</CardsGrouped>
|
/!\<br>{{ warningText }}
|
||||||
</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" v-if="isSelfTurn" class="end_turn_button turn_icon"
|
|
||||||
@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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="current_player_board">
|
<div id="current_player_board">
|
||||||
<div class="other_player_buttons_container">
|
<div class="other_player_buttons_container">
|
||||||
<button @click.stop="damage_team(props.player.team)"
|
<button @click.stop="damage_team(props.player.team)"
|
||||||
@@ -180,13 +127,16 @@ function check_for_guard(player: Player) {
|
|||||||
|
|
||||||
<div class="cards_container">
|
<div class="cards_container">
|
||||||
<!-- HAND -->
|
<!-- HAND -->
|
||||||
<CardPreview :card_id="c?.card_id" v-for="c in props.player.hand" @click="c && playCard(c)">
|
<template v-if="props.player != goStore.self">
|
||||||
<button
|
<CardPreview :card_id="c?.card_id" v-for="c in props.player.hand" @click="c && playCard(c)">
|
||||||
v-if="props.player.turn && c && isSelf && (props.player.turn.discard_markers + props.player.turn.fuck_u_markers) > 0"
|
<button
|
||||||
@click.stop="discard(c)">
|
v-if="props.player.turn && c && isSelf && (props.player.turn.discard_markers + props.player.turn.fuck_u_markers) > 0"
|
||||||
DISCARD
|
@click.stop="discard(c)">
|
||||||
</button>
|
DISCARD
|
||||||
</CardPreview>
|
</button>
|
||||||
|
</CardPreview>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- BOARD -->
|
<!-- BOARD -->
|
||||||
<CardPreview :card_id="c.card_id" v-for="c in props.player.board" @click="lockCard(c)"
|
<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">
|
:locked="props.player.turn && c.uuid in props.player.turn.cards_locked">
|
||||||
@@ -270,11 +220,22 @@ function check_for_guard(player: Player) {
|
|||||||
background-color: rgba(255, 165, 0, 0.7);
|
background-color: rgba(255, 165, 0, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#turn_buttons {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
#end_turn {
|
#end_turn {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#end_turn.hidden {
|
||||||
|
visibility: hidden
|
||||||
|
}
|
||||||
|
|
||||||
.stack,
|
.stack,
|
||||||
.discard,
|
.discard,
|
||||||
.cards_container {
|
.cards_container {
|
||||||
@@ -302,74 +263,24 @@ function check_for_guard(player: Player) {
|
|||||||
visibility: hidden;
|
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 {
|
.player {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: min-content min-content 1fr;
|
grid-template-rows: min-content 1fr;
|
||||||
/* border-top: 5px solid black; */
|
/* border-top: 5px solid black; */
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"player_stats player_stats"
|
"player_stats"
|
||||||
"stack player"
|
"player";
|
||||||
"discard player";
|
|
||||||
transition: all 1s;
|
transition: all 1s;
|
||||||
margin-bottom: -75px;
|
justify-items: center;
|
||||||
padding-bottom: 75px;
|
/* margin-bottom: -75px;
|
||||||
}
|
padding-bottom: 75px; */
|
||||||
|
|
||||||
.discard_overlay {
|
|
||||||
grid-template-areas:
|
|
||||||
"player_stats player_stats"
|
|
||||||
"stack player";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#current_player_board {
|
#current_player_board {
|
||||||
grid-area: player;
|
grid-area: player;
|
||||||
}
|
}
|
||||||
|
|
||||||
.other_player_buttons_container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.damage_button {
|
.damage_button {
|
||||||
background-color: #ff4d4d;
|
background-color: #ff4d4d;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -451,44 +362,24 @@ function check_for_guard(player: Player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#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 {
|
#player_info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#player_banner.big>#player_info {
|
#player_banner.big {
|
||||||
margin-left: 500px;
|
/* 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 {
|
#player_banner img {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@@ -534,22 +425,6 @@ function check_for_guard(player: Player) {
|
|||||||
/* Remove shadow on click */
|
/* 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 {
|
.suicide_effect {
|
||||||
background: linear-gradient(to bottom right, #666666, #ccc);
|
background: linear-gradient(to bottom right, #666666, #ccc);
|
||||||
color: red;
|
color: red;
|
||||||
|
|||||||
@@ -17,20 +17,20 @@ const props = defineProps<Props>()
|
|||||||
{{ player.team?.health }}
|
{{ player.team?.health }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="turn_icon">
|
<!-- <div class="turn_icon" v-if="showTurnData">
|
||||||
<img src="/images/gold.png" class="icon_img" draggable="false" />
|
<img src="/images/gold.png" class="icon_img" draggable="false" />
|
||||||
<span class="turn_icon_number">
|
<span class="turn_icon_number">
|
||||||
{{ player.turn?.gold_reserve }}
|
{{ player.turn?.gold_reserve }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="turn_icon">
|
<div class="turn_icon" v-if="showTurnData">
|
||||||
<img src="/images/damage.png" class="icon_img" draggable="false" />
|
<img src="/images/damage.png" class="icon_img" draggable="false" />
|
||||||
<span class="turn_icon_number">
|
<span class="turn_icon_number">
|
||||||
{{ player.turn?.damage_reserve }}
|
{{ player.turn?.damage_reserve }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="turn_icon">
|
<div class="turn_icon">
|
||||||
<span class="icon_img">🖕</span>
|
<img src="/images/fuck.svg" class="icon_img" draggable="false" />
|
||||||
<span class="turn_icon_number">{{ (player.turn?.discard_markers ?? 0) + (player.turn?.fuck_u_markers ?? 0)
|
<span class="turn_icon_number">{{ (player.turn?.discard_markers ?? 0) + (player.turn?.fuck_u_markers ?? 0)
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,11 +47,11 @@ const props = defineProps<Props>()
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img.icon_img {
|
img.icon_img {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.icon_img {
|
span.icon_img {
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ const close = () => {
|
|||||||
Et n'oubliez pas, la prochaine fois, vous gagnerez! Vous verrez! Vous êtes un gagnant, vous êtes un champion!
|
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
|
Vous êtes le meilleur! Vous êtes
|
||||||
le roi du monde!
|
le roi du monde!
|
||||||
|
<img src="/images/so_back.png" alt="Une image décrivant le meme 'We are so back'" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,317 +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>
|
|
||||||
</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>
|
|
||||||
@@ -103,8 +103,4 @@ import goStore, { useEffect, playCard, playAllCards, discard, lockCard, heal, da
|
|||||||
transform: translateY(-40px);
|
transform: translateY(-40px);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cards_container {
|
|
||||||
min-width: 250px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import goStore, { oauth2_register_discord, setup_websocket, heron_cookie_login, join_game } from "@/netcode";
|
||||||
|
|
||||||
|
export default defineNuxtRouteMiddleware
|
||||||
|
(async (to, from) => {
|
||||||
|
await setup_websocket()
|
||||||
|
|
||||||
|
|
||||||
|
if (to.path == '/login') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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("/games")) {
|
||||||
|
if (Array.isArray(to.params.id)) { return navigateTo('/lobby') }
|
||||||
|
try {
|
||||||
|
await join_game(to.params.id)
|
||||||
|
return
|
||||||
|
} catch {
|
||||||
|
return navigateTo("/lobby")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (to.path == "/lobby") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (to.path == "/rules") {
|
||||||
|
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('/')
|
||||||
|
}
|
||||||
|
})
|
||||||
+61
-21
@@ -15,6 +15,7 @@ import { type Lobby, create_lobby, type LobbyNetcode } from "./Lobby";
|
|||||||
|
|
||||||
import { type Ref } from "vue";
|
import { type Ref } from "vue";
|
||||||
|
|
||||||
|
const socketTimeout = 1000
|
||||||
let socket: WebSocket;
|
let socket: WebSocket;
|
||||||
|
|
||||||
export class GameObjectRegister {
|
export class GameObjectRegister {
|
||||||
@@ -25,6 +26,8 @@ export class GameObjectRegister {
|
|||||||
readonly turns: Map<string, Turn>;
|
readonly turns: Map<string, Turn>;
|
||||||
readonly games: Map<string, Game>;
|
readonly games: Map<string, Game>;
|
||||||
|
|
||||||
|
readonly gameRoutingCallbacks: Map<string, [() => void, () => void]>;
|
||||||
|
readonly loginCallbacks: Set<() => void>;
|
||||||
context: "lobby" | "login" | "pregame" | "game" | "rules" = "login";
|
context: "lobby" | "login" | "pregame" | "game" | "rules" = "login";
|
||||||
private readonly objects: Map<string, GameObjectNetcode>;
|
private readonly objects: Map<string, GameObjectNetcode>;
|
||||||
current_game: Ref<Game | null>;
|
current_game: Ref<Game | null>;
|
||||||
@@ -37,6 +40,9 @@ export class GameObjectRegister {
|
|||||||
clientMouseY: Ref<number>
|
clientMouseY: Ref<number>
|
||||||
discordId: Ref<string>
|
discordId: Ref<string>
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.gameRoutingCallbacks = new Map();
|
||||||
|
this.loginCallbacks = new Set();
|
||||||
|
|
||||||
this.effects = reactive(new Map());
|
this.effects = reactive(new Map());
|
||||||
this.cards = reactive(new Map());
|
this.cards = reactive(new Map());
|
||||||
this.teams = reactive(new Map());
|
this.teams = reactive(new Map());
|
||||||
@@ -117,6 +123,12 @@ export class GameObjectRegister {
|
|||||||
}
|
}
|
||||||
setCurrentGame = (uuid: string) => {
|
setCurrentGame = (uuid: string) => {
|
||||||
this.current_game.value = this.games.get(uuid) ?? null;
|
this.current_game.value = this.games.get(uuid) ?? null;
|
||||||
|
this.gameRoutingCallbacks.get(uuid)?.[0]()
|
||||||
|
this.gameRoutingCallbacks.delete(uuid)
|
||||||
|
for (let [_key, value] of this.gameRoutingCallbacks) {
|
||||||
|
value[1]()
|
||||||
|
this.gameRoutingCallbacks.delete(_key)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +191,8 @@ export const setup_websocket = () =>
|
|||||||
heron_session.value = data.data;
|
heron_session.value = data.data;
|
||||||
} else if (data.data_type == "self_discord_id") {
|
} else if (data.data_type == "self_discord_id") {
|
||||||
goStore.setDiscordId(data.data)
|
goStore.setDiscordId(data.data)
|
||||||
|
goStore.loginCallbacks.forEach(c => c())
|
||||||
|
goStore.loginCallbacks.clear()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// console.log(data.type, data.data_type, data.data);
|
// console.log(data.type, data.data_type, data.data);
|
||||||
@@ -188,24 +202,42 @@ export const setup_websocket = () =>
|
|||||||
|
|
||||||
export async function heron_cookie_login(cookie: string) {
|
export async function heron_cookie_login(cookie: string) {
|
||||||
await setup_websocket();
|
await setup_websocket();
|
||||||
socket.send(
|
return new Promise<void>((resolve, reject) => {
|
||||||
JSON.stringify({
|
goStore.loginCallbacks.add(resolve)
|
||||||
type: "cookie_login",
|
socket.send(
|
||||||
data_type: "cookie",
|
JSON.stringify({
|
||||||
data: cookie,
|
type: "cookie_login",
|
||||||
})
|
data_type: "cookie",
|
||||||
);
|
data: cookie,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
goStore.loginCallbacks.delete(resolve);
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
, socketTimeout)
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function oauth2_register_discord(code: string) {
|
export async function oauth2_register_discord(code: string) {
|
||||||
await setup_websocket();
|
await setup_websocket();
|
||||||
socket.send(
|
return new Promise<void>((resolve, reject) => {
|
||||||
JSON.stringify({
|
goStore.loginCallbacks.add(resolve)
|
||||||
type: "discord_login",
|
|
||||||
data_type: "code",
|
socket.send(
|
||||||
data: code,
|
JSON.stringify({
|
||||||
})
|
type: "discord_login",
|
||||||
);
|
data_type: "code",
|
||||||
|
data: code,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
goStore.loginCallbacks.delete(resolve);
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
, socketTimeout)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function register(username: string) {
|
export async function register(username: string) {
|
||||||
@@ -364,12 +396,20 @@ export async function netcode_create_game() {
|
|||||||
|
|
||||||
export async function join_game(game_uuid: string) {
|
export async function join_game(game_uuid: string) {
|
||||||
await setup_websocket();
|
await setup_websocket();
|
||||||
socket.send(
|
return new Promise<void>(((resolve, reject) => {
|
||||||
JSON.stringify({
|
goStore.gameRoutingCallbacks.set(game_uuid, [resolve, reject])
|
||||||
type: "join",
|
setTimeout(() => {
|
||||||
data_type: "game",
|
const cb = goStore.gameRoutingCallbacks.get(game_uuid)
|
||||||
data: game_uuid,
|
if (!cb) { return }
|
||||||
})
|
cb[1]()
|
||||||
);
|
}, socketTimeout)
|
||||||
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: "join",
|
||||||
|
data_type: "game",
|
||||||
|
data: game_uuid,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
export default goStore;
|
export default goStore;
|
||||||
|
|||||||
Submodule
+1
Submodule netcode/interfaces added at 89c03fe650
@@ -12,13 +12,4 @@ export default defineNuxtConfig({
|
|||||||
'@pinia/nuxt',
|
'@pinia/nuxt',
|
||||||
'@pinia-plugin-persistedstate/nuxt',
|
'@pinia-plugin-persistedstate/nuxt',
|
||||||
],
|
],
|
||||||
app: {
|
|
||||||
head: {
|
|
||||||
script: [{
|
|
||||||
src: "/webgl-utils.js"
|
|
||||||
}, {
|
|
||||||
src: "https://lygia.xyz/resolve.js"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+7
@@ -8,6 +8,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pinia/nuxt": "^0.5.1",
|
"@pinia/nuxt": "^0.5.1",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
"nuxt": "^3.11.2",
|
"nuxt": "^3.11.2",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
@@ -5670,6 +5671,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/gsap": {
|
||||||
|
"version": "3.12.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz",
|
||||||
|
"integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==",
|
||||||
|
"license": "Standard 'no charge' license: https://gsap.com/standard-license. Club GSAP members get more: https://gsap.com/licensing/. Why GreenSock doesn't employ an MIT license: https://gsap.com/why-license/"
|
||||||
|
},
|
||||||
"node_modules/gzip-size": {
|
"node_modules/gzip-size": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pinia/nuxt": "^0.5.1",
|
"@pinia/nuxt": "^0.5.1",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
"nuxt": "^3.11.2",
|
"nuxt": "^3.11.2",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import goStore, { setReady } from '../../netcode';
|
||||||
|
import type { Player } from '../../netcode/Player';
|
||||||
|
import { playSound } from '../../helpers/audioHelpers';
|
||||||
|
import { CardType, type Card } from '../../netcode/Card';
|
||||||
|
import { CardEffects, type Effect } from '../../netcode/Effect';
|
||||||
|
import type { Team } from '~/netcode/Team';
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
const discard_unwrapped = ref(false)
|
||||||
|
const focusedPlayer = ref<Player>((goStore.current_game?.players.find(p => p.team == goStore.current_game?.current_turn) ?? goStore.current_game?.players[0])!)
|
||||||
|
const clientStore = useClientSideStore()
|
||||||
|
const restack_discarded_champion = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CHAMPION, target)
|
||||||
|
const restack_discarded_card = (target: Card) => clientStore.findUseEffect(CardEffects.RESTACK_DISCARDED_CARD, target)
|
||||||
|
const sacrifice = (target: Card) => clientStore.findUseEffect(CardEffects.SACRIFICE, target);
|
||||||
|
|
||||||
|
function focusPlayer(p: Player) {
|
||||||
|
focusedPlayer.value = p
|
||||||
|
}
|
||||||
|
|
||||||
|
watch<Team>(() => goStore.current_game!.current_turn, (new_team, old_team) => {
|
||||||
|
if (focusedPlayer.value.team != old_team) { return }
|
||||||
|
const first_player = goStore.current_game?.players.find(p => p.team == goStore.current_game?.current_turn)
|
||||||
|
if (!first_player) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
focusPlayer(first_player)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div @contextmenu.prevent="false" id="game_board">
|
||||||
|
<AnimatedBackground></AnimatedBackground>
|
||||||
|
<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>
|
||||||
|
<div id="stack_discard">
|
||||||
|
<div id="stack">
|
||||||
|
<template v-for="p in current_players">
|
||||||
|
<CardsGrouped :stack="p.stack_pile">
|
||||||
|
|
||||||
|
</CardsGrouped>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="discard" :class="{ invisible: discard_unwrapped }" @click="() => discard_unwrapped = true">
|
||||||
|
<template v-for="p in current_players">
|
||||||
|
<CardsGrouped :stack="p.discard_pile" :visible="!discard_unwrapped">
|
||||||
|
</CardsGrouped>
|
||||||
|
</template>
|
||||||
|
<template v-for="p in current_players">
|
||||||
|
<div class="discard_overlay" v-if="discard_unwrapped">
|
||||||
|
<CardPreview :tilted="false" v-if="discard_unwrapped" :card_id="c.card_id"
|
||||||
|
v-for="c in p.discard_pile">
|
||||||
|
<template
|
||||||
|
v-if="goStore.self?.turn && c && p == goStore.self && goStore.current_game?.current_turn == goStore.self.team">
|
||||||
|
<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="{ unwrapped: discard_unwrapped }"
|
||||||
|
@click.stop="() => { discard_unwrapped = false; console.log(discard_unwrapped); }"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MarketCards></MarketCards>
|
||||||
|
|
||||||
|
<div id="player_list_container">
|
||||||
|
<div id="player_list">
|
||||||
|
<PlayerListElement v-for="p in goStore.current_game?.players" :player="p" @click="focusPlayer(p)"
|
||||||
|
:focused="focusedPlayer">
|
||||||
|
</PlayerListElement>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="current_player">
|
||||||
|
|
||||||
|
<!-- <AnimatedBackground :color-start="goStore.self == p ? [.0, .3, .3] : undefined"
|
||||||
|
:color-end="goStore.self == p ? [.0, 1, 1] : undefined" /> -->
|
||||||
|
<PlayerSlot v-if="focusedPlayer" :player="focusedPlayer"></PlayerSlot>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ResultDialog :isVisible="showResults" :won="isWinner" :players="goStore.current_game?.players"
|
||||||
|
@close="showResults = false">
|
||||||
|
</ResultDialog>
|
||||||
|
<GlobalOverlay></GlobalOverlay>
|
||||||
|
<SelfView></SelfView>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#game_status {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: rgba(255, 255, 255, .75);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#game_board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr min-content 1fr;
|
||||||
|
grid-template-rows: min-content min-content 1fr;
|
||||||
|
grid-template-areas:
|
||||||
|
"stack_discard market empty"
|
||||||
|
"player_list player_list player_list"
|
||||||
|
"current_player current_player current_player";
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player_list,
|
||||||
|
#current_player {
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
/* background: url('/images/bg-darkgrass.png'); */
|
||||||
|
}
|
||||||
|
|
||||||
|
#current_player {
|
||||||
|
grid-area: current_player;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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("/images/bg-darkgrass.png"); */
|
||||||
|
}
|
||||||
|
|
||||||
|
#player_list {
|
||||||
|
/* border: 5px solid #000; */
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#player_list .card {
|
||||||
|
--max-height: 90px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+1
-23
@@ -1,24 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import goStore, { setReady } from "@/netcode";
|
import goStore, { setReady } from "@/netcode";
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="root_page_div">
|
<div class="root_page_div">
|
||||||
<div id="game_status">
|
tehe
|
||||||
|
|
||||||
<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>
|
|
||||||
<RulesDisplay v-if="goStore.context == 'rules'"></RulesDisplay>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -26,13 +13,4 @@ import goStore, { setReady } from "@/netcode";
|
|||||||
.root_page_div {
|
.root_page_div {
|
||||||
height: 100%;
|
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>
|
</style>
|
||||||
@@ -10,6 +10,14 @@ function killErika() {
|
|||||||
playSound('/sounds/splat1.mp3')
|
playSound('/sounds/splat1.mp3')
|
||||||
erika_alive.value = false;
|
erika_alive.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Autojoin broken
|
||||||
|
// watch(() => goStore.context, () => {
|
||||||
|
// console.log(goStore.context)
|
||||||
|
// if (goStore.context == "pregame") {
|
||||||
|
// router.push(`/games/${goStore.current_game?.uuid}`)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@@ -26,7 +34,8 @@ function killErika() {
|
|||||||
|
|
||||||
<div class="games-list-zone">
|
<div class="games-list-zone">
|
||||||
<div v-for="g in goStore.lobby?.games">
|
<div v-for="g in goStore.lobby?.games">
|
||||||
<button v-if="g" @click="join_game(g.uuid)" :class="goStore.discordId == g.owner ? 'own_game' : ''">
|
<button v-if="g" @click="router.push({ path: `/games/${g.uuid}` })"
|
||||||
|
:class="goStore.discordId == g.owner ? 'own_game' : ''">
|
||||||
<template v-if="g.started && g.ended">
|
<template v-if="g.started && g.ended">
|
||||||
GAME OVER
|
GAME OVER
|
||||||
</template>
|
</template>
|
||||||
@@ -57,7 +66,7 @@ function killErika() {
|
|||||||
|
|
||||||
<div class="overlay" v-if="erika_alive">
|
<div class="overlay" v-if="erika_alive">
|
||||||
<div class="erika-zone">
|
<div class="erika-zone">
|
||||||
<img @click="router.push({ path: 'rules' })" class="overlay-image"
|
<img @click="router.push('/rules')" class="overlay-image"
|
||||||
src="https://static.wikia.nocookie.net/umineko/images/9/9d/PC_Erika.Casual.Sprite_36.png"
|
src="https://static.wikia.nocookie.net/umineko/images/9/9d/PC_Erika.Casual.Sprite_36.png"
|
||||||
alt="pétasse" />
|
alt="pétasse" />
|
||||||
<div class="overlay-button">
|
<div class="overlay-button">
|
||||||
@@ -76,8 +85,6 @@ function killErika() {
|
|||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import goStore, { netcode_create_game, join_game } from '~/netcode';
|
import { navigateTo, useRuntimeConfig } from 'nuxt/app';
|
||||||
|
import goStore, { netcode_create_game, join_game } from '../netcode';
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
@@ -103,4 +104,4 @@ footer {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>import { useRuntimeConfig, navigateTo } from 'nuxt/app';
|
||||||
+318
-2
@@ -1,3 +1,319 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import type { Card } from '~/netcode/Card';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<RulesDisplay></RulesDisplay>
|
<div class="background">
|
||||||
</template>
|
<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 scoped>
|
||||||
|
.background {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
background-image: url('/images/bg-darkgrass.png');
|
||||||
|
background-size: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100vw;
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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>
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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 |
BIN
Binary file not shown.
@@ -8,8 +8,9 @@ enum CardStyles {
|
|||||||
export const useSettingsStore = defineStore("settingstore", {
|
export const useSettingsStore = defineStore("settingstore", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
cardStyle: CardStyles.HERON,
|
cardStyle: CardStyles.HERON,
|
||||||
shaderQuality: 0,
|
shaderQuality: 3,
|
||||||
shaderFramerate: 10,
|
shaderFramerate: 0,
|
||||||
|
shaderPixelated: true
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
switchCardStyles() {
|
switchCardStyles() {
|
||||||
|
|||||||
Reference in New Issue
Block a user