EventMapper/assets/scripts/KaplayMap/gameobj.js
2024-06-17 17:57:39 -05:00

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);
});
}
}