This commit is contained in:
MeowcaTheoRange 2024-06-17 17:57:39 -05:00
parent 1e6dfcaff4
commit 537e4e1287
8 changed files with 8845 additions and 8396 deletions

View file

@ -4,97 +4,4 @@ EventMapper, a lightweight (hopefully), easily-deployable, nearly-static digital
## Specifications ## Specifications
### Data Folder Structure TO BE REDONE
```
/events
[FLOOR].json
/rooms
[FLOOR].json
floors.json
tags.json
```
### Floor
Used to represent a floor in a building. It is a parent to both its [Rooms](#room) and [Events](#events).
This object gets represented in the app as a map poly, and in a set of buttons among other floors.
```json
{
"id": "[FLOOR ID]",
"poly": [
[x, y], // An array of vertex positions.
...
],
"lang": {
"[IETF LANGUAGE TAG]": {
"name": "[SHORT SINGLE-LINE STRING]",
"description": "[SINGLE-LINE STRING]"
}
}
}
```
This will be stored in an array in `floors.json` to be accessed by clients.
### Room
Used to represent a space in which events can happen. It is a child to a [Floor](#floor), and a parent to [Events](#event).
This object gets represented in the app as a map poly.
```json
{
"id": "[FLOOR]_[ROOM ID]",
"poly": [
[x, y], // An array of vertex positions.
...
],
"tags": ["[ARRAY", "OF", "TAG", "IDS]"],
"lang": {
"[IETF LANGUAGE TAG]": {
"name": "[SINGLE-LINE STRING]",
"shortName": "[INITIALS OF NAME]",
"description": "[SINGLE-LINE STRING]"
}
}
}
```
This will be stored in an array in `rooms/[FLOOR].json` to be accessed by clients.
### Event
Used to represent a point in time when an activity happens. It is a child to both a [Floor](#floor) and a [Room](#room), reflected in its ID.
```json
{
"id": "[FLOOR]_[ROOM]_[EVENT ID]",
"when": {
"start": "[ISO 8601 DATE]",
"end": "[ISO 8601 DATE]"
},
"tags": ["[ARRAY", "OF", "TAG", "IDS]"],
"lang": {
"[IETF LANGUAGE TAG]": {
"name": "[SINGLE-LINE STRING]",
"description": "[MULTI-LINE STRING]",
"host": "[HOST NAME (OPTIONAL)]"
}
}
}
```
This will be stored in an array in `events/[FLOOR].json` to be accessed by clients.
### Tag
Used to represent a tag that can be applied by ID to a Room or Event.
This object gets represented in the app as a searchable tag chip.
`radiogroup` can be used to un-toggle other tags when this one is selected.
`show` can be used to hide the tag from search completely.
```json
{
"id": "[FLOOR ID]",
"radiogroup": "[GROUP ID]",
"show": <boolean>,
"lang": {
"[IETF LANGUAGE TAG]": {
"name": "[SHORT SINGLE-LINE STRING]",
"shortName": "[INITIALS OF NAME]",
"description": "[SINGLE-LINE STRING]"
}
}
}
```
This will be stored in an array in `tags.json` to be accessed by clients.

View file

@ -1,20 +1,34 @@
export class GameObjManager { export class GameObjManager {
map; map;
opts = { opts = {
textColor: "FFFFFF", styles: {
bgColor: "303030", default: {
bgFocusColor: "505030", textColor: "FFFFFF",
bgHoverColor: "505050", backgroundColor: "303030",
bgFocusHoverColor: "707050", borderColor: "000000",
bgClickColor: "202020", actionColor: "000000",
bgFocusClickColor: "404020", textOpacity: 1,
borderColor: "000000", backgroundOpacity: 0.75,
borderFocusColor: "808000", borderOpacity: 1,
font: "monospace", borderWidth: 8,
fontSize: 24, font: "monospace",
maxFontWidth: 16, fontSize: 24,
floorOpacity: 0.75, fontWidth: 16,
roomOpacity: 0.75, },
defaultFocused: {
textColor: "FFFFFF",
backgroundColor: "505030",
borderColor: "808000",
actionColor: "000000",
textOpacity: 1,
backgroundOpacity: 0.75,
borderOpacity: 1,
borderWidth: 8,
font: "monospace",
fontSize: 24,
fontWidth: 16,
},
},
}; };
mainmanager; mainmanager;
@ -30,45 +44,126 @@ export class GameObjManager {
}; };
} }
__getTopStyle(styles = [], focused = false) {
const defaultFocused = {
...this.opts.styles.default,
...this.opts.styles.defaultFocused,
};
if (focused) {
return styles.reduce((lastStyle, style) => {
return {
...lastStyle,
...this.opts.styles[style],
...this.opts.styles[style + "Focused"],
};
}, defaultFocused);
} else {
return styles.reduce((lastStyle, style) => {
return {
...lastStyle,
...this.opts.styles[style],
};
}, this.opts.styles.default);
}
}
__getTopFloorStyle(styles = []) {
const defaultFloor = {
...this.opts.styles.default,
...this.opts.styles.default_floor,
};
return styles.reduce((lastStyle, style) => {
return {
...lastStyle,
...this.opts.styles[style],
};
}, defaultFloor);
}
generateFloor() { generateFloor() {
if (this.floorObject) this.floorObject.destroy(); if (this.floorObject) this.floorObject.destroy();
const currentFloor = this.mainmanager.getCurrentFocusObject("floor"); const currentFloor = this.mainmanager.getCurrentFocusObject("floor");
// Generate regular polygon for area()
if (currentFloor.polys && currentFloor.poly == null)
currentFloor.poly = currentFloor.polys.flat(1);
const polygon = new this.map.kp.Polygon( const polygon = new this.map.kp.Polygon(
currentFloor.poly.map(([x, y]) => this.map.kp.vec2(x, y)) currentFloor.poly.map(([x, y]) => this.map.kp.vec2(x, y))
); );
this.floorObject = this.map.kp.make([
this.map.kp.polygon(polygon.pts),
this.map.kp.color(this.map.kp.Color.fromHex(this.opts.bgColor)),
this.map.kp.opacity(this.opts.floorOpacity),
this.map.kp.pos(),
this.map.kp.z(5),
]);
const bounds = polygon.bbox(); const bounds = polygon.bbox();
this.map.camBounds = bounds; this.map.camBounds = bounds;
const curStyle = this.__getTopFloorStyle(currentFloor.styles);
this.floorObject = this.map.kp.make([
this.map.kp.polygon(polygon.pts, {
triangulate: true,
}),
this.map.kp.opacity(0),
this.map.kp.pos(),
this.map.kp.z(4),
]);
let polysgon;
if (currentFloor.polys)
polysgon = currentFloor.polys.map(
(poly) =>
new this.map.kp.Polygon(poly.map(([x, y]) => this.map.kp.vec2(x, y)))
);
this.floorObject.onDraw(() => { this.floorObject.onDraw(() => {
const camScale = 1 / this.map.kp.camScale().y; const camScale = 1 / this.map.kp.camScale().y;
if (polysgon) {
polysgon.forEach((poly) => {
this.map.kp.drawPolygon({
pts: poly.pts,
pos: this.map.kp.vec2(0),
triangulate: true,
color: this.map.kp.Color.fromHex(curStyle.backgroundColor),
opacity: curStyle.backgroundOpacity,
});
});
} else {
this.map.kp.drawPolygon({
pts: polygon.pts,
pos: this.map.kp.vec2(0),
triangulate: true,
color: this.map.kp.Color.fromHex(curStyle.backgroundColor),
opacity: curStyle.backgroundOpacity,
});
}
this.map.kp.drawPolygon({ this.map.kp.drawPolygon({
pts: polygon.pts, pts: polygon.pts,
pos: this.map.kp.vec2(0), pos: this.map.kp.vec2(0),
fill: false, fill: false,
triangulate: true,
outline: { outline: {
color: this.map.kp.Color.fromHex(this.opts.borderColor), color: this.map.kp.Color.fromHex(curStyle.borderColor),
width: 8 * camScale, opacity: curStyle.borderOpacity,
width: curStyle.borderWidth * camScale,
}, },
}); });
let text = currentFloor.name;
if (curStyle.textTransform == "uppercase") {
text = text.toUpperCase();
} else if (curStyle.textTransform == "lowercase") {
text = text.toLowerCase();
}
this.map.kp.drawText({ this.map.kp.drawText({
text: currentFloor.name, text,
size: 24 * camScale, size: curStyle.fontSize * camScale,
font: curStyle.font,
pos: this.map.kp.vec2(-4 * camScale, -8 * camScale), pos: this.map.kp.vec2(-4 * camScale, -8 * camScale),
color: this.map.kp.Color.fromHex(this.opts.textColor), color: this.map.kp.Color.fromHex(curStyle.textColor),
opacity: curStyle.textOpacity,
anchor: "botleft", anchor: "botleft",
}); });
}); });
@ -76,14 +171,14 @@ export class GameObjManager {
this.map.kp.add(this.floorObject); this.map.kp.add(this.floorObject);
} }
zoomToBounds(boundingBox) { zoomToBounds(boundingBox, pos) {
// Get screen size // Get screen size
const width = this.map.kp.width(); const width = this.map.kp.width();
const height = this.map.kp.height(); const height = this.map.kp.height();
// Get bbox size // Get bbox size
const bBoxWidth = boundingBox.width * 2; const bBoxWidth = boundingBox.width * 1.25;
const bBoxHeight = boundingBox.height * 2; const bBoxHeight = boundingBox.height * 1.25;
// Compare bounds // Compare bounds
const scaledWidth = width / bBoxWidth; const scaledWidth = width / bBoxWidth;
@ -92,9 +187,12 @@ export class GameObjManager {
// Whichever one is biggest // Whichever one is biggest
const scale = Math.min(scaledWidth, scaledHeight); const scale = Math.min(scaledWidth, scaledHeight);
let newPos = boundingBox.center();
if (pos) newPos = newPos.add(pos);
this.map.zoomToAbs( this.map.zoomToAbs(
Math.log2(scale), // log scale Math.log2(scale), // log scale
boundingBox.center() newPos
); );
} }
@ -110,7 +208,7 @@ export class GameObjManager {
const objectBounds = selectedObject.renderArea().bbox(); const objectBounds = selectedObject.renderArea().bbox();
this.zoomToBounds(objectBounds); this.zoomToBounds(objectBounds, selectedObject.pos);
} }
generateRooms() { generateRooms() {
@ -122,17 +220,30 @@ export class GameObjManager {
const currentRooms = this.mainmanager.getAllFocusObject("room"); const currentRooms = this.mainmanager.getAllFocusObject("room");
currentRooms.forEach((room) => { currentRooms.forEach((room) => {
// Generate regular polygon for area()
if (room.polys && room.poly == null) room.poly = room.polys.flat(1);
const polygon = new this.map.kp.Polygon( const polygon = new this.map.kp.Polygon(
room.poly.map(([x, y]) => this.map.kp.vec2(x, y)) room.poly.map(([x, y]) => this.map.kp.vec2(x, y))
); );
const polygonBbox = polygon.bbox();
const polygonCenter = polygonBbox.center();
polygon.pts = polygon.pts.map((point) => point.sub(polygonCenter));
let roomFocused = this.mainmanager.getCurrentFocus("room") == room.id;
let curStyle = this.__getTopStyle(room.styles, roomFocused);
const obj = this.map.kp.make([ const obj = this.map.kp.make([
this.map.kp.polygon(polygon.pts), this.map.kp.polygon(polygon.pts, {
this.map.kp.color(this.map.kp.Color.fromHex(this.opts.bgColor)), triangulate: true,
this.map.kp.opacity(this.opts.roomOpacity), }),
this.map.kp.opacity(0),
this.map.kp.area(), this.map.kp.area(),
this.map.kp.pos(), this.map.kp.pos(polygonCenter),
this.map.kp.z(6), this.map.kp.z(room.empty ? 5 : 6),
{ {
clickForgiveness: 5, clickForgiveness: 5,
startClickPosition: null, startClickPosition: null,
@ -141,8 +252,40 @@ export class GameObjManager {
this.roomObjects.set(room.id, obj); this.roomObjects.set(room.id, obj);
obj.onUpdate(() => { let polysgon;
const roomFocused = this.mainmanager.getCurrentFocus("room") == room.id; if (room.polys)
polysgon = room.polys.map(
(poly) =>
new this.map.kp.Polygon(
poly.map(([x, y]) => this.map.kp.vec2(x, y).sub(polygonCenter))
)
);
let labelPos = this.map.kp.vec2(0);
let labelWidth = polygonBbox.width;
if (room.label?.pos)
labelPos = this.map.kp.vec2(room.label.pos).sub(polygonCenter);
if (room.label?.width) labelWidth = room.label.width;
obj.onDraw(() => {
// Check if room is within bounds
const camScale = 1 / this.map.kp.camScale().y;
const camBounds = this.map.kp.vec2(
this.map.kp.width() * camScale,
this.map.kp.height() * camScale
);
const camPos = this.map.kp.camPos().sub(camBounds.scale(0.5));
const rect = new this.map.kp.Rect(camPos, camBounds.x, camBounds.y);
if (!rect.collides(polygonBbox)) return;
const curRoomFocused =
this.mainmanager.getCurrentFocus("room") == room.id;
if (curRoomFocused != roomFocused) {
curStyle = this.__getTopStyle(room.styles, curRoomFocused);
roomFocused = curRoomFocused;
}
if (roomFocused) { if (roomFocused) {
obj.z = 7; obj.z = 7;
@ -150,78 +293,145 @@ export class GameObjManager {
obj.z = 6; obj.z = 6;
} }
if (obj.isHovering()) { if (polysgon) {
if (roomFocused) polysgon.forEach((poly) => {
obj.color = this.map.kp.Color.fromHex(this.opts.bgFocusHoverColor); this.map.kp.drawPolygon({
else obj.color = this.map.kp.Color.fromHex(this.opts.bgHoverColor); pts: poly.pts,
pos: this.map.kp.vec2(0),
triangulate: true,
color: this.map.kp.Color.fromHex(curStyle.backgroundColor),
opacity: curStyle.backgroundOpacity,
});
});
} else { } else {
if (roomFocused) this.map.kp.drawPolygon({
obj.color = this.map.kp.Color.fromHex(this.opts.bgFocusColor); pts: polygon.pts,
else obj.color = this.map.kp.Color.fromHex(this.opts.bgColor); pos: this.map.kp.vec2(0),
triangulate: true,
color: this.map.kp.Color.fromHex(curStyle.backgroundColor),
opacity: curStyle.backgroundOpacity,
});
} }
if (this.map.kp.isMousePressed() && obj.isHovering()) { if (!room.empty) {
obj.startClickPosition = this.map.kp.mousePos(); let opacity = 0;
}
if (this.map.kp.isMouseDown() && obj.isHovering()) { if (obj.isHovering()) {
if (roomFocused) opacity = 0.25;
obj.color = this.map.kp.Color.fromHex(this.opts.bgFocusClickColor);
else obj.color = this.map.kp.Color.fromHex(this.opts.bgClickColor);
}
if (this.map.kp.isMouseReleased() && obj.isHovering()) {
const endClickPosition = this.map.kp.mousePos();
if (
obj.startClickPosition &&
obj.startClickPosition.dist(endClickPosition) < obj.clickForgiveness
) {
window.location.hash = room.id;
} }
if (roomFocused)
obj.color = this.map.kp.Color.fromHex(this.opts.bgFocusHoverColor);
this.map.clearMouseMode();
}
});
obj.onDraw(() => { if (this.map.kp.isMousePressed() && obj.isHovering()) {
const camScale = 1 / this.map.kp.camScale().y; obj.startClickPosition = this.map.kp.mousePos();
const roomFocused = this.mainmanager.getCurrentFocus("room") == room.id; }
if (this.map.kp.isMouseDown() && obj.isHovering()) {
opacity = 0.5;
}
if (this.map.kp.isMouseReleased() && obj.isHovering()) {
const endClickPosition = this.map.kp.mousePos();
if (
obj.startClickPosition &&
obj.startClickPosition.dist(endClickPosition) <
obj.clickForgiveness
) {
window.location.hash = room.id;
}
this.map.clearMouseMode();
}
if (polysgon) {
polysgon.forEach((poly) => {
this.map.kp.drawPolygon({
pts: poly.pts,
pos: this.map.kp.vec2(0),
triangulate: true,
color: this.map.kp.Color.fromHex(curStyle.actionColor),
opacity,
});
});
} else {
this.map.kp.drawPolygon({
pts: polygon.pts,
pos: this.map.kp.vec2(0),
triangulate: true,
color: this.map.kp.Color.fromHex(curStyle.actionColor),
opacity,
});
}
}
this.map.kp.drawPolygon({ this.map.kp.drawPolygon({
pts: polygon.pts, pts: polygon.pts,
pos: this.map.kp.vec2(0), pos: this.map.kp.vec2(0),
fill: false, fill: false,
triangulate: true,
outline: { outline: {
color: this.map.kp.Color.fromHex( color: this.map.kp.Color.fromHex(curStyle.borderColor),
roomFocused ? this.opts.borderFocusColor : this.opts.borderColor opacity: curStyle.borderOpacity,
), width: curStyle.borderWidth * camScale,
width: 8 * camScale,
}, },
}); });
if ( // Easy optimization: Don't draw empty room names
this.opts.maxFontWidth * camScale * room.name.length > if (room.name != "") {
polygon.bbox().width if (
) room.shortName != "" &&
this.map.kp.drawText({ curStyle.fontWidth * camScale * room.name.length > labelWidth
text: room.shortName + "…", ) {
size: this.opts.fontSize * camScale, if (
pos: polygon.bbox().center(), curStyle.shortNameCollapse &&
color: this.map.kp.Color.fromHex(this.opts.textColor), curStyle.fontWidth * camScale * room.shortName.length > labelWidth
align: "center", )
anchor: "center", return;
});
else let text = room.shortName + (curStyle.textCut ?? "…");
this.map.kp.drawText({ if (curStyle.textTransform == "uppercase") {
text: room.name, text = text.toUpperCase();
width: polygon.bbox().width, } else if (curStyle.textTransform == "lowercase") {
size: this.opts.fontSize * camScale, text = text.toLowerCase();
pos: polygon.bbox().center(), }
color: this.map.kp.Color.fromHex(this.opts.textColor),
align: "center", this.map.kp.drawText({
anchor: "center", text,
}); size: curStyle.shortNameMinScale
? Math.min(curStyle.fontSize, curStyle.fontSize * camScale)
: curStyle.fontSize * camScale,
font: curStyle.font,
pos: labelPos,
color: this.map.kp.Color.fromHex(curStyle.textColor),
opacity: curStyle.textOpacity,
align: "center",
anchor: "center",
});
} else {
if (
curStyle.shortNameCollapse &&
curStyle.fontWidth * camScale * room.name.length > labelWidth
)
return;
let text = room.name;
if (curStyle.textTransform == "uppercase") {
text = text.toUpperCase();
} else if (curStyle.textTransform == "lowercase") {
text = text.toLowerCase();
}
this.map.kp.drawText({
text,
size: curStyle.shortNameMinScale
? Math.min(labelWidth, curStyle.fontSize * camScale)
: curStyle.fontSize * camScale,
font: curStyle.font,
pos: labelPos,
color: this.map.kp.Color.fromHex(curStyle.textColor),
opacity: curStyle.textOpacity,
align: "center",
anchor: "center",
});
}
}
}); });
this.floorObject.add(obj); this.floorObject.add(obj);

View file

@ -48,7 +48,8 @@ See https://git.gay/MeowcaTheoRange/EventMapper for more info
Thanks to: Thanks to:
- Joe 2DCon @ https://2dcon.gg/ - Joe 2DCon @ https://2dcon.gg/
- KikiCraft @ https://kikicraft.com/ - KikiCraft @ https://kikicraft.com/
- Captain Zach @ https://discord.gg/2dcon`, - Captain Zach @ https://discord.gg/2dcon
- Jason @ https://discord.gg/2dcon`,
search_dialog_close_button: "Close search", search_dialog_close_button: "Close search",
search_dialog_header_title: "Search Events", search_dialog_header_title: "Search Events",
search_dialog_search_bar: "Type search query here...", search_dialog_search_bar: "Type search query here...",

File diff suppressed because one or more lines are too long

View file

@ -15,33 +15,24 @@ const kp = kaplay({
global: false, global: false,
maxFPS: 120, maxFPS: 120,
texFilter: "nearest", texFilter: "nearest",
background: window.backgroundBranding ?? "404040", background: window.branding?.background ?? "404040",
}); });
const kaplaymap = new KaplayMap(kp, {}); const kaplaymap = new KaplayMap(kp, {
minZoomLevel: 0,
maxZoomLevel: 6,
...window.branding?.kaplayMap,
});
const eventmappermanager = new EventMapperManager(kaplaymap, mapUi, { const eventmappermanager = new EventMapperManager(kaplaymap, mapUi, {
gameobj: window.gameObjBranding, gameobj: window.branding?.gameObj,
}); });
async function main() { async function main() {
const grid = kp.loadSprite(null, "/files/images/grid.png"); if (window.branding?.init != null) window.branding.init(kp);
kp.onDraw(() => {
kp.drawSprite({
sprite: grid,
tiled: true,
opacity: 0.25,
width: kp.width() + 200,
height: kp.height() + 200,
anchor: "center",
pos: kp.vec2(
Math.floor(kp.camPos().x / 100) * 100 + 0.5,
Math.floor(kp.camPos().y / 100) * 100 + 0.5
),
});
});
await eventmappermanager.load(); await eventmappermanager.load();
console.log(kp.VERSION);
} }
main(); main();

8
package-lock.json generated
View file

@ -11,7 +11,8 @@
"dependencies": { "dependencies": {
"express": "^4.19.2", "express": "^4.19.2",
"http-proxy-middleware": "^3.0.0", "http-proxy-middleware": "^3.0.0",
"https": "^1.0.0" "https": "^1.0.0",
"kaplay": "^3001.0.0-alpha.10"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.1.1" "nodemon": "^3.1.1"
@ -674,6 +675,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/kaplay": {
"version": "3001.0.0-alpha.10",
"resolved": "https://registry.npmjs.org/kaplay/-/kaplay-3001.0.0-alpha.10.tgz",
"integrity": "sha512-Nsl0GvOszQdIZDnUSWaB46Iehk24CczXVAD3aiaYWMeAUHZNx/DDbnliNC/sosQB0F9m/+h2D+qhxkNTJ+5ITw=="
},
"node_modules/media-typer": { "node_modules/media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",

View file

@ -13,7 +13,8 @@
"dependencies": { "dependencies": {
"express": "^4.19.2", "express": "^4.19.2",
"http-proxy-middleware": "^3.0.0", "http-proxy-middleware": "^3.0.0",
"https": "^1.0.0" "https": "^1.0.0",
"kaplay": "^3001.0.0-alpha.10"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.1.1" "nodemon": "^3.1.1"

View file

@ -8,6 +8,9 @@ app.use("/assets", express.static('assets'));
app.use("/", express.static('pages')); app.use("/", express.static('pages'));
app.use("/dataServer", express.static('data'));
app.use("/fileServer", express.static('files'));
app.get('/files/*', async (req, res) => { app.get('/files/*', async (req, res) => {
// Get events // Get events
let eventsReq = await fetch(new URL(req.params[0], config.files_url)); let eventsReq = await fetch(new URL(req.params[0], config.files_url));