Compare commits

..

No commits in common. "c259168f6f740ecaee3da1a3cc737387d0a935ab" and "f75abe564d99cdb75d261d27d2974d4c3dbd0316" have entirely different histories.

6 changed files with 4 additions and 785 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View file

@ -1,183 +0,0 @@
(function() {
var ADMIN_USER = "nak";
if (AccountServices.username !== ADMIN_USER) {
return;
}
var APP_NAME = "POKER";
var APP_URL = "https://wizards.cyou/tablet/poker-admin.html";
var APP_ICON = "https://wizards.cyou/tablet/poker-admin-icon.svg";
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
text: APP_NAME,
icon: APP_ICON
});
var webEventHandler = function(data) {
try {
var msg = JSON.parse(data);
if (msg.type === "ready") {
tablet.emitScriptEvent(JSON.stringify({
type: "init",
username: AccountServices.username || "",
position: MyAvatar.position,
rotation: MyAvatar.orientation,
}));
} else if (msg.type === "getPosition") {
tablet.emitScriptEvent(JSON.stringify({
type: "position",
position: MyAvatar.position,
rotation: MyAvatar.orientation,
}));
} else if (msg.type === "spawnSeats") {
spawnTable(msg.pokerID, msg.seatCount, msg.position, msg.rotation);
} else if (msg.type === "deleteTable") {
deleteTableEntities(msg.pokerID);
}
} catch(e) {
print("[pokerAdmin] web event error: " + e);
}
};
function spawnTable(pokerID, seatCount, avatarPosition, avatarRotation) {
var constants = Script.require(Script.resolvePath("poker_constants.js"));
var layout = constants.POKER_SEATS[seatCount];
if (!layout) {
print("[pokerAdmin] no layout for seatCount=" + seatCount);
tablet.emitScriptEvent(JSON.stringify({
type: "spawnError",
error: "No seat layout for seatCount=" + seatCount,
}));
return;
}
// Place table in front of avatar at floor level
// Use only the yaw component of avatar rotation so table sits flat
var yaw = Quat.safeEulerAngles(avatarRotation).y;
var tableRot = Quat.fromPitchYawRollDegrees(0, yaw - 90, 0);
// 2.5m in front of avatar, snapped to floor
var forward = Vec3.multiplyQbyV(tableRot, { x: 0, y: 0, z: -2.5 });
var tablePos = {
x: avatarPosition.x + forward.x,
y: -0.45,
z: avatarPosition.z + forward.z,
};
// Spawn table model — natural dimensions, not squished
var tableID = Entities.addEntity({
type: "Model",
name: "poker_table_" + pokerID,
modelURL: constants.POKER_TABLE_MODEL_URL,
position: tablePos,
rotation: tableRot,
naturalDimensions: true,
registrationPoint: { x: 0.5, y: 0, z: 0.5 },
userData: JSON.stringify({
pokerID: pokerID,
seatCount: seatCount,
}),
grabbable: false,
locked: false,
});
Script.setTimeout(function() {
Entities.editEntity(tableID, { locked: true });
}, 2000);
print("[pokerAdmin] spawned table entity " + tableID + " for " + pokerID);
// Spawn seat pads as children of the table entity
// Offsets are in table-local space so no world transform needed
for (var i = 0; i < seatCount; i++) {
var seat = layout[i];
var seatRot = Quat.fromPitchYawRollDegrees(-90, seat.yaw, 0);
var seatOffset = {
x: seat.offset.x,
y: seat.offset.y - 0.13,
z: seat.offset.z
};
Entities.addEntity({
type: "Image",
name: "poker_seat_" + pokerID + "_" + i,
imageURL: constants.POKER_SEAT_PAD.imageURL,
dimensions: constants.POKER_SEAT_PAD.dimensions,
alpha: constants.POKER_SEAT_PAD.alpha,
parentID: tableID,
localPosition: seatOffset,
localRotation: seatRot,
script: "https://wizards.cyou/scripts/poker_sit.js",
userData: JSON.stringify({
tableID: tableID,
pokerID: pokerID,
seatIndex: i,
}),
triggerable: true,
grabbable: false,
ignorePickIntersection: false,
locked: true,
});
print("[pokerAdmin] spawned seat " + i + " parented to " + tableID);
}
tablet.emitScriptEvent(JSON.stringify({
type: "spawnComplete",
tableEntityID: tableID,
}));
}
function deleteTableEntities(pokerID) {
// Find table entity by name
var results = Entities.findEntitiesByName(
"poker_table_" + pokerID, MyAvatar.position, 100, false
);
if (results.length === 0) {
print("[pokerAdmin] no entities found for table " + pokerID);
tablet.emitScriptEvent(JSON.stringify({
type: "deleteEntitiesComplete",
pokerID: pokerID,
found: false,
}));
return;
}
results.forEach(function(tableEntityID) {
// Find and delete all children (seat pads)
var children = Entities.getChildrenIDs(tableEntityID);
children.forEach(function(childID) {
Entities.editEntity(childID, { locked: false });
Entities.deleteEntity(childID);
print("[pokerAdmin] deleted seat entity " + childID);
});
// Delete the table itself
Entities.editEntity(tableEntityID, { locked: false });
Entities.deleteEntity(tableEntityID);
print("[pokerAdmin] deleted table entity " + tableEntityID);
});
tablet.emitScriptEvent(JSON.stringify({
type: "deleteEntitiesComplete",
pokerID: pokerID,
found: true,
}));
}
button.clicked.connect(function() {
tablet.gotoWebScreen(APP_URL);
tablet.webEventReceived.connect(webEventHandler);
});
Script.scriptEnding.connect(function() {
tablet.removeButton(button);
tablet.webEventReceived.disconnect(webEventHandler);
});
})();

