From 091e9df29543666aca58a09561ba5aef96f280cf Mon Sep 17 00:00:00 2001 From: nak Date: Wed, 18 Mar 2026 05:50:44 +0000 Subject: [PATCH] Add poker scripts to repo --- scripts/poker_constants.js | 38 +++++++ scripts/poker_sit.js | 215 +++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 scripts/poker_constants.js create mode 100644 scripts/poker_sit.js diff --git a/scripts/poker_constants.js b/scripts/poker_constants.js new file mode 100644 index 0000000..d0bdc9d --- /dev/null +++ b/scripts/poker_constants.js @@ -0,0 +1,38 @@ +// poker_constants.js — shared constants for poker entity scripts +// Script.require() this from poker_sit.js, poker_spawn.js, etc. +// +// Seat offsets are in local space relative to the table entity's position +// and rotation. Y is height above table origin, X/Z are horizontal offsets. +// Yaw is the direction the seated avatar faces, in degrees. + +module.exports = { + + // Seat layouts keyed by seat count + POKER_SEATS: { + 7: [ + { offset: { x: 0.95, y: 0.8, z: -1.00 }, yaw: 105 }, + { offset: { x: -0.05, y: 0.8, z: -1.80 }, yaw: 180 }, + { offset: { x: -1.05, y: 0.8, z: -1.07 }, yaw: -110 }, + { offset: { x: -1.15, y: 0.8, z: 0.00 }, yaw: -90 }, + { offset: { x: -1.05, y: 0.8, z: 1.07 }, yaw: -70 }, + { offset: { x: -0.05, y: 0.8, z: 1.80 }, yaw: 0 }, + { offset: { x: 0.95, y: 0.8, z: 1.00 }, yaw: 80 }, + ], + // Add 6 and 9 seat layouts here when custom tables are ready + }, + + // Seat pad entity appearance + POKER_SEAT_PAD: { + dimensions: { x: 0.6, y: 0.6, z: 0.01 }, + imageURL: "https://wizards.cyou/poker/sit.png", + alpha: 0.7, + }, + + // Table mesh URL + POKER_TABLE_MODEL_URL: "https://wizards.cyou/poker/table.glb", + + // API base URLs + API_BASE: "https://wizards.cyou/api", + POKER_BASE: "https://wizards.cyou/poker", + +}; diff --git a/scripts/poker_sit.js b/scripts/poker_sit.js new file mode 100644 index 0000000..59f8a70 --- /dev/null +++ b/scripts/poker_sit.js @@ -0,0 +1,215 @@ +(function () { + // poker_sit.js — Poker Seat Entity Script + // + // Attach to each seat pad entity. userData must contain: + // { "tableID": "{uuid-of-table-mesh-entity}", "pokerID": "main", "seatIndex": 3 } + // + // tableID — Overte entity UUID of the table mesh, used for position calculations + // pokerID — poker table slug used for API calls (matches table config id) + // + // Seat layout is loaded from poker_constants.js so this script + // does not need to know anything about the table geometry. + + var constants = Script.require(Script.resolvePath("poker_constants.js")); + var POKER_SEATS = constants.POKER_SEATS; + var API_BASE = constants.API_BASE; + var POKER_BASE = constants.POKER_BASE; + var JUMP_ACTION = 50; + + var _entityID = null; + var _tableID = null; // Overte entity UUID of the table mesh + var _pokerID = null; // poker table slug for API calls + var _seatIndex = null; + var _seatData = null; + var _token = null; + var _username = null; + var _busy = false; + var isSeated = false; + + // ── helpers ────────────────────────────────────────────────── + + function getUserData(entityID) { + try { + var props = Entities.getEntityProperties(entityID, ["userData"]); + return JSON.parse(props.userData || "{}"); + } catch (e) { + print("[pokerSeat] userData parse error: " + e); + return {}; + } + } + + function getTableSeatCount() { + var ud = getUserData(_tableID); + return ud.seatCount || 7; + } + + function getSeatPosition() { + var tableProps = Entities.getEntityProperties(_tableID, ["position", "rotation"]); + var worldOffset = Vec3.multiplyQbyV(tableProps.rotation, _seatData.offset); + return Vec3.sum(tableProps.position, worldOffset); + } + + function getSeatRotation() { + return Quat.fromPitchYawRollDegrees(0, _seatData.yaw, 0); + } + + function notify(msg) { + Window.displayAnnouncement(msg); + } + + // ── session ────────────────────────────────────────────────── + + function ensureSession(cb) { + if (_token) { cb(null, _token); return; } + _username = MyAvatar.displayName; + if (!_username) { cb("Not logged in to Overte"); return; } + + print("[pokerSeat] requesting session from: " + API_BASE + "/session for " + _username); + var xhr = new XMLHttpRequest(); + xhr.open("POST", API_BASE + "/session", true); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4) return; + if (xhr.status === 200) { + try { + _token = JSON.parse(xhr.responseText).token; + Script.setTimeout(function () { _token = null; }, 5 * 60 * 1000); + cb(null, _token); + } catch (e) { cb("Session parse error"); } + } else if (xhr.status === 403) { + cb("Must be in-world to sit down"); + } else { + cb("Session error (" + xhr.status + ")"); + } + }; + xhr.send(JSON.stringify({ username: _username })); + } + + // ── sit / stand ────────────────────────────────────────────── + + function onSit() { + if (isSeated || _busy) return; + _busy = true; + + if (!_seatData) { + notify("Seat not configured (index " + _seatIndex + ")"); + _busy = false; + return; + } + + ensureSession(function (err) { + if (err) { notify(err); _busy = false; return; } + + // POST to server — handles buy-in deduction and seat reservation. + // Server will reject if seat is taken or balance insufficient. + var xhr = new XMLHttpRequest(); + xhr.open("POST", POKER_BASE + "/tables/" + _pokerID + "/sit", true); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("Authorization", "Bearer " + _token); + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4) return; + print("[pokerSeat] sit error response: " + xhr.status + " " + xhr.responseText); + if (xhr.status === 200) { + // Seat reserved — now physically sit the avatar + MyAvatar.beginSit(getSeatPosition(), getSeatRotation()); + isSeated = true; + Entities.editEntity(_entityID, { visible: false }); + Controller.actionEvent.connect(onActionEvent); + + Messages.sendMessage("poker:seat", JSON.stringify({ + event: "sit", + tableID: _tableID, + pokerID: _pokerID, + seatIndex: _seatIndex, + username: _username, + })); + } else { + try { + var err = JSON.parse(xhr.responseText); + notify(err.error || "Could not sit down"); + } catch (e) { + notify("Could not sit down (" + xhr.status + ")"); + } + } + _busy = false; + }; + xhr.send(JSON.stringify({ + seatIndex: parseInt(_seatIndex), + })); + }); + } + + function onStand() { + if (!isSeated) return; + + var xhr = new XMLHttpRequest(); + xhr.open("POST", POKER_BASE + "/tables/" + _pokerID + "/stand", true); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("Authorization", "Bearer " + _token); + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4) return; + // Stand regardless of server response — worst case server cleans up on disconnect + MyAvatar.endSit(getSeatPosition(), getSeatRotation()); + isSeated = false; + Entities.editEntity(_entityID, { visible: true }); + Controller.actionEvent.disconnect(onActionEvent); + + Messages.sendMessage("poker:seat", JSON.stringify({ + event: "stand", + tableID: _tableID, + seatIndex: _seatIndex, + username: _username, + })); + }; + xhr.send(JSON.stringify({ seatIndex: parseInt(_seatIndex) })); + } + + function onActionEvent(action, value) { + if (action === JUMP_ACTION && value > 0) { + onStand(); + } + } + + // ── lifecycle ──────────────────────────────────────────────── + + this.preload = function (entityID) { + _entityID = entityID; + + var ud = getUserData(entityID); + _tableID = ud.tableID || null; + _pokerID = ud.pokerID || null; + _seatIndex = ud.seatIndex !== undefined ? String(ud.seatIndex) : null; + + if (!_tableID || !_pokerID || _seatIndex === null) { + print("[pokerSeat] missing tableID, pokerID, or seatIndex in userData on " + entityID); + return; + } + + // Load seat geometry from constants keyed by table's seat count + var seatCount = getTableSeatCount(); + var layout = POKER_SEATS[seatCount]; + if (!layout || !layout[parseInt(_seatIndex)]) { + print("[pokerSeat] no layout for seatCount=" + seatCount + " index=" + _seatIndex); + return; + } + + _seatData = layout[parseInt(_seatIndex)]; + print("[pokerSeat] loaded seat " + _seatIndex + " at table " + _tableID); + }; + + // Mouse + controller triggers + this.clickDownOnEntity = function () { onSit(); }; + this.clickUpOnEntity = function () { _busy = false; }; + this.startNearTrigger = function () { onSit(); }; + this.stopNearTrigger = function () { _busy = false; }; + this.startFarTrigger = function () { onSit(); }; + this.stopFarTrigger = function () { _busy = false; }; + + this.unload = function () { + if (isSeated) { + Controller.actionEvent.disconnect(onActionEvent); + isSeated = false; + } + _token = null; + }; +});