(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) 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; var _pokerID = null; 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 doStand(cb) { var tok = _token; var xhr = new XMLHttpRequest(); xhr.open("POST", POKER_BASE + "/tables/" + _pokerID + "/stand", true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Authorization", "Bearer " + tok); xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; _token = null; if (isSeated) { 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, })); } if (cb) cb(); }; xhr.send(JSON.stringify({ seatIndex: parseInt(_seatIndex) })); } 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; } 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 response: " + xhr.status + " " + xhr.responseText); if (xhr.status === 200) { 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, })); _busy = false; } else { try { var resp = JSON.parse(xhr.responseText); if (resp.error === "already seated") { print("[pokerSeat] recovering stale seat, standing first"); doStand(function () { onSit(); }); return; } notify(resp.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; doStand(null); } 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; } 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); }; 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; }; });