View file

@ -23,13 +23,13 @@ module.exports = {
// Seat pad entity appearance
POKER_SEAT_PAD: {
dimensions: { x: 0.4, y: 0.4, z: 0.01 },
imageURL: "https://wizards.cyou/images/sit.png",
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/models/poker_table.glb",
POKER_TABLE_MODEL_URL: "https://wizards.cyou/poker/table.glb",
// API base URLs
API_BASE: "https://wizards.cyou/api",

View file

@ -47,9 +47,7 @@
}
function getSeatRotation() {
var tableProps = Entities.getEntityProperties(_tableID, ["rotation"]);
var tableYaw = Quat.safeEulerAngles(tableProps.rotation).y;
return Quat.fromPitchYawRollDegrees(0, tableYaw + _seatData.yaw, 0);
return Quat.fromPitchYawRollDegrees(0, _seatData.yaw, 0);
}
function notify(msg) {
@ -99,9 +97,7 @@
if (isSeated) {
MyAvatar.endSit(getSeatPosition(), getSeatRotation());
isSeated = false;
Entities.editEntity(_entityID, { locked: false });
Entities.editEntity(_entityID, { visible: true });
Entities.editEntity(_entityID, { locked: true });
Controller.actionEvent.disconnect(onActionEvent);
Messages.sendMessage("poker:seat", JSON.stringify({
event: "stand",
@ -140,9 +136,7 @@
if (xhr.status === 200) {
MyAvatar.beginSit(getSeatPosition(), getSeatRotation());
isSeated = true;
Entities.editEntity(_entityID, { locked: false });
Entities.editEntity(_entityID, { visible: false });
Entities.editEntity(_entityID, { locked: true });
Controller.actionEvent.connect(onActionEvent);
Messages.sendMessage("poker:seat", JSON.stringify({
event: "sit",

View file

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#FFFFFF" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 463.644 463.644" xml:space="preserve">
<path id="XMLID_1_" d="M463.164,146.031l-77.369,288.746c-1.677,6.26-7.362,10.4-13.556,10.401c-1.198,0-2.414-0.155-3.625-0.479
l-189.261-50.712c-7.472-2.003-11.922-9.711-9.919-17.183l2.041-7.616c1.287-4.801,6.222-7.647,11.023-6.363
c4.801,1.287,7.65,6.222,6.363,11.023l-1.013,3.78l181.587,48.656l75.314-281.076l-77.031-20.64
c-4.801-1.287-7.651-6.222-6.364-11.023s6.225-7.648,11.022-6.364l80.869,21.668C460.718,130.853,465.167,138.56,463.164,146.031z
M166.128,56.029c-4.971,0-9,4.029-9,9v8.565c0,4.971,4.029,9,9,9s9-4.029,9-9v-8.565C175.128,60.058,171.099,56.029,166.128,56.029
z M280.889,176.762c2.202,3.114,2.202,7.278,0,10.393l-41.716,58.996c-1.687,2.385-4.427,3.804-7.349,3.804
c-2.921,0-5.662-1.418-7.348-3.804l-41.718-58.996c-2.202-3.114-2.202-7.278,0-10.393l41.718-58.996
c1.687-2.385,4.427-3.804,7.348-3.804c2.922,0,5.662,1.418,7.349,3.804L280.889,176.762z M262.518,181.958l-30.694-43.408
l-30.694,43.408l30.694,43.407L262.518,181.958z M343.016,380.764l-2.216,8.273c-1.286,4.801,1.563,9.736,6.365,11.022
c0.78,0.209,1.563,0.309,2.334,0.309c3.974,0,7.611-2.653,8.688-6.674l2.216-8.273c1.286-4.801-1.563-9.736-6.365-11.022
C349.237,373.111,344.302,375.963,343.016,380.764z M112.375,215.913c2.577-0.69,5.056-1.089,7.454-1.195V32.492
c0-7.736,6.293-14.029,14.028-14.029h195.935c7.736,0,14.03,6.293,14.03,14.029v182.225c2.396,0.106,4.875,0.505,7.45,1.195
c16.511,4.424,26.346,21.457,21.922,37.968c-4.28,15.974-17.951,28.108-29.372,36.404v41.139c0,7.736-6.294,14.03-14.03,14.03
H133.857c-7.735,0-14.028-6.294-14.028-14.03v-41.137c-11.422-8.295-25.093-20.428-29.376-36.405
c-2.143-7.996-1.042-16.35,3.1-23.523C97.695,223.186,104.38,218.055,112.375,215.913z M343.821,267.05
c6.531-6.172,10.424-12,11.985-17.828c1.855-6.924-2.27-14.067-9.194-15.923c-1.047-0.281-1.97-0.451-2.791-0.538V267.05z
M137.829,327.454h187.992v-41.7c-0.001-0.08-0.001-0.161,0-0.241v-59.907c-0.003-0.13-0.003-0.261,0-0.391V36.463H137.829v188.755
c0.003,0.13,0.003,0.261,0,0.392v59.898c0.001,0.084,0.001,0.168,0,0.252V327.454z M107.84,249.222
c1.563,5.83,5.457,11.66,11.989,17.832v-34.292c-0.822,0.086-1.746,0.256-2.794,0.537c-3.353,0.898-6.156,3.051-7.894,6.061
C107.404,242.369,106.942,245.871,107.84,249.222z M173.576,405.019l-79.363,21.265L18.897,145.209l77.031-20.641
c4.801-1.287,7.651-6.222,6.364-11.023c-1.287-4.801-6.225-7.65-11.022-6.364L10.402,128.85c-3.614,0.968-6.637,3.29-8.512,6.538
c-1.876,3.249-2.376,7.029-1.407,10.644l77.37,288.743c0.968,3.616,3.29,6.641,6.54,8.518c2.166,1.25,4.567,1.89,7,1.89
c1.216,0,2.439-0.16,3.644-0.482l83.199-22.293c4.801-1.287,7.651-6.222,6.364-11.022
C183.312,406.581,178.377,403.734,173.576,405.019z M51.298,156.782c-4.801,1.287-7.65,6.222-6.364,11.023l2.217,8.274
c1.078,4.021,4.714,6.673,8.688,6.673c0.771,0,1.555-0.1,2.335-0.309c4.801-1.287,7.65-6.222,6.364-11.023l-2.217-8.274
C61.034,158.344,56.101,155.496,51.298,156.782z M297.52,281.322c-4.971,0-9,4.029-9,9v8.565c0,4.971,4.029,9,9,9s9-4.029,9-9
v-8.565C306.52,285.352,302.491,281.322,297.52,281.322z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -1,560 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>POKER ADMIN</title>
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Cinzel:wght@400;600&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0e0e1a;
--panel: #13132a;
--border: #2a2a4a;
--gold: #ffd700;
--gold-dim: #a08800;
--text: #c8c8e0;
--muted: #555570;
--red: #ff4444;
--green: #44cc88;
--mono: 'Share Tech Mono', monospace;
--serif: 'Cinzel', serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--mono);
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(var(--border) 1px, transparent 1px),
linear-gradient(90deg, var(--border) 1px, transparent 1px);
background-size: 40px 40px;
opacity: 0.25;
pointer-events: none;
}
header {
position: sticky;
top: 0;
z-index: 10;
background: var(--bg);
border-bottom: 1px solid var(--border);
padding: 12px 16px 10px;
display: flex;
align-items: center;
gap: 12px;
}
.app-title {
font-family: var(--serif);
font-size: 13px;
letter-spacing: 4px;
color: var(--gold);
flex: 1;
}
.user-badge {
font-size: 9px;
color: var(--muted);
letter-spacing: 2px;
text-transform: uppercase;
}
#status-bar {
font-size: 9px;
color: var(--muted);
text-align: center;
padding: 4px 16px;
border-bottom: 1px solid var(--border);
letter-spacing: 1px;
min-height: 20px;
transition: color 0.3s;
}
#status-bar.error { color: var(--red); }
#status-bar.ok { color: var(--green); }
/* ── tabs ── */
.tabs {
display: flex;
border-bottom: 1px solid var(--border);
}
.tab {
flex: 1;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
color: var(--muted);
font-family: var(--mono);
font-size: 9px;
letter-spacing: 2px;
text-transform: uppercase;
padding: 10px;
cursor: pointer;
transition: color 0.15s, border-color 0.15s;
}
.tab:hover { color: var(--text); }
.tab.active {
color: var(--gold);
border-bottom-color: var(--gold);
}
/* ── panels ── */
.panel {
display: none;
flex-direction: column;
flex: 1;
padding: 16px;
gap: 12px;
}
.panel.active { display: flex; }
/* ── form elements ── */
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.field label {
font-size: 9px;
color: var(--muted);
letter-spacing: 2px;
text-transform: uppercase;
}
.field input {
background: var(--panel);
border: 1px solid var(--border);
color: var(--text);
font-family: var(--mono);
font-size: 12px;
padding: 8px 10px;
outline: none;
transition: border-color 0.15s;
}
.field input:focus { border-color: var(--gold-dim); }
.field-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.btn {
background: transparent;
border: 1px solid var(--gold-dim);
color: var(--gold);
font-family: var(--mono);
font-size: 10px;
letter-spacing: 2px;
text-transform: uppercase;
padding: 10px;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
width: 100%;
}
.btn:hover { background: rgba(255,215,0,0.08); border-color: var(--gold); }
.btn.danger {
border-color: #662222;
color: var(--red);
}
.btn.danger:hover { background: rgba(255,68,68,0.08); border-color: var(--red); }
.btn:disabled { opacity: 0.4; cursor: default; }
/* ── table list ── */
.table-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.table-card {
background: var(--panel);
border: 1px solid var(--border);
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.table-card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.table-name {
font-family: var(--serif);
font-size: 11px;
color: #e8e8ff;
letter-spacing: 1px;
}
.table-id {
font-size: 8px;
color: var(--muted);
letter-spacing: 1px;
}
.table-meta {
font-size: 9px;
color: var(--muted);
letter-spacing: 1px;
}
.table-meta span { color: var(--gold-dim); }
.table-actions {
display: flex;
gap: 6px;
margin-top: 4px;
}
.table-actions .btn {
font-size: 8px;
padding: 6px 8px;
}
.section-header {
font-family: var(--serif);
font-size: 9px;
letter-spacing: 3px;
color: var(--muted);
text-transform: uppercase;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
.divider {
height: 1px;
background: var(--border);
}
#no-tables {
font-size: 10px;
color: var(--muted);
text-align: center;
padding: 24px;
letter-spacing: 1px;
}
</style>
</head>
<body>
<header>
<div class="app-title">POKER ADMIN</div>
<div class="user-badge" id="user-badge"></div>
</header>
<div id="status-bar">Initialising...</div>
<div class="tabs">
<button class="tab active" onclick="switchTab('create')">Create</button>
<button class="tab" onclick="switchTab('manage')">Manage</button>
</div>
<!-- ── Create Tab ── -->
<div class="panel active" id="tab-create">
<div class="section-header">New Table</div>
<div class="field">
<label>Table ID (slug)</label>
<input type="text" id="f-id" placeholder="main" value="main">
</div>
<div class="field">
<label>Display Name</label>
<input type="text" id="f-name" placeholder="Main Table">
</div>
<div class="field-row">
<div class="field">
<label>Seat Count</label>
<input type="number" id="f-seats" value="7" min="2" max="9">
</div>
<div class="field">
<label>Turn Timer (s)</label>
<input type="number" id="f-timer" value="30" min="10" max="120">
</div>
</div>
<div class="field-row">
<div class="field">
<label>Small Blind</label>
<input type="number" id="f-sb" value="5" min="1">
</div>
<div class="field">
<label>Big Blind</label>
<input type="number" id="f-bb" value="10" min="2">
</div>
</div>
<div class="field-row">
<div class="field">
<label>Min Buy-in</label>
<input type="number" id="f-minbuy" value="100" min="1">
</div>
<div class="field">
<label>Max Buy-in</label>
<input type="number" id="f-maxbuy" value="1000" min="1">
</div>
</div>
<div class="divider"></div>
<button class="btn" id="btn-create" onclick="createTable()">Spawn Table at Avatar Position</button>
</div>
<!-- ── Manage Tab ── -->
<div class="panel" id="tab-manage">
<div class="section-header">Tables</div>
<div class="table-list" id="table-list">
<div id="no-tables">Loading...</div>
</div>
<button class="btn" onclick="loadTables()" style="margin-top:auto">Refresh</button>
</div>
<script>
var API_BASE = "https://wizards.cyou/api";
var POKER_BASE = "https://wizards.cyou/poker";
var state = {
username: null,
token: null,
position: null,
rotation: null,
};
// ── tabs ─────────────────────────────────────────────────────
function switchTab(name) {
document.querySelectorAll(".tab").forEach(function(t) { t.classList.remove("active"); });
document.querySelectorAll(".panel").forEach(function(p) { p.classList.remove("active"); });
document.querySelector("[onclick=\"switchTab('" + name + "')\"]").classList.add("active");
document.getElementById("tab-" + name).classList.add("active");
if (name === "manage") loadTables();
}
// ── status ───────────────────────────────────────────────────
function setStatus(msg, cls) {
var el = document.getElementById("status-bar");
el.textContent = msg;
el.className = cls || "";
}
// ── session ──────────────────────────────────────────────────
function ensureSession(cb) {
if (state.token) { cb(null, state.token); return; }
fetch(API_BASE + "/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: state.username }),
})
.then(function(r) {
if (!r.ok) throw new Error("Session failed (" + r.status + ")");
return r.json();
})
.then(function(d) {
state.token = d.token;
cb(null, state.token);
})
.catch(function(e) { cb(e.message); });
}
// ── create table ─────────────────────────────────────────────
function createTable() {
var id = document.getElementById("f-id").value.trim();
var name = document.getElementById("f-name").value.trim();
var seats = parseInt(document.getElementById("f-seats").value);
var timer = parseInt(document.getElementById("f-timer").value);
var sb = parseInt(document.getElementById("f-sb").value);
var bb = parseInt(document.getElementById("f-bb").value);
var minbuy = parseInt(document.getElementById("f-minbuy").value);
var maxbuy = parseInt(document.getElementById("f-maxbuy").value);
if (!id || !name) { setStatus("ID and Name are required", "error"); return; }
if (bb !== sb * 2) { setStatus("Big blind must be exactly 2x small blind", "error"); return; }
if (minbuy < bb * 10) { setStatus("Min buy-in must be at least 10x big blind (" + (bb*10) + ")", "error"); return; }
if (maxbuy < minbuy) { setStatus("Max buy-in must be >= min buy-in", "error"); return; }
var btn = document.getElementById("btn-create");
btn.disabled = true;
setStatus("Getting session...");
ensureSession(function(err) {
if (err) { setStatus(err, "error"); btn.disabled = false; return; }
// Request a fresh position snapshot from the tablet script
setStatus("Fetching avatar position...");
EventBridge.emitWebEvent(JSON.stringify({ type: "getPosition" }));
// Store config to use once position arrives
state.pendingCreate = {
id: id, name: name, seatCount: seats, turnTimer: timer,
smallBlind: sb, bigBlind: bb, minBuyin: minbuy, maxBuyin: maxbuy,
};
btn.disabled = false;
});
}
function doCreateTable(cfg, position, rotation) {
var btn = document.getElementById("btn-create");
btn.disabled = true;
setStatus("Creating table...");
fetch(POKER_BASE + "/admin/tables", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + state.token,
},
body: JSON.stringify(cfg),
})
.then(function(r) {
if (!r.ok) return r.json().then(function(e) { throw new Error(e.error || r.status); });
return r.json();
})
.then(function(d) {
setStatus("Table created! Spawning entities...", "ok");
// Tell the tablet script to spawn the in-world entities
EventBridge.emitWebEvent(JSON.stringify({
type: "spawnSeats",
pokerID: cfg.id,
seatCount: cfg.seatCount,
position: position,
rotation: rotation,
}));
})
.catch(function(e) {
setStatus(e.message, "error");
btn.disabled = false;
});
}
// ── manage tables ─────────────────────────────────────────────
function loadTables() {
setStatus("Loading tables...");
fetch(POKER_BASE + "/tables")
.then(function(r) { return r.json(); })
.then(function(d) {
renderTables(d.tables || []);
setStatus(d.tables.length + " table(s) loaded", "ok");
})
.catch(function() { setStatus("Could not load tables", "error"); });
}
function renderTables(tables) {
var list = document.getElementById("table-list");
if (tables.length === 0) {
list.innerHTML = '<div id="no-tables">No tables yet</div>';
return;
}
list.innerHTML = "";
tables.forEach(function(t) {
var cfg = t.config || t;
var card = document.createElement("div");
card.className = "table-card";
card.innerHTML =
'<div class="table-card-header">' +
'<div class="table-name">' + cfg.name + '</div>' +
'<div class="table-id">' + cfg.id + '</div>' +
'</div>' +
'<div class="table-meta">' +
'<span>' + cfg.seatCount + '</span> seats · ' +
'blinds <span>' + cfg.smallBlind + '/' + cfg.bigBlind + '</span> · ' +
'buy-in <span>' + cfg.minBuyin + '' + cfg.maxBuyin + '</span>' +
'</div>' +
'<div class="table-actions">' +
'<button class="btn danger" onclick="deleteTable(\'' + cfg.id + '\')">Delete</button>' +
'</div>';
list.appendChild(card);
});
}
function deleteTable(id) {
ensureSession(function(err) {
if (err) { setStatus(err, "error"); return; }
setStatus("Deleting " + id + "...");
// Delete server-side first
fetch(POKER_BASE + "/admin/tables/" + id, {
method: "DELETE",
headers: { "Authorization": "Bearer " + state.token },
})
.then(function(r) {
if (!r.ok) return r.json().then(function(e) { throw new Error(e.error || r.status); });
return r.json();
})
.then(function() {
// Then clean up in-world entities
EventBridge.emitWebEvent(JSON.stringify({
type: "deleteTable",
pokerID: id,
}));
})
.catch(function(e) { setStatus(e.message, "error"); });
});
}
// ── EventBridge ──────────────────────────────────────────────
function init() {
if (typeof EventBridge !== "undefined") {
EventBridge.scriptEventReceived.connect(function(data) {
try {
var msg = JSON.parse(data);
if (msg.type === "init") {
state.username = msg.username;
state.position = msg.position;
state.rotation = msg.rotation;
document.getElementById("user-badge").textContent = msg.username || "unknown";
setStatus("Ready");
if (msg.username !== "nak") {
setStatus("Admin access required", "error");
document.getElementById("btn-create").disabled = true;
}
} else if (msg.type === "position") {
state.position = msg.position;
state.rotation = msg.rotation;
if (state.pendingCreate) {
var cfg = state.pendingCreate;
state.pendingCreate = null;
doCreateTable(cfg, state.position, state.rotation);
}
} else if (msg.type === "spawnComplete") {
setStatus("Table and seats spawned!", "ok");
document.getElementById("btn-create").disabled = false;
} else if (msg.type === "deleteEntitiesComplete") {
var detail = msg.found ? "Table and entities deleted" : "Table deleted (no entities found)";
setStatus(detail, "ok");
loadTables();
}
} catch(e) {
print("poker-admin event error: " + e);
}
});
EventBridge.emitWebEvent(JSON.stringify({ type: "ready" }));
} else {
setStatus("EventBridge not available (dev mode)", "error");
}
}
window.onload = init;
</script>
</body>
</html>