implemented tokens; alpha testing go

This commit is contained in:
Iris Lightshard 2023-07-09 00:48:19 -06:00
parent 4ba6c9315f
commit e7081a5320
Signed by: Iris Lightshard
GPG key ID: 3B7FBC22144E6398
9 changed files with 218 additions and 96 deletions

View file

@ -231,8 +231,6 @@ func (self *GameTableServer) writeToDB(tableMsg *models.TableMessage) error {
if err != nil {
return err
}
tableMsg.Token.X = nil
tableMsg.Token.Y = nil
} else if t.X != nil && t.Y != nil {
err := self.dbAdapter.MoveToken(key, t)
if err != nil {
@ -273,11 +271,6 @@ func (self *GameTableServer) writeToDB(tableMsg *models.TableMessage) error {
}
if tableMsg.Token != nil {
t := tableMsg.Token
strId := ""
if t.Id != nil {
strId = *t.Id
}
fmt.Println(strId +"::" + t.Name + "::" + t.Sprite)
if t.Id == nil {
id, err := self.dbAdapter.CreateToken(key, *t)
t.Id = &id

View file

@ -18,16 +18,16 @@ type DiceRoll struct {
}
type Token struct {
Id *string `json:"id" bson:"_id"`
Name string `json:"name"`
Sprite string `json:"sprite"`
W int `json:"w"`
H int `json:"h"`
OX int `json:"oX"`
OY int `json:"oY"`
X *int `json:"x"`
Y *int `json:"y"`
Active bool `json:"active"`
Id *string `json:"id" bson:"_id"`
Name string `json:"name"`
Sprite string `json:"sprite"`
W int `json:"w"`
H int `json:"h"`
OX int `json:"oX"`
OY int `json:"oY"`
X *float64 `json:"x"`
Y *float64 `json:"y"`
Active bool `json:"active"`
}
type Table struct {
@ -42,8 +42,8 @@ type Table struct {
type TableMessage struct {
Auth *string `json:"auth,omitempty"`
Key *TableKey `json:"key"`
DiceRoll *DiceRoll `json:"diceRoll"`
Token *Token `json:"token"`
MapImg *string `json:"mapImg"`
AuxMsg *string `json:"auxMsg"`
DiceRoll *DiceRoll `json:"diceRoll,omitempty"`
Token *Token `json:"token,omitempty"`
MapImg *string `json:"mapImg,omitempty"`
AuxMsg *string `json:"auxMsg,omitempty"`
}

View file

@ -290,24 +290,21 @@ func (self *DbEngine) GetAuxMessage(table models.TableKey) (string, error) {
}
func (self *DbEngine) CheckToken(table models.TableKey, tokenId string) (bool, bool) {
mongoId, err := primitive.ObjectIDFromHex(tokenId)
if err != nil {
return false, false
}
tables := self.db.Collection("tables")
if tables != nil {
result := models.Table{}
err := tables.FindOne(self.mkCtx(10), bson.D{
{"name", table.Name},
{"passcode", table.Passcode},
{"tokens", bson.E{"_id", mongoId}},
{"tokens._id", tokenId},
}).Decode(&result)
if err != nil {
fmt.Printf("%v", err)
return false, false
} else {
active := false
for _, t := range result.Tokens {
if *t.Id == tokenId && t.Active {
if t.Id != nil && *t.Id == tokenId && t.Active {
active = true
}
}
@ -344,10 +341,6 @@ func (self *DbEngine) CreateToken(table models.TableKey, token models.Token) (st
}
func (self *DbEngine) ActivateToken(table models.TableKey, tokenId string, active bool) error {
mongoId, err := primitive.ObjectIDFromHex(tokenId)
if err != nil {
return err
}
tables := self.db.Collection("tables")
if tables != nil {
var result models.Table
@ -356,12 +349,10 @@ func (self *DbEngine) ActivateToken(table models.TableKey, tokenId string, activ
bson.D{
{"name", table.Name},
{"passcode", table.Passcode},
{"tokens", bson.E{"_id", mongoId}},
{"tokens._id", tokenId},
},
bson.D{
{"$set", bson.D{{"tokens.$", bson.D{
{"active", active},
}}}},
{"$set", bson.D{{"tokens.$.active", active}}},
},
).Decode(&result)
return err
@ -370,10 +361,6 @@ func (self *DbEngine) ActivateToken(table models.TableKey, tokenId string, activ
}
func (self *DbEngine) MoveToken(table models.TableKey, token models.Token) error {
mongoId, err := primitive.ObjectIDFromHex(*token.Id)
if err != nil {
return err
}
tables := self.db.Collection("tables")
if tables != nil {
var result models.Table
@ -382,13 +369,10 @@ func (self *DbEngine) MoveToken(table models.TableKey, token models.Token) error
bson.D{
{"name", table.Name},
{"passcode", table.Passcode},
{"tokens", bson.E{"_id", mongoId}},
{"tokens._id", token.Id},
},
bson.D{
{"$set", bson.D{{"tokens.$", bson.D{
{"x", token.X},
{"y", token.Y},
}}}},
{"$set", bson.D{{"tokens.$.x", token.X}, {"tokens.$.y", token.Y}}},
},
).Decode(&result)
return err
@ -397,10 +381,7 @@ func (self *DbEngine) MoveToken(table models.TableKey, token models.Token) error
}
func (self *DbEngine) DestroyToken(table models.TableKey, tokenId string) error {
mongoId, err := primitive.ObjectIDFromHex(tokenId)
if err != nil {
return err
}
tables := self.db.Collection("tables")
if tables != nil {
var result models.Table
@ -411,7 +392,7 @@ func (self *DbEngine) DestroyToken(table models.TableKey, tokenId string) error
{"passcode", table.Passcode},
},
bson.D{
{"$pull", bson.D{{"tokens", bson.D{{"_id", mongoId}}}}},
{"$pull", bson.D{{"tokens", bson.D{{"_id", tokenId}}}}},
},
).Decode(&result)
return err

View file

@ -43,17 +43,17 @@ async function rebindUi(name, pass) {
document.getElementById("input_table_pass").value = pass;
dial();
const table = await res.json()
infoHtml = "<a href='#' onclick='getTables()'>&larr; table list</a><br>";
infoHtml = "<a href='#' onclick='getTables();return false;'>&larr; table list</a><br>";
infoHtml += `<textarea id='auxMsgZone'>${table.auxMsg}</textarea><br><button onclick='publishAuxMsg()'>Set Status</button>`
infoHtml += "<button onclick='destroyTable()'>Destroy Table</button><br/>";
infoHtml += "<input id='map_img_upload' type='file'/><button onclick='uploadMapImg()'>Upload Map</button><br/>"
if (mapImgs.ok) {
infoHtml += "<label>Available Maps</label>";
const imgs = await mapImgs.json();
infoHtml += "<ul>";
infoHtml += "<ul class='two_btn_list'>";
for (const i of imgs) {
const parts = i.split("/");
infoHtml += `<li>${parts[parts.length - 1]} <a href="${i}" target="_blank">view</a> <button onclick="sendMapImg('${i}');">Set</button> <button onclick="deleteImg('${i}')">Delete</button></li>\n`;
infoHtml += `<li><a href="${i}" target="_blank">${parts[parts.length - 1]}</a> <button onclick="sendMapImg('${i}');">Set</button> <button onclick="deleteImg('${i}')">Delete</button></li>\n`;
}
infoHtml += "</ul>";
} else {
@ -65,10 +65,10 @@ async function rebindUi(name, pass) {
if (tokenImgs.ok) {
tokenListHTML += "<label>Available Sprites</label>";
const tokens = await tokenImgs.json();
tokenListHTML += "<ul>";
tokenListHTML += "<ul class='single_btn_list'>";
for (const t of tokens) {
const parts = t.split("/");
tokenListHTML += `<li>${parts[parts.length - 1]} <a href="${t}" target="_blank">view</a> <button onclick="deleteImg('${t}')">Delete</button></li>\n`
tokenListHTML += `<li><a href="${t}" target="_blank">${parts[parts.length - 1]}</a> <button onclick="deleteImg('${t}')">Delete</button></li>\n`
}
tokenListHTML += "</ul>";
fillSpriteDropdown(tokens);
@ -156,6 +156,7 @@ function scaleSpritePreview(source) {
}
function drawTokenOrigin() {
if (tokenSpriteDropdown.selectedIndex >= 0) {
const img = previewZone.children[0];
const x = Number(tokenWidth.value) / Number(tokenCX.value);
const y = Number(tokenHeight.value) / Number(tokenCY.value);
@ -173,6 +174,7 @@ function drawTokenOrigin() {
} else {
previewZone.appendChild(originImg);
}
}
}
function reinitializeSpritePreview() {
@ -189,11 +191,8 @@ function reinitializeSpritePreview() {
tokenHeight.value = h;
scaleSpritePreview();
}
if (previewZone.children.length) {
previewZone.replaceChild(img, previewZone.children[0]);
} else {
previewZone.appendChild(img);
}
previewZone.innerHTML = "";
previewZone.appendChild(img);
}
function createToken() {
@ -233,6 +232,46 @@ function createToken() {
setErr("All token fields are required");
}
function destroyToken(id) {
const existing = tokens.find(t=>t.t.id == id);
if (existing) {
const self = Object.assign({}, existing.t);
self.active = false;
self.x = null;
self.y = null;
sendToken(self);
}
}
function previewExistingToken(id) {
const existing = tokens.find(t=>t.t.id == id);
if (existing) {
tokenWidth.value = existing.t.w;
tokenHeight.value = existing.t.h;
tokenCX.value = existing.t.oX;
tokenCY.value = existing.t.oY;
tokenName.value = existing.t.name;
for (let i = 0; i < tokenSpriteDropdown.options.length; i++) {
if (tokenSpriteDropdown.options[i].value == existing.t.sprite) {
tokenSpriteDropdown.selectedIndex = i;
break;
}
}
reinitializeSpritePreview();
}
}
function renderTokenMasterList() {
if (tokenZone) {
let tokenMasterListHTML = "<label>Available Tokens</label><br/><ul class='single_btn_list'>";
for (const t of tokens) {
tokenMasterListHTML += `<li><a href="#" onclick="previewExistingToken('${t.t.id}');return false">${t.t.name}</a><button onclick="destroyToken('${t.t.id}')">Destroy</button></li>\n`;
}
tokenMasterListHTML += "</ul>";
tokenZone.innerHTML = tokenMasterListHTML;
}
}
function publishAuxMsg() {
const txtArea = document.getElementById("auxMsgZone");
if (txtArea != null) {
@ -248,12 +287,6 @@ function sendToken(t) {
publish({token: t, auth: adminToken.access_token});
}
function revokeToken(t) {
t.x = null;
t.y = null;
sendToken(t);
}
async function uploadMapImg() {
try {
var input = document.getElementById("map_img_upload");
@ -391,6 +424,7 @@ async function doLogin() {
getTables();
adminWrapper.style.display="inline";
adminZone.style.display = "block";
closeErr();
} else {
setErr("Incorrect credentials");
}
@ -430,11 +464,19 @@ function setTableCreateFormVisible(v) {
}
function setTokenCreateFormVisible(v) {
if (createTokenForm) {
if (createTokenForm && tokenZone) {
if (v) {
// clear the form when displaying because we may have values from a preview of an existing token
tokenWidth.value = "";
tokenHeight.value = "";
tokenCX.value = "";
tokenCY.value = "";
tokenName.value = "";
tokenSpriteDropdown.selectedIndex = 0;
}
createTokenForm.style.display = v ? "block" : "none";
}
if (!v) {
// clear the form
tokenZone.style.display = v ? "none" : "inline";
reinitializeSpritePreview();
}
}

View file

@ -106,8 +106,8 @@
<label>cY<input type="number" id="token_cy" min="0" max="9999" onchange="previewSprite(this)"/></label><br/>
<button type="submit" onclick="createToken()">Create</button>
<button onclick="setTokenCreateFormVisible(false)">Cancel</button>
<div id="tokenPreview_zone"></div>
</form>
<div id="tokenPreview_zone"></div>
<div id="tokenZone"></div>
</details><br/>
<details id="admin_sprite_win" class="ui_win admin_win"><summary>sprites</summary>

View file

@ -19,10 +19,8 @@ function initializeMap(mapImgUrl) {
if (init) {
map.setView([0,0], 2);
}
while (tokens.some(t=>t)) {
tokens[0].m.removeFrom(map);
tokens.shift();
}
/*
*/
}
// this works but assumes the map is square (reasonable limitation I think)
@ -30,11 +28,58 @@ function resizeMarkers() {
tokens.forEach(t=>{
const icon = t.m.options.icon;
const scaleFactor = mapImg._image.clientWidth / mapImg._image.naturalWidth;
icon.options.iconSize = [scaleFactor * t.sz[0], scaleFactor * t.sz[1]];
icon.options.iconSize = [scaleFactor * t.t.w, scaleFactor * t.t.h];
icon.options.iconAnchor = [scaleFactor * t.t.oX, scaleFactor * t.t.oY];
t.m.setIcon(icon);
});
}
function processTokens(tokenChanges) {
for (const t of tokenChanges) {
const i = tokens.findIndex(tk=>tk.t.id == t.id);
if (i >= 0) {
const self = tokens[i];
// token was made active
if (t.x != null && t.y != null && !self.t.active && t.active) {
self.t.active = true;
self.m.addTo(map);
// token was made inactive
} else if (t.x != null && t.y != null && self.t.active && !t.active) {
self.t.active = false;
self.m.removeFrom(map);
// token was destroyed
} else if (t.x == null && t.y == null) {
self.m.removeFrom(map);
tokens.splice(i, 1);
// token was moved
} else {
self.t.x = t.x;
self.t.y = t.y;
self.m.setLatLng([t.y, t.x]);
}
} else {
if (t.x != null && t.y != null) {
const self = NewToken(t);
tokens.push(self);
if (t.active) {
self.m.addTo(map);
}
}
}
}
resizeMarkers();
}
function toggleActive(tokenId) {
const existing = tokens.find(t=>t.t.id == tokenId);
if (existing) {
const self = Object.assign({}, existing.t);
self.active = !self.active;
console.log(self);
publish({token: self});
}
}
function getCascadingPos() {
const topLeft = [0,0];
const n = tokens.length;
@ -43,19 +88,36 @@ function getCascadingPos() {
return topLeft;
}
function NewToken(w, h, oX, oY, img, name, x, y) {
function moveToken(id) {
const existing = tokens.find(t=>t.t.id == id);
if (existing) {
const self = Object.assign({}, existing.t);
const realPos = existing.m.getLatLng();
self.x = realPos.lng;
self.y = realPos.lat;
console.log(self);
publish({token: self});
}
}
function NewToken(token) {
const marker = L.marker([token.y,token.x], {
icon: L.icon({
iconUrl: token.sprite,
iconSize: [token.w,token.h],
iconAnchor: [token.oX, token.oY]
}),
title: token.name,
draggable: true,
autoPan: true
});
marker.on("moveend", ()=>{moveToken(token.id)});
return {
sz: [w, h],
m: L.marker((x && y) ? [y,x] : getCascadingPos(), {
icon: L.icon({
iconUrl: img,
iconSize: [w,h],
}),
title: name,
draggable: true,
autoPan: true
}),
};
t: token,
m: marker,
};
}
function addToken(token) {

View file

@ -33,9 +33,9 @@ function formatDice(r) {
return p;
}
function logDice(dice, many) {
function logDice(dice) {
const diceLog = document.getElementById("dice_log");
if (!many) {
if (!Array.isArray(dice)) {
dice = [ dice ];
} else {
if (diceLog) {
@ -58,19 +58,45 @@ function setAuxMsg(msg) {
}
}
function updateTokens(tokens) {
// update internal token array and map
processTokens(tokens);
// update token select window
renderTokenSelect();
// if admin, update token master list
renderTokenMasterList();
}
function renderTokenSelect() {
const tokenSelect = document.getElementById("token_select");
let tokenSelectHTML = "<ul class='single_btn_list'>";
for (const t of tokens) {
tokenSelectHTML += `<li><a target="_blank" href="${t.t.sprite}">${t.t.name}</a><button onclick="toggleActive('${t.t.id}')">${(t.t.active ? "Deactivate" : "Activate")}</button></li>\n`;
}
tokenSelectHTML += "</ul>";
tokenSelect.innerHTML = tokenSelectHTML;
}
function makeUpToDate(table) {
if (table) {
if (table.diceRolls) {
logDice(table.diceRolls, true);
} else if (table.diceRoll) {
logDice(table.diceRoll, false);
}
// map image has to be set before tokens can be handled!
if (table.mapImg) {
setMapImg(table.mapImg);
}
if (table.auxMsg) {
setAuxMsg(table.auxMsg);
}
if (table.diceRolls) {
logDice(table.diceRolls);
} else if (table.diceRoll) {
logDice([table.diceRoll]);
}
if (table.tokens) {
updateTokens(table.tokens);
} else if (table.token) {
updateTokens([table.token]);
}
}
}
@ -109,6 +135,10 @@ function dial() {
tabletop.style.display = "none";
}
table = null;
while (tokens.some(t=>t)) {
tokens[0].m.removeFrom(map);
tokens.shift();
}
}
});
conn.addEventListener("open", e => {
@ -126,7 +156,7 @@ function dial() {
conn.addEventListener("message", e => {
const data = JSON.parse(e.data);
if (table == null) {
// first fetch comes from mongo, so the rolls array in each diceRoll is a byte array and needs to be decoded
// dicerolls are treated as a byte array when marshalling to json, so we have to decode them
data.diceRolls.forEach(r=>{
r.roll = Uint8Array.from(atob(r.roll), c => c.charCodeAt(0))
})

View file

@ -24,6 +24,7 @@ body {
background: url('./bg.png');
background-repeat: repeat;
background-attachment: fixed;
font-family: sans-serif;
}
label {
@ -150,6 +151,7 @@ pre {
.ui_win ul {
max-height: 10em;
overflow: auto;
}
#admin_section {
@ -159,6 +161,9 @@ pre {
.admin_win {
}
.admin_win summary {
text-align: right;
}
#map {
position:fixed;
@ -182,4 +187,14 @@ nav {
#tokenPreview_zone {
position: relative;
}
.single_btn_list li {
display: grid;
grid-template-columns: 1fr auto;
}
.two_btn_list li {
display: grid;
grid-template-columns: 1fr auto auto;
}

View file

@ -20,7 +20,6 @@ function saveName() {
console.log("saving username");
const username = document.getElementById("name_entry");
if (username) {
console.log(username.value + "input found");
document.cookie = "username=" + username.value;
}
}