feat(pathfinder): add per-room underpass setting via room settings UI

Add allow_underpass as a per-room boolean setting that controls whether
avatars can walk under elevated furniture. When disabled (default), the
room behaves normally with blocking items. When enabled, items elevated
above the UNDERPASS_HEIGHT threshold allow walking underneath.

Changes:
- Room: add allowUnderpass field with DB load/save
- RoomTileManager: make all 3 underpass checks conditional on room setting
- RoomItemManager: getWalkableItemAt() falls back when underpass disabled
- RoomSettingsComposer/SaveEvent: send/receive the flag in room settings packet
- SQL migration: add allow_underpass column to rooms table

Co-Authored-By: medievalshell <medievalshell@users.noreply.github.com>
This commit is contained in:
simoleo89
2026-03-17 13:38:43 +01:00
parent e75fe83c80
commit b0f3f1488d
6 changed files with 28 additions and 5 deletions
@@ -0,0 +1 @@
ALTER TABLE `rooms` ADD COLUMN `allow_underpass` ENUM('0','1') NOT NULL DEFAULT '0' AFTER `move_diagonally`;
@@ -156,6 +156,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
private volatile boolean promoted;
private volatile int tradeMode;
private volatile boolean moveDiagonally;
private volatile boolean allowUnderpass;
private volatile boolean jukeboxActive;
private volatile boolean hideWired;
private RoomPromotion promotion;
@@ -239,6 +240,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
this.tradeMode = set.getInt("trade_mode");
this.moveDiagonally = set.getString("move_diagonally").equals("1");
this.allowUnderpass = set.getString("allow_underpass").equals("1");
this.preLoaded = true;
this.allowBotsWalk = true;
@@ -1077,7 +1079,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
if (this.needsUpdate) {
try (Connection connection = Emulator.getDatabase().getDataSource()
.getConnection(); PreparedStatement statement = connection.prepareStatement(
"UPDATE rooms SET name = ?, description = ?, password = ?, state = ?, users_max = ?, category = ?, score = ?, paper_floor = ?, paper_wall = ?, paper_landscape = ?, thickness_wall = ?, wall_height = ?, thickness_floor = ?, moodlight_data = ?, tags = ?, allow_other_pets = ?, allow_other_pets_eat = ?, allow_walkthrough = ?, allow_hidewall = ?, chat_mode = ?, chat_weight = ?, chat_speed = ?, chat_hearing_distance = ?, chat_protection =?, who_can_mute = ?, who_can_kick = ?, who_can_ban = ?, poll_id = ?, guild_id = ?, roller_speed = ?, override_model = ?, is_staff_picked = ?, promoted = ?, trade_mode = ?, move_diagonally = ?, owner_id = ?, owner_name = ?, jukebox_active = ?, hidewired = ? WHERE id = ?")) {
"UPDATE rooms SET name = ?, description = ?, password = ?, state = ?, users_max = ?, category = ?, score = ?, paper_floor = ?, paper_wall = ?, paper_landscape = ?, thickness_wall = ?, wall_height = ?, thickness_floor = ?, moodlight_data = ?, tags = ?, allow_other_pets = ?, allow_other_pets_eat = ?, allow_walkthrough = ?, allow_hidewall = ?, chat_mode = ?, chat_weight = ?, chat_speed = ?, chat_hearing_distance = ?, chat_protection =?, who_can_mute = ?, who_can_kick = ?, who_can_ban = ?, poll_id = ?, guild_id = ?, roller_speed = ?, override_model = ?, is_staff_picked = ?, promoted = ?, trade_mode = ?, move_diagonally = ?, owner_id = ?, owner_name = ?, jukebox_active = ?, hidewired = ?, allow_underpass = ? WHERE id = ?")) {
statement.setString(1, this.name);
statement.setString(2, this.description);
statement.setString(3, this.password);
@@ -1126,7 +1128,8 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
statement.setString(37, this.ownerName);
statement.setString(38, this.jukeboxActive ? "1" : "0");
statement.setString(39, this.hideWired ? "1" : "0");
statement.setInt(40, this.id);
statement.setString(40, this.allowUnderpass ? "1" : "0");
statement.setInt(41, this.id);
statement.executeUpdate();
this.needsUpdate = false;
} catch (SQLException e) {
@@ -1408,6 +1411,14 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
this.allowWalkthrough = allowWalkthrough;
}
public boolean isAllowUnderpass() {
return this.allowUnderpass;
}
public void setAllowUnderpass(boolean allowUnderpass) {
this.allowUnderpass = allowUnderpass;
}
public boolean isAllowBotsWalk() {
return this.allowBotsWalk;
}
@@ -458,6 +458,11 @@ public class RoomItemManager {
return null;
}
// If underpass is disabled for this room, just return the top item
if (!this.room.isAllowUnderpass()) {
return topItem;
}
// If the top item is walkable, just return it
if (topItem.isWalkable() || topItem.getBaseItem().allowWalk() || topItem.getBaseItem().allowSit() || topItem.getBaseItem().allowLay()) {
return topItem;
@@ -91,7 +91,7 @@ public class RoomTileManager {
tallestItem = item;
}
if (result == RoomTileState.BLOCKED && tallestItem != null) {
if (this.room.isAllowUnderpass() && result == RoomTileState.BLOCKED && tallestItem != null) {
double walkSurface = this.getUnderpassWalkHeight(tile, items, exclude);
if (tallestItem.getZ() - walkSurface >= RoomLayout.UNDERPASS_HEIGHT) {
result = RoomTileState.OPEN;
@@ -228,7 +228,7 @@ public class RoomTileManager {
double itemTop = item.getZ() + (item.getBaseItem().allowSit() ? 0 : Item.getCurrentHeight(item));
// Underpass: if the top item is blocking but high enough to walk under, use floor height
if (!item.isWalkable() && !item.getBaseItem().allowWalk() && !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay()) {
if (this.room.isAllowUnderpass() && !item.isWalkable() && !item.getBaseItem().allowWalk() && !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay()) {
RoomLayout layout2 = this.room.getLayout();
RoomTile tile = layout2 != null ? layout2.getTile(x, y) : null;
THashSet<HabboItem> allItems = tile != null ? this.room.getItemManager().getItemsAt(tile) : null;
@@ -444,7 +444,7 @@ public class RoomTileManager {
}
// Underpass: if top item blocks but is high enough, allow walking under
if (!canWalk && topItem != null) {
if (this.room.isAllowUnderpass() && !canWalk && topItem != null) {
double walkSurface = this.getUnderpassWalkHeight(roomTile, items, null);
if (topItem.getZ() - walkSurface >= RoomLayout.UNDERPASS_HEIGHT) {
canWalk = true;
@@ -129,6 +129,11 @@ public class RoomSettingsSaveEvent extends MessageHandler {
room.setChatSpeed(this.packet.readInt());
room.setChatDistance(Math.abs(this.packet.readInt()));
room.setChatProtection(this.packet.readInt());
if (this.packet.bytesAvailable() > 0) {
room.setAllowUnderpass(this.packet.readBoolean());
}
room.setNeedsUpdate(true);
room.sendComposer(new RoomThicknessComposer(room).compose());
@@ -52,6 +52,7 @@ public class RoomSettingsComposer extends MessageComposer {
this.response.appendInt(this.room.getMuteOption());
this.response.appendInt(this.room.getKickOption());
this.response.appendInt(this.room.getBanOption());
this.response.appendInt(this.room.isAllowUnderpass() ? 1 : 0);
return this.response;
}