440 lines
12 KiB
JavaScript
440 lines
12 KiB
JavaScript
export class GameObjManager {
|
|
map;
|
|
opts = {
|
|
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;
|
|
|
|
floorObject;
|
|
roomObjects = new Map([]);
|
|
|
|
constructor(map, opts = {}) {
|
|
this.map = map;
|
|
this.opts = {
|
|
...this.opts,
|
|
...opts,
|
|
};
|
|
}
|
|
|
|
__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))
|
|
);
|
|
|
|
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(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,
|
|
size: curStyle.fontSize * camScale,
|
|
font: curStyle.font,
|
|
pos: this.map.kp.vec2(-4 * camScale, -8 * camScale),
|
|
color: this.map.kp.Color.fromHex(curStyle.textColor),
|
|
opacity: curStyle.textOpacity,
|
|
anchor: "botleft",
|
|
});
|
|
});
|
|
|
|
this.map.kp.add(this.floorObject);
|
|
}
|
|
|
|
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 * 1.25;
|
|
const bBoxHeight = boundingBox.height * 1.25;
|
|
|
|
// Compare bounds
|
|
const scaledWidth = width / bBoxWidth;
|
|
const scaledHeight = height / bBoxHeight;
|
|
|
|
// 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
|
|
newPos
|
|
);
|
|
}
|
|
|
|
zoomToFloor() {
|
|
const objectBounds = this.floorObject.renderArea().bbox();
|
|
|
|
this.zoomToBounds(objectBounds);
|
|
}
|
|
|
|
zoomToRoom(id) {
|
|
const selectedObject = this.roomObjects.get(id);
|
|
if (selectedObject == null) return;
|
|
|
|
const objectBounds = selectedObject.renderArea().bbox();
|
|
|
|
this.zoomToBounds(objectBounds, selectedObject.pos);
|
|
}
|
|
|
|
generateRooms() {
|
|
if (this.roomObjects.size > 0) {
|
|
this.roomObjects.forEach((x) => x.destroy());
|
|
this.roomObjects.clear();
|
|
}
|
|
|
|
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, {
|
|
triangulate: true,
|
|
}),
|
|
this.map.kp.opacity(0),
|
|
this.map.kp.area(),
|
|
this.map.kp.pos(polygonCenter),
|
|
this.map.kp.z(room.empty ? 5 : 6),
|
|
{
|
|
clickForgiveness: 5,
|
|
startClickPosition: null,
|
|
},
|
|
]);
|
|
|
|
this.roomObjects.set(room.id, obj);
|
|
|
|
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;
|
|
} else {
|
|
obj.z = 6;
|
|
}
|
|
|
|
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,
|
|
});
|
|
}
|
|
|
|
if (!room.empty) {
|
|
let opacity = 0;
|
|
|
|
if (obj.isHovering()) {
|
|
opacity = 0.25;
|
|
}
|
|
|
|
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(curStyle.borderColor),
|
|
opacity: curStyle.borderOpacity,
|
|
width: curStyle.borderWidth * camScale,
|
|
},
|
|
});
|
|
|
|
// 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);
|
|
});
|
|
}
|
|
}
|