overte-www/scripts/poker_sit.js

294 lines
11 KiB
JavaScript
Raw Permalink Normal View History

2026-03-18 05:50:44 +00:00
(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)
2026-03-18 06:49:51 +00:00
var constants = Script.require(Script.resolvePath("poker_constants.js"));
2026-03-18 05:50:44 +00:00
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;
2026-03-18 06:49:51 +00:00
var _tableID = null;
var _pokerID = null;
2026-03-18 05:50:44 +00:00
var _seatIndex = null;
var _seatData = null;
var _token = null;
var _username = null;
var _busy = false;
2026-03-21 22:16:30 +00:00
var _pendingStand = false;
2026-03-18 05:50:44 +00:00
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() {
2026-03-19 03:39:16 +00:00
var tableProps = Entities.getEntityProperties(_tableID, ["rotation"]);
var tableYaw = Quat.safeEulerAngles(tableProps.rotation).y;
return Quat.fromPitchYawRollDegrees(0, tableYaw + _seatData.yaw, 0);
2026-03-18 05:50:44 +00:00
}
function notify(msg) {
Window.displayAnnouncement(msg);
}
// ── session ──────────────────────────────────────────────────
function ensureSession(cb) {
if (_token) { cb(null, _token); return; }
2026-03-18 06:49:51 +00:00
2026-03-21 22:16:30 +00:00
_username = AccountServices.username;
2026-03-18 05:50:44 +00:00
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 ──────────────────────────────────────────────
2026-03-18 06:49:51 +00:00
function doStand(cb) {
2026-03-21 22:16:30 +00:00
_username = _username || AccountServices.username;
2026-03-21 23:08:28 +00:00
var tok = _token
// If no token, need to re-auth first
if (!_token) {
ensureSession(function(err) {
if (err) { print("[pokerSeat] doStand: no session: " + err); if (cb) cb(); return; }
doStand(cb);
});
return;
}
2026-03-21 22:16:30 +00:00
print("[pokerSeat] doStand called, token: " + (tok ? "ok" : "null") + " username: " + _username + " pokerID: " + _pokerID);
2026-03-18 06:49:51 +00:00
var xhr = new XMLHttpRequest();
xhr.open("POST", POKER_BASE + "/tables/" + _pokerID + "/stand", true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", "Bearer " + tok);
2026-03-21 22:16:30 +00:00
xhr.onreadystatechange = function() {
2026-03-18 06:49:51 +00:00
if (xhr.readyState !== 4) return;
2026-03-21 22:16:30 +00:00
print("[pokerSeat] stand response: " + xhr.status + " " + xhr.responseText);
var resp = {};
try { resp = JSON.parse(xhr.responseText); } catch(e) {}
2026-03-21 23:08:28 +00:00
// Mid-hand block — server refused, queue a deferred stand
if (xhr.status === 400) {
print("[pokerSeat] stand deferred (mid-hand)");
2026-03-21 22:16:30 +00:00
_pendingStand = true;
2026-03-21 23:08:28 +00:00
notify("You'll stand at the end of this hand");
if (cb) cb();
return;
}
// Auth failure — token expired, don't touch avatar state
if (xhr.status === 401) {
print("[pokerSeat] stand 401, token expired");
_token = null;
if (cb) cb();
return;
}
// Any other non-200 — server error, log and bail without touching avatar
if (xhr.status !== 200) {
print("[pokerSeat] stand failed (" + xhr.status + "), leaving avatar state intact");
2026-03-21 22:16:30 +00:00
if (cb) cb();
return;
}
2026-03-21 23:08:28 +00:00
// 200 — successful stand, clean up everything
2026-03-18 06:49:51 +00:00
_token = null;
2026-03-21 23:08:28 +00:00
_pendingStand = false;
2026-03-18 06:49:51 +00:00
if (isSeated) {
MyAvatar.endSit(getSeatPosition(), getSeatRotation());
isSeated = false;
2026-03-19 05:05:37 +00:00
Entities.editEntity(_entityID, { locked: false });
2026-03-18 06:49:51 +00:00
Entities.editEntity(_entityID, { visible: true });
2026-03-19 05:05:37 +00:00
Entities.editEntity(_entityID, { locked: true });
2026-03-18 06:49:51 +00:00
Controller.actionEvent.disconnect(onActionEvent);
Messages.sendMessage("poker:seat", JSON.stringify({
event: "stand",
tableID: _tableID,
seatIndex: _seatIndex,
username: _username,
}));
}
if (cb) cb();
2026-03-21 23:08:28 +00:00
};
2026-03-18 06:49:51 +00:00
xhr.send(JSON.stringify({ seatIndex: parseInt(_seatIndex) }));
}
2026-03-18 05:50:44 +00:00
function onSit() {
2026-03-18 09:04:31 +00:00
print("[pokerSeat] onSit called, seat=" + _seatIndex + " isSeated=" + isSeated + " _busy=" + _busy + " _seatData=" + (_seatData ? "ok" : "null"));
2026-03-18 05:50:44 +00:00
if (isSeated || _busy) return;
_busy = true;
2026-03-18 09:04:31 +00:00
Script.setTimeout(function () { _busy = false; }, 3000);
2026-03-18 05:50:44 +00:00
if (!_seatData) {
notify("Seat not configured (index " + _seatIndex + ")");
_busy = false;
return;
}
ensureSession(function (err) {
if (err) { notify(err); _busy = false; return; }
2026-03-21 22:16:30 +00:00
_username = AccountServices.username;
2026-03-21 23:08:28 +00:00
if (!_username) { notify("Not logged in to Overte"); _busy = false; return; }
2026-03-18 05:50:44 +00:00
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;
2026-03-18 06:34:58 +00:00
print("[pokerSeat] sit response: " + xhr.status + " " + xhr.responseText);
2026-03-18 05:50:44 +00:00
if (xhr.status === 200) {
MyAvatar.beginSit(getSeatPosition(), getSeatRotation());
isSeated = true;
2026-03-19 05:05:37 +00:00
Entities.editEntity(_entityID, { locked: false });
2026-03-18 05:50:44 +00:00
Entities.editEntity(_entityID, { visible: false });
2026-03-19 05:05:37 +00:00
Entities.editEntity(_entityID, { locked: true });
2026-03-18 05:50:44 +00:00
Controller.actionEvent.connect(onActionEvent);
Messages.sendMessage("poker:seat", JSON.stringify({
2026-03-18 06:49:51 +00:00
event: "sit",
tableID: _tableID,
pokerID: _pokerID,
seatIndex: _seatIndex,
username: _username,
2026-03-18 05:50:44 +00:00
}));
2026-03-18 06:34:58 +00:00
_busy = false;
2026-03-18 05:50:44 +00:00
} else {
try {
2026-03-18 06:34:58 +00:00
var resp = JSON.parse(xhr.responseText);
if (resp.error === "already seated") {
print("[pokerSeat] recovering stale seat, standing first");
2026-03-18 06:49:51 +00:00
doStand(function () { onSit(); });
2026-03-18 06:34:58 +00:00
return;
}
notify(resp.error || "Could not sit down");
2026-03-18 05:50:44 +00:00
} catch (e) {
notify("Could not sit down (" + xhr.status + ")");
}
2026-03-18 06:34:58 +00:00
_busy = false;
2026-03-18 05:50:44 +00:00
}
};
2026-03-18 06:49:51 +00:00
xhr.send(JSON.stringify({ seatIndex: parseInt(_seatIndex) }));
2026-03-18 05:50:44 +00:00
});
}
2026-03-18 06:34:58 +00:00
function onStand() {
if (!isSeated) return;
2026-03-21 22:16:30 +00:00
if (_pendingStand) {
print("[pokerSeat] stand already pending, ignoring");
return;
}
2026-03-18 06:34:58 +00:00
doStand(null);
}
2026-03-18 05:50:44 +00:00
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];
2026-03-18 09:04:31 +00:00
if (!layout || layout[parseInt(_seatIndex)] === undefined) {
2026-03-18 05:50:44 +00:00
print("[pokerSeat] no layout for seatCount=" + seatCount + " index=" + _seatIndex);
return;
}
_seatData = layout[parseInt(_seatIndex)];
print("[pokerSeat] loaded seat " + _seatIndex + " at table " + _tableID);
};
this.startNearTrigger = function () { onSit(); };
2026-03-18 09:04:31 +00:00
this.stopNearTrigger = function () {};
2026-03-18 05:50:44 +00:00
this.startFarTrigger = function () { onSit(); };
2026-03-18 09:04:31 +00:00
this.stopFarTrigger = function () {};
this.clickDownOnEntity = function () { onSit(); };
this.clickUpOnEntity = function () {};
2026-03-18 05:50:44 +00:00
this.unload = function () {
if (isSeated) {
Controller.actionEvent.disconnect(onActionEvent);
isSeated = false;
2026-03-21 22:16:30 +00:00
_pendingStand = false;
2026-03-18 05:50:44 +00:00
}
2026-03-21 23:08:28 +00:00
Messages.messageReceived.disconnect(onMessage);
Messages.unsubscribe("poker:standComplete");
2026-03-18 05:50:44 +00:00
_token = null;
};
2026-03-21 23:08:28 +00:00
Messages.subscribe("poker:standComplete");
Messages.messageReceived.connect(onMessage);
function onMessage(channel, message) {
if (channel !== "poker:standComplete") return;
try {
var data = JSON.parse(message);
if (data.username !== _username) return;
} catch(e) { return; }
_pendingStand = false;
// Now actually stand
doStand(null);
}
2026-03-18 05:50:44 +00:00
});