overte-www/scripts/poker_cards.js

244 lines
8.9 KiB
JavaScript
Raw Normal View History

2026-03-20 08:02:39 +00:00
(function() {
// poker_cards.js — attach to the card hand mesh entity
// userData: { "pokerID": "main", "tableID": "{uuid}" }
var constants = Script.require(Script.resolvePath("poker_constants.js"));
var API_BASE = constants.API_BASE;
var _entityID = null;
var _pokerID = null;
var _username = null;
var _token = null;
var _ws = null;
var _rendererID = null; // local web entity that generates the texture
var _materialID = null; // local material entity applied to this mesh
var _wsRetryTimer = null;
var _pendingCards = null;
// ── helpers ──────────────────────────────────────────────────
function getUserData(id) {
try {
return JSON.parse(Entities.getEntityProperties(id, ["userData"]).userData || "{}");
} catch(e) { return {}; }
}
// ── session ──────────────────────────────────────────────────
function ensureSession(cb) {
if (_token) { cb(null, _token); return; }
_username = AccountServices.username;
if (!_username) { cb("not logged in"); return; }
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;
cb(null, _token);
} catch(e) { cb("session parse error"); }
} else {
cb("session error " + xhr.status);
}
};
xhr.send(JSON.stringify({ username: _username }));
}
// ── WebSocket ────────────────────────────────────────────────
function connectWS() {
var wsURL = API_BASE.replace("https://", "wss://").replace("http://", "ws://")
.replace("/api", "/ws");
_ws = new WebSocket(wsURL);
_ws.onopen = function() {
print("[pokerCards] WS connected");
// Identify so server can route private messages
2026-03-21 22:16:30 +00:00
print("[pokerCards] WS connected, identifying as: " + _username);
2026-03-20 08:02:39 +00:00
_ws.send(JSON.stringify({ type: "identify", username: _username }));
};
_ws.onmessage = function(event) {
2026-03-21 22:16:30 +00:00
print("[pokerCards] WS message received: " + event.data.substring(0, 100));
2026-03-20 08:02:39 +00:00
try {
var msg = JSON.parse(event.data);
if (msg.type === "hand:dealt") {
onCardsDealt(msg.cards);
} else if (msg.type === "hand:end") {
hideCards();
2026-03-21 22:16:30 +00:00
} else if (msg.type === "player:disconnect:stand") {
if (msg.username === _username) {
print("[pokerCards] deferred stand complete, chips returned: " + msg.stack);
Messages.sendLocalMessage("poker:standComplete", JSON.stringify({
username: msg.username,
stack: msg.stack,
}));
}
2026-03-20 08:02:39 +00:00
}
} catch(e) {
print("[pokerCards] WS parse error: " + e);
}
};
_ws.onclose = function() {
print("[pokerCards] WS closed, retrying in 5s");
_wsRetryTimer = Script.setTimeout(connectWS, 5000);
};
_ws.onerror = function(e) {
print("[pokerCards] WS error: " + e);
};
}
// ── card rendering ───────────────────────────────────────────
function onCardsDealt(cards) {
if (!cards || cards.length < 2) return;
var card1 = parseCard(cards[0]);
var card2 = parseCard(cards[1]);
print("[pokerCards] dealt " + cards[0] + " " + cards[1]);
2026-03-21 22:16:30 +00:00
_pendingCards = { card1: card1, card2: card2 };
2026-03-20 08:02:39 +00:00
_rendererID = Entities.addEntity({
type: "Web",
name: "card-renderer",
sourceUrl: "https://wizards.cyou/tablet/card-face.html",
2026-03-21 22:16:30 +00:00
position: MyAvatar.position,
2026-03-20 08:02:39 +00:00
dimensions: { x: 0.01, y: 0.01, z: 0.01 },
visible: false,
}, "local");
2026-03-21 22:16:30 +00:00
print("[pokerCards] spawned renderer entity: " + _rendererID);
2026-03-20 08:02:39 +00:00
}
function onTextureReady(dataURI) {
// Clean up renderer
if (_rendererID) {
Entities.deleteEntity(_rendererID);
_rendererID = null;
}
// Remove old material if any
if (_materialID) {
Entities.deleteEntity(_materialID);
_materialID = null;
}
// Apply local material entity to card mesh
_materialID = Entities.addEntity({
type: "Material",
name: "card-face-material",
parentID: _entityID,
materialURL: "materialData",
materialData: JSON.stringify({
materials: {
model: "hifi_pbr",
albedoMap: dataURI,
roughness: 0.8,
metallic: 0.0,
}
}),
priority: 1,
}, "local");
print("[pokerCards] card texture applied");
}
function hideCards() {
if (_materialID) {
Entities.deleteEntity(_materialID);
_materialID = null;
}
print("[pokerCards] cards hidden");
}
function parseCard(str) {
// "As" → { rank: "A", suit: "♠" }
// "10h" → { rank: "10", suit: "♥" }
var suitMap = { s: '♠', h: '♥', d: '♦', c: '♣' };
var suit = suitMap[str.slice(-1)] || '♠';
var rank = str.slice(0, -1);
return { rank: rank, suit: suit };
}
function onCardsDealt(cards) {
if (!cards || cards.length < 2) return;
var card1 = parseCard(cards[0]);
var card2 = parseCard(cards[1]);
print("[pokerCards] dealt " + cards[0] + " " + cards[1]);
_pendingCards = { card1: card1, card2: card2 };
_rendererID = Entities.addEntity({
type: "Web",
name: "card-renderer",
sourceUrl: "https://wizards.cyou/tablet/card-face.html",
position: MyAvatar.position,
dimensions: { x: 0.01, y: 0.01, z: 0.01 },
visible: false,
}, "local");
}
function onWebEventReceived(id, data) {
2026-03-21 22:16:30 +00:00
print("[pokerCards] webEvent from " + id + " renderer is " + _rendererID);
2026-03-20 08:02:39 +00:00
if (id !== _rendererID) return;
try {
var msg = JSON.parse(data);
2026-03-21 22:16:30 +00:00
print("[pokerCards] renderer msg type: " + msg.type);
2026-03-20 08:02:39 +00:00
if (msg.type === 'rendererReady') {
2026-03-21 22:16:30 +00:00
print("[pokerCards] renderer ready, sending cards");
2026-03-20 08:02:39 +00:00
Entities.emitScriptEvent(_rendererID, JSON.stringify({
type: 'dealCards',
card1: _pendingCards.card1,
card2: _pendingCards.card2,
}));
} else if (msg.type === 'cardTexture') {
2026-03-21 22:16:30 +00:00
print("[pokerCards] texture received, length: " + msg.dataURI.length);
2026-03-20 08:02:39 +00:00
onTextureReady(msg.dataURI);
}
2026-03-21 22:16:30 +00:00
} catch(e) {
print("[pokerCards] webEvent parse error: " + e);
}
2026-03-20 08:02:39 +00:00
}
// ── web entity event listener ────────────────────────────────
function onWebEventReceived(id, data) {
if (id !== _rendererID) return;
try {
var msg = JSON.parse(data);
if (msg.type === "cardTexture") {
onTextureReady(msg.dataURI);
}
} catch(e) {}
}
// ── lifecycle ────────────────────────────────────────────────
this.preload = function(entityID) {
_entityID = entityID;
var ud = getUserData(entityID);
_pokerID = ud.pokerID || "main";
Entities.webEventReceived.connect(onWebEventReceived);
ensureSession(function(err) {
if (err) { print("[pokerCards] session error: " + err); return; }
connectWS();
});
};
this.unload = function() {
if (_wsRetryTimer) Script.clearTimeout(_wsRetryTimer);
if (_ws) _ws.close();
if (_materialID) Entities.deleteEntity(_materialID);
if (_rendererID) Entities.deleteEntity(_rendererID);
Entities.webEventReceived.disconnect(onWebEventReceived);
_token = null;
};
});