You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
@@ -1,78 +1,89 @@
|
|||||||
-- Soundboard
|
ALTER TABLE `rooms`
|
||||||
-- The room flag column + sounds table are also created at boot by
|
ADD COLUMN IF NOT EXISTS `soundboard_enabled` TINYINT(1) NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
ALTER TABLE `rooms` ADD COLUMN IF NOT EXISTS `soundboard_enabled` TINYINT(1) NOT NULL DEFAULT 0;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `soundboard_sounds` (
|
CREATE TABLE IF NOT EXISTS `soundboard_sounds` (
|
||||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
`name` VARCHAR(64) NOT NULL DEFAULT '', -- pad label shown in the client
|
`name` VARCHAR(64) NOT NULL DEFAULT '', -- pad label shown in the client
|
||||||
`url` VARCHAR(255) NOT NULL DEFAULT '', -- audio url (uploaded via CMS, like custom badges)
|
`url` VARCHAR(255) NOT NULL DEFAULT '', -- audio url (uploaded via CMS, like custom badges)
|
||||||
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
-- Fortune Wheel
|
-- Fortune Wheel — tables
|
||||||
-- Tables are also created at boot by WheelManager (CREATE TABLE IF NOT EXISTS),
|
-- ----------------------------------------------------------------------------
|
||||||
-- so applying this file is only needed to seed prizes + settings.
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `wheel_prizes` (
|
CREATE TABLE IF NOT EXISTS `wheel_prizes` (
|
||||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
`type` VARCHAR(16) NOT NULL DEFAULT 'nothing', -- item | badge | credits | points | spin | nothing
|
`type` VARCHAR(16) NOT NULL DEFAULT 'nothing', -- item | badge | credits | points | spin | nothing
|
||||||
`value` VARCHAR(64) NOT NULL DEFAULT '', -- item: base item id ; badge: badge code ; others: unused
|
`value` VARCHAR(64) NOT NULL DEFAULT '', -- item: base item id ; badge: badge code ; others: unused
|
||||||
`amount` INT(11) NOT NULL DEFAULT 1, -- item qty / credits / points / extra spins
|
`amount` INT(11) NOT NULL DEFAULT 1, -- item qty / credits / points / extra spins
|
||||||
`points_type` INT(11) NOT NULL DEFAULT 5, -- for type=points (diamond default 5)
|
`points_type` INT(11) NOT NULL DEFAULT 5, -- for type=points (diamond default 5)
|
||||||
`weight` INT(11) NOT NULL DEFAULT 1, -- relative probability
|
`weight` INT(11) NOT NULL DEFAULT 1, -- relative probability
|
||||||
`label` VARCHAR(64) NOT NULL DEFAULT '', -- slice label override (optional)
|
`label` VARCHAR(64) NOT NULL DEFAULT '', -- slice label override (optional)
|
||||||
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `wheel_user_state` (
|
CREATE TABLE IF NOT EXISTS `wheel_user_state` (
|
||||||
`user_id` INT(11) NOT NULL,
|
`user_id` INT(11) NOT NULL,
|
||||||
`free_spins` INT(11) NOT NULL DEFAULT 0, -- remaining free spins for the current day
|
`free_spins` INT(11) NOT NULL DEFAULT 0, -- remaining free spins for the current day
|
||||||
`extra_spins` INT(11) NOT NULL DEFAULT 0, -- bought / won spins
|
`extra_spins` INT(11) NOT NULL DEFAULT 0, -- bought / won spins
|
||||||
`last_reset` INT(11) NOT NULL DEFAULT 0, -- day index of last daily reset (unix / 86400)
|
`last_reset` INT(11) NOT NULL DEFAULT 0, -- day index of last daily reset (unix / 86400)
|
||||||
PRIMARY KEY (`user_id`)
|
PRIMARY KEY (`user_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `wheel_recent_wins` (
|
CREATE TABLE IF NOT EXISTS `wheel_recent_wins` (
|
||||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` INT(11) NOT NULL,
|
`user_id` INT(11) NOT NULL,
|
||||||
`username` VARCHAR(64) NOT NULL DEFAULT '',
|
`username` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
`look` VARCHAR(255) NOT NULL DEFAULT '',
|
`look` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
`prize_label` VARCHAR(64) NOT NULL DEFAULT '',
|
`prize_label` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
`won_at` INT(11) NOT NULL DEFAULT 0,
|
`won_at` INT(11) NOT NULL DEFAULT 0,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`)
|
||||||
KEY `idx_wheel_recent_wins_id` (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
|
INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
|
||||||
('wheel.free_spins_per_day', '1', 'Fortune wheel: free spins granted each day.')
|
('wheel.free_spins_per_day', '1', 'Fortune wheel: free spins granted each day.'),
|
||||||
ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`);
|
('wheel.spin_cost', '50', 'Fortune wheel: cost of one extra spin.'),
|
||||||
INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
|
('wheel.spin_cost_type', '5', 'Fortune wheel: currency type for the spin cost (5 = diamonds; -1 = credits).')
|
||||||
('wheel.spin_cost', '50', 'Fortune wheel: cost of one extra spin.')
|
|
||||||
ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`);
|
|
||||||
INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
|
|
||||||
('wheel.spin_cost_type', '5', 'Fortune wheel: currency type for the spin cost (5 = diamonds; -1 = credits).')
|
|
||||||
ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`);
|
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO `wheel_prizes` (`type`, `amount`, `points_type`, `weight`, `label`, `sort_order`) VALUES
|
|
||||||
('points',25, 5, 20, '25 diamonds',1),
|
|
||||||
('points',50, 5, 12, '50 diamonds',2),
|
|
||||||
('points',200, 5, 3, '200 diamonds',3),
|
|
||||||
('credits',100, 0, 15, '100 credits',4),
|
|
||||||
('spin',1, 0, 15, '1 Extra spin', 5),
|
|
||||||
('spin',2, 0, 6, '2 Extra spins',6),
|
|
||||||
('nothing',0, 0, 29, 'Oh to bad!',7);
|
|
||||||
|
|
||||||
INSERT INTO `permission_definitions`
|
|
||||||
(`permission_key`, `max_value`, `comment`,
|
|
||||||
`rank_1`, `rank_2`, `rank_3`, `rank_4`, `rank_5`, `rank_6`, `rank_7`)
|
|
||||||
VALUES
|
|
||||||
('acc_wheeladmin', 1, 'Required to open the Fortune Wheel settings popup and edit prize rows.',
|
|
||||||
0, 0, 0, 0, 0, 0, 1)
|
|
||||||
ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`);
|
ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`);
|
||||||
|
|
||||||
|
INSERT INTO `wheel_prizes` (`type`, `amount`, `points_type`, `weight`, `label`, `sort_order`)
|
||||||
|
SELECT `type`, `amount`, `points_type`, `weight`, `label`, `sort_order`
|
||||||
|
FROM (
|
||||||
|
SELECT 'points' AS `type`, 25 AS `amount`, 5 AS `points_type`, 20 AS `weight`, '25 diamonds' AS `label`, 1 AS `sort_order`
|
||||||
|
UNION ALL SELECT 'points', 50, 5, 12, '50 diamonds', 2
|
||||||
|
UNION ALL SELECT 'points', 200, 5, 3, '200 diamonds', 3
|
||||||
|
UNION ALL SELECT 'credits', 100, 0, 15, '100 credits', 4
|
||||||
|
UNION ALL SELECT 'spin', 1, 0, 15, '1 Extra spin', 5
|
||||||
|
UNION ALL SELECT 'spin', 2, 0, 6, '2 Extra spins', 6
|
||||||
|
UNION ALL SELECT 'nothing', 0, 0, 29, 'Oh to bad!', 7
|
||||||
|
) AS seed
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM `wheel_prizes`);
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `permission_definitions` (`permission_key`, `max_value`, `comment`)
|
||||||
|
VALUES (
|
||||||
|
'acc_wheeladmin',
|
||||||
|
1,
|
||||||
|
'Allows opening the Fortune Wheel prize editor (FortuneWheelSettingsView) to add/edit prize slices. Gated server-side by the same key.'
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @cols := NULL;
|
||||||
|
SELECT GROUP_CONCAT(CONCAT('dst.`', `column_name`, '` = src.`', `column_name`, '`') SEPARATOR ', ')
|
||||||
|
INTO @cols
|
||||||
|
FROM `information_schema`.`columns`
|
||||||
|
WHERE `table_schema` = DATABASE()
|
||||||
|
AND `table_name` = 'permission_definitions'
|
||||||
|
AND `column_name` REGEXP '^rank_[0-9]+$';
|
||||||
|
|
||||||
|
SET @sql := CONCAT(
|
||||||
|
'UPDATE `permission_definitions` dst ',
|
||||||
|
'JOIN `permission_definitions` src ON src.`permission_key` = ''acc_ads_background'' ',
|
||||||
|
'SET ', @cols, ' ',
|
||||||
|
'WHERE dst.`permission_key` = ''acc_wheeladmin'''
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
@@ -202,8 +202,8 @@ public class CatalogManager {
|
|||||||
public final Item ecotronItem;
|
public final Item ecotronItem;
|
||||||
public final THashMap<Integer, CatalogLimitedConfiguration> limitedNumbers;
|
public final THashMap<Integer, CatalogLimitedConfiguration> limitedNumbers;
|
||||||
private final List<Voucher> vouchers;
|
private final List<Voucher> vouchers;
|
||||||
// spriteId -> [credits, points, pointsType], derived from catalog_items (see loadFurnitureValues)
|
|
||||||
public final TIntObjectMap<int[]> furnitureValues;
|
public final TIntObjectMap<int[]> furnitureValues;
|
||||||
|
private volatile byte[] rareValuesPayloadCache;
|
||||||
|
|
||||||
public CatalogManager() {
|
public CatalogManager() {
|
||||||
long millis = System.currentTimeMillis();
|
long millis = System.currentTimeMillis();
|
||||||
@@ -249,10 +249,6 @@ public class CatalogManager {
|
|||||||
this.loadFurnitureValues();
|
this.loadFurnitureValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds spriteId -> [credits, points, pointsType] from catalog_items so the
|
|
||||||
// client can show a furni's "value" (toolbar price guide + infostand line).
|
|
||||||
// Only single-item, single-amount FLOOR/WALL sales are considered, so bundles
|
|
||||||
// and multi-packs don't pollute the per-rare price. First clean entry wins.
|
|
||||||
private synchronized void loadFurnitureValues() {
|
private synchronized void loadFurnitureValues() {
|
||||||
this.furnitureValues.clear();
|
this.furnitureValues.clear();
|
||||||
final int diamondType = Emulator.getConfig().getInt("seasonal.currency.diamond", 5);
|
final int diamondType = Emulator.getConfig().getInt("seasonal.currency.diamond", 5);
|
||||||
@@ -266,8 +262,6 @@ public class CatalogManager {
|
|||||||
int points = catalogItem.getPoints();
|
int points = catalogItem.getPoints();
|
||||||
int pointsType = catalogItem.getPointsType();
|
int pointsType = catalogItem.getPointsType();
|
||||||
|
|
||||||
// Only diamond-priced items — both the "Valore Rari" panel and the
|
|
||||||
// infostand value line show diamonds only.
|
|
||||||
if (points <= 0 || pointsType != diamondType)
|
if (points <= 0 || pointsType != diamondType)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -291,13 +285,39 @@ public class CatalogManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.rebuildRareValuesPayloadCache();
|
||||||
|
|
||||||
LOGGER.info("Furniture Values -> Loaded! ({} entries)", this.furnitureValues.size());
|
LOGGER.info("Furniture Values -> Loaded! ({} entries)", this.furnitureValues.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void rebuildRareValuesPayloadCache() {
|
||||||
|
try (java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(this.furnitureValues.size() * 16 + 8);
|
||||||
|
java.io.DataOutputStream out = new java.io.DataOutputStream(baos)) {
|
||||||
|
out.writeInt(this.furnitureValues.size());
|
||||||
|
TIntObjectIterator<int[]> iterator = this.furnitureValues.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
iterator.advance();
|
||||||
|
int[] value = iterator.value();
|
||||||
|
out.writeInt(iterator.key()); // spriteId
|
||||||
|
out.writeInt(value[0]); // credits
|
||||||
|
out.writeInt(value[1]); // points
|
||||||
|
out.writeInt(value[2]); // pointsType
|
||||||
|
}
|
||||||
|
this.rareValuesPayloadCache = baos.toByteArray();
|
||||||
|
} catch (java.io.IOException e) {
|
||||||
|
LOGGER.error("Failed to build rare values payload cache", e);
|
||||||
|
this.rareValuesPayloadCache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public TIntObjectMap<int[]> getFurnitureValues() {
|
public TIntObjectMap<int[]> getFurnitureValues() {
|
||||||
return this.furnitureValues;
|
return this.furnitureValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getRareValuesPayloadSnapshot() {
|
||||||
|
return this.rareValuesPayloadCache;
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void loadLimitedNumbers() {
|
private synchronized void loadLimitedNumbers() {
|
||||||
this.limitedNumbers.clear();
|
this.limitedNumbers.clear();
|
||||||
|
|
||||||
@@ -1104,9 +1124,6 @@ public class CatalogManager {
|
|||||||
type = type.replace("bot_", "");
|
type = type.replace("bot_", "");
|
||||||
type = type.replace("visitor_logger", "visitor_log");
|
type = type.replace("visitor_logger", "visitor_log");
|
||||||
|
|
||||||
// Permission gate keyed on the canonical base-item name
|
|
||||||
// (admin-controlled but stable), not the catalog page name
|
|
||||||
// which can be renamed and bypass the check.
|
|
||||||
if (("bot_" + com.eu.habbo.habbohotel.bots.FrankBot.BOT_TYPE).equals(baseName)
|
if (("bot_" + com.eu.habbo.habbohotel.bots.FrankBot.BOT_TYPE).equals(baseName)
|
||||||
|| ("rentable_bot_" + com.eu.habbo.habbohotel.bots.FrankBot.BOT_TYPE).equals(baseName)) {
|
|| ("rentable_bot_" + com.eu.habbo.habbohotel.bots.FrankBot.BOT_TYPE).equals(baseName)) {
|
||||||
if (!habbo.getClient().getHabbo().hasPermission(com.eu.habbo.habbohotel.bots.FrankBot.PERMISSION_USE)) {
|
if (!habbo.getClient().getHabbo().hasPermission(com.eu.habbo.habbohotel.bots.FrankBot.PERMISSION_USE)) {
|
||||||
|
|||||||
+7
-3
@@ -42,14 +42,18 @@ public class RoomBundleLayout extends SingleBundle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.room == null) {
|
if (this.room == null) {
|
||||||
if (this.roomId > 0) {
|
RoomManager roomManager = Emulator.getGameEnvironment().getRoomManager();
|
||||||
this.room = Emulator.getGameEnvironment().getRoomManager().loadRoom(this.roomId);
|
if (this.roomId > 0 && roomManager != null) {
|
||||||
|
this.room = roomManager.loadRoom(this.roomId);
|
||||||
|
|
||||||
if (this.room != null)
|
if (this.room != null)
|
||||||
this.room.preventUnloading = true;
|
this.room.preventUnloading = true;
|
||||||
} else {
|
} else if (this.roomId <= 0) {
|
||||||
LOGGER.error("No room id specified for room bundle {}({})", this.getPageName(), this.getId());
|
LOGGER.error("No room id specified for room bundle {}({})", this.getPageName(), this.getId());
|
||||||
}
|
}
|
||||||
|
// roomManager can be null when CatalogManager.loadFurnitureValues() runs
|
||||||
|
// during GameEnvironment.load() before RoomManager is constructed; in that
|
||||||
|
// case skip eager room loading — the bundle resolves lazily at runtime.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.room == null) {
|
if (this.room == null) {
|
||||||
|
|||||||
@@ -153,7 +153,13 @@ public class GameClient {
|
|||||||
this.channel.close();
|
this.channel.close();
|
||||||
|
|
||||||
if (this.habbo != null) {
|
if (this.habbo != null) {
|
||||||
if (this.habbo.isOnline()) {
|
// Agisci sull'Habbo SOLO se è ancora attaccato a QUESTO client. Su un
|
||||||
|
// reconnect veloce (drop Cloudflare → il client riconnette) l'Habbo può
|
||||||
|
// essere già stato riassegnato alla NUOVA connessione (session resume):
|
||||||
|
// in quel caso questo dispose della vecchia connessione NON deve
|
||||||
|
// parcheggiarlo né disconnetterlo, altrimenti ucciderebbe la sessione
|
||||||
|
// appena ripristinata (era la causa del "Bye"/kick al 2° reconnect).
|
||||||
|
if (this.habbo.getClient() == this && this.habbo.isOnline()) {
|
||||||
// Try to park the habbo in the grace period instead of immediate disconnect
|
// Try to park the habbo in the grace period instead of immediate disconnect
|
||||||
boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket);
|
boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket);
|
||||||
|
|
||||||
|
|||||||
+20
-4
@@ -118,16 +118,32 @@ public class SessionResumeManager {
|
|||||||
LOGGER.error("[SessionResume] Error during deferred disconnect", e);
|
LOGGER.error("[SessionResume] Error during deferred disconnect", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSsoTicket(habbo.getHabboInfo().getId());
|
// NON svuotare il ticket SSO qui. Dietro Cloudflare la pagina si ricarica
|
||||||
|
// lentamente (~15s) e la grace (5s) scade prima che la nuova connessione
|
||||||
|
// arrivi: svuotando il ticket si cancellava quello NUOVO appena scritto dal
|
||||||
|
// CMS per il refresh → "non-existing SSO token" → bisognava refreshare 2 volte.
|
||||||
|
// Il ticket vive col suo TTL (auth_ticket_expires_at) e viene sovrascritto dal
|
||||||
|
// CMS al prossimo /client o azzerato al logout.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreSsoTicket(int userId, String ssoTicket) {
|
private void restoreSsoTicket(int userId, String ssoTicket) {
|
||||||
|
// Restore the old ticket ONLY if no fresh ticket has been written in the
|
||||||
|
// meantime. On a hard-refresh the CMS writes a NEW auth_ticket for the same
|
||||||
|
// user before this parking restore runs; without the guard we'd clobber it
|
||||||
|
// with the old ticket, so the new connection's SSO wouldn't be found and the
|
||||||
|
// client would get "session expired" on the first attempt. The guard means:
|
||||||
|
// normal reconnect (ticket cleared to '' after login) -> restore; hard-refresh
|
||||||
|
// (CMS already wrote a new ticket) -> leave the new ticket untouched.
|
||||||
try (var connection = Emulator.getDatabase().getDataSource().getConnection();
|
try (var connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
|
var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? AND (auth_ticket = '' OR auth_ticket IS NULL) LIMIT 1")) {
|
||||||
statement.setString(1, ssoTicket);
|
statement.setString(1, ssoTicket);
|
||||||
statement.setInt(2, userId);
|
statement.setInt(2, userId);
|
||||||
statement.execute();
|
int updated = statement.executeUpdate();
|
||||||
LOGGER.info("[SessionResume] Restored SSO ticket for user {} during grace period", userId);
|
if (updated > 0) {
|
||||||
|
LOGGER.info("[SessionResume] Restored SSO ticket for user {} during grace period", userId);
|
||||||
|
} else {
|
||||||
|
LOGGER.info("[SessionResume] Skipped SSO restore for user {} — a newer ticket is already present (likely a fresh login/hard-refresh)", userId);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("[SessionResume] Failed to restore SSO ticket for user " + userId, e);
|
LOGGER.error("[SessionResume] Failed to restore SSO ticket for user " + userId, e);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -29,6 +29,10 @@ public class InteractionRoomAds extends InteractionCustomValues {
|
|||||||
{
|
{
|
||||||
this.put("offsetZ", "0");
|
this.put("offsetZ", "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
this.put("scale", "100");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public InteractionRoomAds(ResultSet set, Item baseItem) throws SQLException {
|
public InteractionRoomAds(ResultSet set, Item baseItem) throws SQLException {
|
||||||
|
|||||||
@@ -132,15 +132,12 @@ public class HabboManager {
|
|||||||
Emulator.getPluginManager().fireEvent(new UserRegisteredEvent(habbo));
|
Emulator.getPluginManager().fireEvent(new UserRegisteredEvent(habbo));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Emulator.debugging) {
|
// NB: il ticket SSO NON viene svuotato qui di proposito. Dietro
|
||||||
try (PreparedStatement stmt = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
|
// Cloudflare il WebSocket viene droppato e il client ritenta più
|
||||||
stmt.setString(1, "");
|
// volte con lo STESSO ticket: se lo consumassimo al primo uso, i
|
||||||
stmt.setInt(2, habbo.getHabboInfo().getId());
|
// retry (e l'hard-refresh) fallirebbero con "non-existing SSO token".
|
||||||
stmt.execute();
|
// Il ticket resta valido fino alla scadenza (auth_ticket_expires_at,
|
||||||
} catch (SQLException e) {
|
// TTL gestito dal CMS) o finché il CMS non ne scrive uno nuovo / logout.
|
||||||
LOGGER.error("Caught SQL exception", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
|||||||
+4
-11
@@ -133,17 +133,10 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
this.client.setHabbo(habbo);
|
this.client.setHabbo(habbo);
|
||||||
this.client.setMachineId(habbo.getHabboInfo().getMachineID());
|
this.client.setMachineId(habbo.getHabboInfo().getMachineID());
|
||||||
|
|
||||||
// Clear the SSO ticket now that session is resumed (prevent reuse)
|
// NB: NON svuotiamo il ticket SSO qui (vedi HabboManager.loadHabbo):
|
||||||
if (!Emulator.debugging) {
|
// dietro Cloudflare il client ritenta la connessione con lo stesso
|
||||||
try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
// ticket, quindi deve restare valido fino alla scadenza TTL. Consumarlo
|
||||||
java.sql.PreparedStatement stmt = conn.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
|
// farebbe fallire i retry / l'hard-refresh con "non-existing SSO token".
|
||||||
stmt.setString(1, "");
|
|
||||||
stmt.setInt(2, habbo.getHabboInfo().getId());
|
|
||||||
stmt.execute();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error("Failed to clear SSO ticket after session resume", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Normal login — load from database
|
// Normal login — load from database
|
||||||
habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso);
|
habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso);
|
||||||
|
|||||||
+11
-5
@@ -1,11 +1,10 @@
|
|||||||
package com.eu.habbo.messages.incoming.rarevalues;
|
package com.eu.habbo.messages.incoming.rarevalues;
|
||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.catalog.CatalogManager;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import com.eu.habbo.messages.outgoing.rarevalues.RareValuesComposer;
|
import com.eu.habbo.messages.outgoing.rarevalues.RareValuesComposer;
|
||||||
|
|
||||||
// Client requests the furni value map once on load. Public info (catalog prices),
|
|
||||||
// no permission gate. Rate limited since the payload is large.
|
|
||||||
public class RequestRareValuesEvent extends MessageHandler {
|
public class RequestRareValuesEvent extends MessageHandler {
|
||||||
@Override
|
@Override
|
||||||
public int getRatelimit() {
|
public int getRatelimit() {
|
||||||
@@ -14,8 +13,15 @@ public class RequestRareValuesEvent extends MessageHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle() throws Exception {
|
public void handle() throws Exception {
|
||||||
this.client.sendResponse(new RareValuesComposer(
|
if (this.client.getHabbo() == null) return;
|
||||||
Emulator.getGameEnvironment().getCatalogManager().getFurnitureValues()
|
|
||||||
));
|
CatalogManager catalog = Emulator.getGameEnvironment().getCatalogManager();
|
||||||
|
byte[] snapshot = catalog.getRareValuesPayloadSnapshot();
|
||||||
|
if (snapshot != null) {
|
||||||
|
this.client.sendResponse(new RareValuesComposer(snapshot));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.sendResponse(new RareValuesComposer(catalog.getFurnitureValues()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -6,18 +6,29 @@ import com.eu.habbo.messages.outgoing.Outgoing;
|
|||||||
import gnu.trove.iterator.TIntObjectIterator;
|
import gnu.trove.iterator.TIntObjectIterator;
|
||||||
import gnu.trove.map.TIntObjectMap;
|
import gnu.trove.map.TIntObjectMap;
|
||||||
|
|
||||||
// Sends the full spriteId -> value map to the client. Consumed by the toolbar
|
|
||||||
// price guide and the furni infostand "value" line. See CatalogManager#loadFurnitureValues.
|
|
||||||
public class RareValuesComposer extends MessageComposer {
|
public class RareValuesComposer extends MessageComposer {
|
||||||
private final TIntObjectMap<int[]> values;
|
private final TIntObjectMap<int[]> values;
|
||||||
|
private final byte[] snapshot;
|
||||||
|
|
||||||
|
public RareValuesComposer(byte[] snapshot) {
|
||||||
|
this.values = null;
|
||||||
|
this.snapshot = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
public RareValuesComposer(TIntObjectMap<int[]> values) {
|
public RareValuesComposer(TIntObjectMap<int[]> values) {
|
||||||
this.values = values;
|
this.values = values;
|
||||||
|
this.snapshot = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ServerMessage composeInternal() {
|
protected ServerMessage composeInternal() {
|
||||||
this.response.init(Outgoing.RareValuesComposer);
|
this.response.init(Outgoing.RareValuesComposer);
|
||||||
|
|
||||||
|
if (this.snapshot != null) {
|
||||||
|
this.response.appendRawBytes(this.snapshot);
|
||||||
|
return this.response;
|
||||||
|
}
|
||||||
|
|
||||||
this.response.appendInt(this.values.size());
|
this.response.appendInt(this.values.size());
|
||||||
|
|
||||||
TIntObjectIterator<int[]> iterator = this.values.iterator();
|
TIntObjectIterator<int[]> iterator = this.values.iterator();
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.eu.habbo.messages.rcon;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
// Ricarica i suoni della Soundboard dal DB (live), così i suoni aggiunti/caricati
|
||||||
|
// dal CMS (/admin/soundboard) si applicano senza riavviare l'emulatore.
|
||||||
|
public class UpdateSoundboard extends RCONMessage<UpdateSoundboard.SoundboardJSON> {
|
||||||
|
|
||||||
|
public UpdateSoundboard() {
|
||||||
|
super(SoundboardJSON.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Gson gson, SoundboardJSON object) {
|
||||||
|
Emulator.getGameEnvironment().getSoundboardManager().reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SoundboardJSON {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.eu.habbo.messages.rcon;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
// Ricarica i premi/settings della Ruota della Fortuna dal DB (live), così le
|
||||||
|
// modifiche fatte dal CMS (/admin/wheel) si applicano senza riavviare l'emulatore.
|
||||||
|
public class UpdateWheel extends RCONMessage<UpdateWheel.WheelJSON> {
|
||||||
|
|
||||||
|
public UpdateWheel() {
|
||||||
|
super(WheelJSON.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Gson gson, WheelJSON object) {
|
||||||
|
Emulator.getGameEnvironment().getWheelManager().reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class WheelJSON {
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -11,6 +11,7 @@ import io.netty.handler.codec.DecoderException;
|
|||||||
import io.netty.handler.codec.TooLongFrameException;
|
import io.netty.handler.codec.TooLongFrameException;
|
||||||
import io.netty.handler.codec.UnsupportedMessageTypeException;
|
import io.netty.handler.codec.UnsupportedMessageTypeException;
|
||||||
import io.netty.handler.ssl.NotSslRecordException;
|
import io.netty.handler.ssl.NotSslRecordException;
|
||||||
|
import io.netty.util.ReferenceCountUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -39,6 +40,19 @@ public class GameMessageHandler extends ChannelInboundHandlerAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
if (!(msg instanceof ClientMessage)) {
|
||||||
|
try {
|
||||||
|
if (Emulator.getConfig().getBoolean("debug.mode")) {
|
||||||
|
LOGGER.debug("Discarding non-game message {} from {}",
|
||||||
|
msg.getClass().getSimpleName(), ctx.channel().remoteAddress());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
ReferenceCountUtil.release(msg);
|
||||||
|
ctx.channel().close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ClientMessage message = (ClientMessage) msg;
|
ClientMessage message = (ClientMessage) msg;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public class RCONServer extends Server {
|
|||||||
this.addRCONMessage("sendroombundle", SendRoomBundle.class);
|
this.addRCONMessage("sendroombundle", SendRoomBundle.class);
|
||||||
this.addRCONMessage("setrank", SetRank.class);
|
this.addRCONMessage("setrank", SetRank.class);
|
||||||
this.addRCONMessage("updatewordfilter", UpdateWordfilter.class);
|
this.addRCONMessage("updatewordfilter", UpdateWordfilter.class);
|
||||||
|
this.addRCONMessage("updatewheel", UpdateWheel.class);
|
||||||
|
this.addRCONMessage("updatesoundboard", UpdateSoundboard.class);
|
||||||
this.addRCONMessage("updatecatalog", UpdateCatalog.class);
|
this.addRCONMessage("updatecatalog", UpdateCatalog.class);
|
||||||
this.addRCONMessage("executecommand", ExecuteCommand.class);
|
this.addRCONMessage("executecommand", ExecuteCommand.class);
|
||||||
this.addRCONMessage("progressachievement", ProgressAchievement.class);
|
this.addRCONMessage("progressachievement", ProgressAchievement.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user