diff options
| author | LLLL Colonq <llll@colonq> | 2026-05-26 03:55:41 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2026-05-26 03:55:41 -0400 |
| commit | 188643eccb13452f0e77ccea2a6e1baf6d5320b3 (patch) | |
| tree | 468ce87ba5a8357f7293741c827a7a4e5ced7059 /assets/js/charsheet/charsheet.js | |
| parent | f776dc83178d1606b80f068e1e49fb9e53f62365 (diff) | |
Big refactor, jam
Diffstat (limited to 'assets/js/charsheet/charsheet.js')
| -rw-r--r-- | assets/js/charsheet/charsheet.js | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/assets/js/charsheet/charsheet.js b/assets/js/charsheet/charsheet.js new file mode 100644 index 0000000..c43a2b2 --- /dev/null +++ b/assets/js/charsheet/charsheet.js @@ -0,0 +1,415 @@ +let GL = null; +let VERTS = null; +let IDXS = null; +let ATTRIB_VERTEX = null; +let ATTRIB_TEXCOORD = null; +let UNIFORM_PROJECTION = null; +let UNIFORM_VIEW = null; +let UNIFORM_POSITION = null; +let CUBE_ROTATION = 0.0; +let UID = null; +let USER = {}; +let TALENTS = {}; +let TALENT_SELECTED = null; + +function showContents() { + let elem = document.getElementById("contents"); + if (elem) { + elem.classList.remove("invisible"); + elem.classList.add("contents-anim-unfold"); + let timeout = 500; + for (let c of elem.children) { + setTimeout( + () => c.classList.add("opaque"), + timeout + ); + timeout += 200; + } + let badges = document.getElementById("badges"); + if (badges) { + setTimeout( + () => badges.classList.add("opaque"), + timeout + ); + } + } +} + +function showError(msg) { + let elem = document.getElementById("error"); + if (elem) { + elem.classList.remove("invisible"); + elem.classList.add("error-anim-unfold"); + let timeout = 500; + for (let c of elem.children) { + setTimeout( + () => c.classList.add("opaque"), + timeout + ); + timeout += 200; + } + } + let em = document.getElementById("error-message"); + if (em) { em.innerText = msg; } +} + +function scaleName() { + let elem = document.getElementById("name"); + if (elem && elem.firstElementChild) { + elem.firstElementChild.style = `transform: scale(1, 1)`; + let { width: pw, height: ph } = elem.getBoundingClientRect(); + let { width: cw, height: ch } = elem.firstElementChild.getBoundingClientRect(); + elem.firstElementChild.style = `transform: scale(${pw / cw}, ${ph / ch})`; + } +} +function setName(nm) { + let elem = document.getElementById("name"); + if (elem && elem.firstElementChild && elem.firstElementChild.innerText.toLowerCase().trim() != nm) { + elem.firstElementChild.innerText = nm + scaleName(); + new ResizeObserver(scaleName).observe(elem); + } +} + +function uploadCube(gl) { + if (GL) { + const verts = GL.createBuffer(); + GL.bindBuffer(GL.ARRAY_BUFFER, verts); + const vertdata = [ + // front + -0.5, -0.5, -0.5, 0, 0, + +0.5, -0.5, -0.5, 1.0, 0, + +0.5, +0.5, -0.5, 1.0, 1.0, + -0.5, +0.5, -0.5, 0, 1.0, + // right + +0.5, -0.5, -0.5, 0, 0, + +0.5, -0.5, +0.5, 1.0, 0, + +0.5, +0.5, +0.5, 1.0, 1.0, + +0.5, +0.5, -0.5, 0, 1.0, + // left + -0.5, -0.5, +0.5, 0, 0, + -0.5, -0.5, -0.5, 1.0, 0, + -0.5, +0.5, -0.5, 1.0, 1.0, + -0.5, +0.5, +0.5, 0, 1.0, + // top + -0.5, -0.5, +0.5, 0, 0, + +0.5, -0.5, +0.5, 1.0, 0, + +0.5, -0.5, -0.5, 1.0, 1.0, + -0.5, -0.5, -0.5, 0, 1.0, + // bottom + -0.5, +0.5, -0.5, 0, 0, + +0.5, +0.5, -0.5, 1.0, 0, + +0.5, +0.5, +0.5, 1.0, 1.0, + -0.5, +0.5, +0.5, 0, 1.0, + // back + +0.5, -0.5, +0.5, 0, 0, + -0.5, -0.5, +0.5, 1.0, 0, + -0.5, +0.5, +0.5, 1.0, 1.0, + +0.5, +0.5, +0.5, 0, 1.0 + ]; + GL.bufferData(GL.ARRAY_BUFFER, new Float32Array(vertdata), GL.STATIC_DRAW); + const quadidx = (p0, p1, p2, p3) => [p0, p1, p2, p2, p3, p0]; + const idxs = GL.createBuffer(); + GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, idxs); + const idxdata = [ + quadidx(0, 1, 2, 3), // front + quadidx(4, 5, 6, 7), // right + quadidx(8, 9, 10, 11), // left + quadidx(12, 13, 14, 15), // top + quadidx(16, 17, 18, 19), // bottom + quadidx(20, 21, 22, 23) // back + ].flat(); + GL.bufferData(GL.ELEMENT_ARRAY_BUFFER, new Uint16Array(idxdata), GL.STATIC_DRAW); + return { verts: verts, idxs: idxs }; + } +} +function uploadShader(type, source) { + if (GL) { + const shader = GL.createShader(type); + GL.shaderSource(shader, source); + GL.compileShader(shader); + if (!GL.getShaderParameter(shader, GL.COMPILE_STATUS)) { + alert(`shader compilation error: ${GL.getShaderInfoLog(shader)}`); + GL.deleteShader(shader); + return null; + } + return shader; + } +} +function uploadImage(url) { + if (GL) { + const tex = GL.createTexture(); + GL.bindTexture(GL.TEXTURE_2D, tex); + GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, 1, 1, 0, GL.RGBA, GL.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 1.0])); + const img = new Image(); + img.onload = () => { + GL.bindTexture(GL.TEXTURE_2D, tex); + GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, img); + GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST); + GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST); + GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); + GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); + }; + img.src = url; + return tex; + } +} + +function initGL() { + const canvas = document.getElementById("canvas"); + GL = canvas.getContext("webgl"); + console.log(GL); + if (!GL) return; + console.log("initialized opengl"); + GL.clearColor(1.0, 0.0, 0.0, 1.0); + GL.clearDepth(1.0); + GL.enable(GL.DEPTH_TEST); + GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT); + const vertsrc = ` + attribute vec3 vertex; + attribute vec2 texcoord; + uniform mat4 view; + uniform mat4 position; + uniform mat4 projection; + varying highp vec2 vertex_texcoord; + void main(void) { + gl_Position = projection * view * position * vec4(vertex, 1.0); + vertex_texcoord = texcoord; + } + `; + const fragsrc = ` + varying highp vec2 vertex_texcoord; + uniform sampler2D texture_data; + void main(void) { + gl_FragColor = texture2D(texture_data, vertex_texcoord); + gl_FragColor.a = 1.0; + } + `; + const vshader = uploadShader(GL.VERTEX_SHADER, vertsrc); + const fshader = uploadShader(GL.FRAGMENT_SHADER, fragsrc); + const prog = GL.createProgram(); + GL.attachShader(prog, vshader); + GL.attachShader(prog, fshader); + GL.linkProgram(prog); + if (!GL.getProgramParameter(prog, GL.LINK_STATUS)) { + alert(`shader linking error: ${gl.getProgramInfoLog(prog)}`); + return null; + } + GL.useProgram(prog); + ATTRIB_VERTEX = GL.getAttribLocation(prog, "vertex"); + ATTRIB_TEXCOORD = GL.getAttribLocation(prog, "texcoord"); + UNIFORM_PROJECTION = GL.getUniformLocation(prog, "projection"); + UNIFORM_VIEW = GL.getUniformLocation(prog, "view"); + UNIFORM_POSITION = GL.getUniformLocation(prog, "position"); + const cube = uploadCube(); + VERTS = cube.verts; + IDXS = cube.idxs; + requestAnimationFrame(render); +} + +function render() { + if (GL) { + CUBE_ROTATION += 0.01; + CUBE_ROTATION %= 2.0 * Math.PI; + const projection = mat4.create(); + mat4.perspective(projection, Math.PI / 4.0, GL.canvas.clientWidth / GL.canvas.clientHeight, 0.1, 100.0); + const view = mat4.create(); + mat4.lookAt(view, [0.0, 0.0, -5.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]); + const position = mat4.create(); + mat4.rotateX(position, position, Math.PI / 4.0); + mat4.rotateY(position, position, CUBE_ROTATION); + mat4.rotateZ(position, position, CUBE_ROTATION); + GL.bindBuffer(GL.ARRAY_BUFFER, VERTS); + GL.vertexAttribPointer(ATTRIB_VERTEX, 3, GL.FLOAT, false, 20, 0); + GL.enableVertexAttribArray(ATTRIB_VERTEX); + GL.vertexAttribPointer(ATTRIB_TEXCOORD, 2, GL.FLOAT, false, 20, 12); + GL.enableVertexAttribArray(ATTRIB_TEXCOORD); + GL.uniformMatrix4fv(UNIFORM_PROJECTION, false, projection); + GL.uniformMatrix4fv(UNIFORM_VIEW, false, view); + GL.uniformMatrix4fv(UNIFORM_POSITION, false, position); + GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, IDXS); + GL.drawElements(GL.TRIANGLES, 36, GL.UNSIGNED_SHORT, 0); + requestAnimationFrame(render); + } +} + +function setField(nm, v) { + const elem = document.getElementById("info-" + nm); + if (elem) { + elem.innerText = v.toString(); + } +} + +function addBadge(b) { + const badges = document.getElementById("badges"); + const elem = document.createElement("span"); + elem.classList.add("badge"); + elem.title = b.desc; + if (b.mode == "text") { + elem.innerText = b.text; + } else if (b.mode == "icon") { + const img = document.createElement("img"); + img.src = `${globalThis.apiServer}/badge/icon/${b.bid}.png`; + elem.appendChild(img); + } else { + return; + } + badges.appendChild(elem); +} + +async function fetchUser(uid) { + const resp = await fetch(`${globalThis.apiServer}/user/info/${uid}`); + if (!resp.ok) { + showError("user not found"); + return false; + } else { + uploadImage(`${globalThis.apiServer}/user/avatar/${uid}.png`); + const info = await resp.json() + UID = uid; + USER = info; + setName(info.properties["name"]); + for (let nm in info.stats) { + setField(nm, info.stats[nm]); + } + for (let nm in info.properties) { + setField(nm, info.properties[nm]); + } + setField( + "unallocated", + (info.stats["talentpoints"] || 0) - (info.stats["allocatedtalentpoints"] || 0) + ); + const badges = document.getElementById("badges"); + if (badges) { + const badges_resp = await fetch(`${globalThis.apiServer}/user/badges/${uid}`); + if (badges_resp.ok) { + const bs = await badges_resp.json(); + for (let b of bs) { + addBadge(b); + } + } + } + return true; + } +} + +globalThis.mainPublic = async () => { + const uid = location.hash.slice(1); + initGL(); + if (await fetchUser(uid)) { + showContents(); + } +} + +async function getAuthedUser() { + const resp = await fetch(`${globalThis.secureApiServer}/info`); + const s = await resp.text(); + const [_nm, uid] = s.split(" "); + return uid; +} + +function showContentsSecure() { + let elem = document.getElementById("edit-contents"); + if (elem) { + let timeout = 500; + for (let c of elem.children) { + if (!c.dataset.skipfade) { + setTimeout( + () => c.classList.add("opaque"), + timeout + ); + timeout += 200; + } + } + } +} + +async function fetchTalents() { + const resp = await fetch(`${globalThis.apiServer}/talents`); + return await resp.json(); +} +async function renderTalent(tid, x, y) { + const p = document.getElementById("edit-talentarea"); + const elem = document.createElement("div"); + elem.id = `edit-talent-${tid}`; + elem.dataset.tid = tid; + elem.classList.add("edit-talent"); + elem.style = `top: ${y}px; left: ${x}px;`; + elem.addEventListener("click", () => selectTalent(elem)); + const img = document.createElement("img"); + img.src = `${globalThis.apiServer}/talent/icon/${tid}.png`; + elem.appendChild(img); + p.appendChild(elem); +} +function selectTalent(elem) { + const p = document.getElementById("edit-talentarea"); + for (let c of p.children) { + c.classList.remove("edit-talent-selected"); + } + if (elem.dataset.tid == TALENT_SELECTED) { + TALENT_SELECTED = null; + const box = document.getElementById("edit-selected-tooltip-box"); + box.classList.remove("opaque"); + } else { + elem.classList.add("edit-talent-selected"); + TALENT_SELECTED = elem.dataset.tid; + const box = document.getElementById("edit-selected-tooltip-box"); + box.classList.add("opaque"); + const name = document.getElementById("edit-selected-tooltip-name"); + name.innerText = TALENTS[TALENT_SELECTED].name; + const tooltip = document.getElementById("edit-selected-tooltip"); + tooltip.innerText = TALENTS[TALENT_SELECTED].desc; + const allocated = document.getElementById("edit-selected-tooltip-allocated"); + allocated.innerText = (USER.talents[TALENT_SELECTED] || 0).toString(); + } +} +async function buySelectedTalent() { + if (TALENT_SELECTED) { + const resp = await fetch(`${globalThis.secureApiServer}/talent/${TALENT_SELECTED}/purchase`, { + method: "POST", + }); + if (resp.ok) { + console.log(await resp.text()); + await fetchUser(UID); + const allocated = document.getElementById("edit-selected-tooltip-allocated"); + allocated.innerText = (USER.talents[TALENT_SELECTED] || 0).toString(); + } + } +} +async function resetTalents() { + const resp = await fetch(`${globalThis.secureApiServer}/talent/reset`, { + method: "POST", + }); + if (resp.ok) { + console.log(await resp.text()); + await fetchUser(UID); + const allocated = document.getElementById("edit-selected-tooltip-allocated"); + allocated.innerText = "0"; + } +} + +globalThis.mainSecure = async () => { + const scrollable = document.getElementById("edit-scrollable"); + const basex = window.innerWidth + window.innerWidth / 2 + 20; + const basey = window.innerHeight + 20; + scrollable.scroll(window.innerWidth, window.innerHeight); + const uid = await getAuthedUser(); + if (await fetchUser(uid)) { + showContentsSecure(); + } + const buy = document.getElementById("edit-selected-tooltip-buy"); + buy.addEventListener("click", buySelectedTalent); + const reset = document.getElementById("talent-reset"); + reset.addEventListener("click", resetTalents); + TALENTS = await fetchTalents(); + const dim = 100; + let x = 0; + let y = 0; + for (let tid in TALENTS) { + renderTalent(tid, basex + x * dim, basey + y * dim); + x += 1; + if (x > 4) { + x = 0; + y += 1; + } + } +} |
