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
### Data Folder Structure
```
/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.
TO BE REDONE

View file

@ -1,20 +1,34 @@
export class GameObjManager {
map;
opts = {
textColor: "FFFFFF",
bgColor: "303030",
bgFocusColor: "505030",
bgHoverColor: "505050",
bgFocusHoverColor: "707050",
bgClickColor: "202020",
bgFocusClickColor: "404020",
borderColor: "000000",
borderFocusColor: "808000",
font: "monospace",
fontSize: 24,
maxFontWidth: 16,
floorOpacity: 0.75,
roomOpacity: 0.75,
styles: {
default: {
textColor: "FFFFFF",
backgroundColor: "303030",
borderColor: "000000",
actionColor: "000000",
textOpacity: 1,
backgroundOpacity: 0.75,
borderOpacity: 1,
borderWidth: 8,
font: "monospace",
fontSize: 24,
fontWidth: 16,
},
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;
@ -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() {
if (this.floorObject) this.floorObject.destroy();
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(
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();
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(() => {
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({
pts: polygon.pts,
pos: this.map.kp.vec2(0),
fill: false,
triangulate: true,
outline: {
color: this.map.kp.Color.fromHex(this.opts.borderColor),
width: 8 * camScale,
color: this.map.kp.Color.fromHex(curStyle.borderColor),
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({
text: currentFloor.name,
size: 24 * camScale,
text,
size: curStyle.fontSize * camScale,
font: curStyle.font,
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",
});
});
@ -76,14 +171,14 @@ export class GameObjManager {
this.map.kp.add(this.floorObject);
}
zoomToBounds(boundingBox) {
zoomToBounds(boundingBox, pos) {
// Get screen size
const width = this.map.kp.width();
const height = this.map.kp.height();
// Get bbox size
const bBoxWidth = boundingBox.width * 2;
const bBoxHeight = boundingBox.height * 2;
const bBoxWidth = boundingBox.width * 1.25;
const bBoxHeight = boundingBox.height * 1.25;
// Compare bounds
const scaledWidth = width / bBoxWidth;
@ -92,9 +187,12 @@ export class GameObjManager {
// Whichever one is biggest
const scale = Math.min(scaledWidth, scaledHeight);
let newPos = boundingBox.center();
if (pos) newPos = newPos.add(pos);
this.map.zoomToAbs(
Math.log2(scale), // log scale
boundingBox.center()
newPos
);
}
@ -110,7 +208,7 @@ export class GameObjManager {
const objectBounds = selectedObject.renderArea().bbox();
this.zoomToBounds(objectBounds);
this.zoomToBounds(objectBounds, selectedObject.pos);
}
generateRooms() {
@ -122,17 +220,30 @@ export class GameObjManager {
const currentRooms = this.mainmanager.getAllFocusObject("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(
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([
this.map.kp.polygon(polygon.pts),
this.map.kp.color(this.map.kp.Color.fromHex(this.opts.bgColor)),
this.map.kp.opacity(this.opts.roomOpacity),
this.map.kp.polygon(polygon.pts, {
triangulate: true,
}),
this.map.kp.opacity(0),
this.map.kp.area(),
this.map.kp.pos(),
this.map.kp.z(6),
this.map.kp.pos(polygonCenter),
this.map.kp.z(room.empty ? 5 : 6),
{
clickForgiveness: 5,
startClickPosition: null,
@ -141,8 +252,40 @@ export class GameObjManager {
this.roomObjects.set(room.id, obj);
obj.onUpdate(() => {
const roomFocused = this.mainmanager.getCurrentFocus("room") == room.id;
let polysgon;
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) {
obj.z = 7;
@ -150,78 +293,145 @@ export class GameObjManager {
obj.z = 6;
}
if (obj.isHovering()) {
if (roomFocused)
obj.color = this.map.kp.Color.fromHex(this.opts.bgFocusHoverColor);
else obj.color = this.map.kp.Color.fromHex(this.opts.bgHoverColor);
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 {
if (roomFocused)
obj.color = this.map.kp.Color.fromHex(this.opts.bgFocusColor);
else obj.color = this.map.kp.Color.fromHex(this.opts.bgColor);
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,
});
}
if (this.map.kp.isMousePressed() && obj.isHovering()) {
obj.startClickPosition = this.map.kp.mousePos();
}
if (!room.empty) {
let opacity = 0;
if (this.map.kp.isMouseDown() && obj.isHovering()) {
if (roomFocused)
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 (obj.isHovering()) {
opacity = 0.25;
}
if (roomFocused)
obj.color = this.map.kp.Color.fromHex(this.opts.bgFocusHoverColor);
this.map.clearMouseMode();
}
});
obj.onDraw(() => {
const camScale = 1 / this.map.kp.camScale().y;
const roomFocused = this.mainmanager.getCurrentFocus("room") == room.id;
if (this.map.kp.isMousePressed() && obj.isHovering()) {
obj.startClickPosition = this.map.kp.mousePos();
}
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({
pts: polygon.pts,
pos: this.map.kp.vec2(0),
fill: false,
triangulate: true,
outline: {
color: this.map.kp.Color.fromHex(
roomFocused ? this.opts.borderFocusColor : this.opts.borderColor
),
width: 8 * camScale,
color: this.map.kp.Color.fromHex(curStyle.borderColor),
opacity: curStyle.borderOpacity,
width: curStyle.borderWidth * camScale,
},
});
if (
this.opts.maxFontWidth * camScale * room.name.length >
polygon.bbox().width
)
this.map.kp.drawText({
text: room.shortName + "…",
size: this.opts.fontSize * camScale,
pos: polygon.bbox().center(),
color: this.map.kp.Color.fromHex(this.opts.textColor),
align: "center",
anchor: "center",
});
else
this.map.kp.drawText({
text: room.name,
width: polygon.bbox().width,
size: this.opts.fontSize * camScale,
pos: polygon.bbox().center(),
color: this.map.kp.Color.fromHex(this.opts.textColor),
align: "center",
anchor: "center",
});
// Easy optimization: Don't draw empty room names
if (room.name != "") {
if (
room.shortName != "" &&
curStyle.fontWidth * camScale * room.name.length > labelWidth
) {
if (
curStyle.shortNameCollapse &&
curStyle.fontWidth * camScale * room.shortName.length > labelWidth
)
return;
let text = room.shortName + (curStyle.textCut ?? "…");
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(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);

View file

@ -48,7 +48,8 @@ See https://git.gay/MeowcaTheoRange/EventMapper for more info
Thanks to:
- Joe 2DCon @ https://2dcon.gg/
- 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_header_title: "Search Events",
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,
maxFPS: 120,
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, {
gameobj: window.gameObjBranding,
gameobj: window.branding?.gameObj,
});
async function main() {
const grid = kp.loadSprite(null, "/files/images/grid.png");
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
),
});
});
if (window.branding?.init != null) window.branding.init(kp);
await eventmappermanager.load();
console.log(kp.VERSION);
}
main();

8
package-lock.json generated
View file

@ -11,7 +11,8 @@
"dependencies": {
"express": "^4.19.2",
"http-proxy-middleware": "^3.0.0",
"https": "^1.0.0"
"https": "^1.0.0",
"kaplay": "^3001.0.0-alpha.10"
},
"devDependencies": {
"nodemon": "^3.1.1"
@ -674,6 +675,11 @@
"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": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",

View file

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

View file

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