213 lines
7.9 KiB
JavaScript
213 lines
7.9 KiB
JavaScript
(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
|
|
print("[pokerCards] WS connected, identifying as: " + _username);
|
|
_ws.send(JSON.stringify({ type: "identify", username: _username }));
|
|
};
|
|
|
|
_ws.onmessage = function(event) {
|
|
print("[pokerCards] WS message received: " + event.data.substring(0, 100));
|
|
try {
|
|
var msg = JSON.parse(event.data);
|
|
if (msg.type === "hand:dealt") {
|
|
onCardsDealt(msg.cards);
|
|
} else if (msg.type === "hand:end") {
|
|
hideCards();
|
|
} 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,
|
|
}));
|
|
}
|
|
}
|
|
} 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]);
|
|
|
|
_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");
|
|
|
|
print("[pokerCards] spawned renderer entity: " + _rendererID);
|
|
}
|
|
|
|
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 onWebEventReceived(id, data) {
|
|
print("[pokerCards] webEvent from " + id + " renderer is " + _rendererID);
|
|
if (id !== _rendererID) return;
|
|
try {
|
|
var msg = JSON.parse(data);
|
|
print("[pokerCards] renderer msg type: " + msg.type);
|
|
if (msg.type === 'rendererReady') {
|
|
print("[pokerCards] renderer ready, sending cards");
|
|
Entities.emitScriptEvent(_rendererID, JSON.stringify({
|
|
type: 'dealCards',
|
|
card1: _pendingCards.card1,
|
|
card2: _pendingCards.card2,
|
|
}));
|
|
} else if (msg.type === 'cardTexture') {
|
|
print("[pokerCards] texture received, length: " + msg.dataURI.length);
|
|
onTextureReady(msg.dataURI);
|
|
}
|
|
} catch(e) {
|
|
print("[pokerCards] webEvent parse error: " + 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;
|
|
};
|
|
});
|